compensation.rs 2.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071
  1. //! Compensation actions for the melt saga pattern.
  2. //!
  3. //! When a saga step fails, compensating actions are executed in reverse order (LIFO)
  4. //! to undo all completed steps and restore the database to its pre-saga state.
  5. use async_trait::async_trait;
  6. use cdk_common::database::DynMintDatabase;
  7. use cdk_common::{Error, PublicKey, QuoteId};
  8. use tracing::instrument;
  9. use uuid::Uuid;
  10. /// Trait for compensating actions in the saga pattern.
  11. ///
  12. /// Compensating actions are registered as steps complete and executed in reverse
  13. /// order (LIFO) if the saga fails. Each action should be idempotent.
  14. #[async_trait]
  15. pub trait CompensatingAction: Send + Sync {
  16. async fn execute(&self, db: &DynMintDatabase) -> Result<(), Error>;
  17. fn name(&self) -> &'static str;
  18. }
  19. /// Compensation action to remove melt setup and reset quote state.
  20. ///
  21. /// This compensation is used when payment fails or finalization fails after
  22. /// the setup transaction has committed. It removes:
  23. /// - Input proofs (identified by input_ys)
  24. /// - Output blinded messages (identified by blinded_secrets)
  25. /// - Melt request tracking record
  26. /// - Saga state record
  27. ///
  28. /// And resets:
  29. /// - Quote state from Pending back to Unpaid
  30. ///
  31. /// This restores the database to its pre-melt state, allowing the user to retry.
  32. pub struct RemoveMeltSetup {
  33. /// Y values (public keys) from the input proofs
  34. pub input_ys: Vec<PublicKey>,
  35. /// Blinded secrets (B values) from the change output blinded messages
  36. pub blinded_secrets: Vec<PublicKey>,
  37. /// Quote ID to reset state
  38. pub quote_id: QuoteId,
  39. /// Operation ID (saga ID) to delete
  40. pub operation_id: Uuid,
  41. }
  42. #[async_trait]
  43. impl CompensatingAction for RemoveMeltSetup {
  44. #[instrument(skip_all)]
  45. async fn execute(&self, db: &DynMintDatabase) -> Result<(), Error> {
  46. tracing::info!(
  47. "Compensation: Removing melt setup for quote {} ({} proofs, {} blinded messages, saga {})",
  48. self.quote_id,
  49. self.input_ys.len(),
  50. self.blinded_secrets.len(),
  51. self.operation_id
  52. );
  53. super::super::shared::rollback_melt_quote(
  54. db,
  55. &self.quote_id,
  56. &self.input_ys,
  57. &self.blinded_secrets,
  58. &self.operation_id,
  59. )
  60. .await
  61. }
  62. fn name(&self) -> &'static str {
  63. "RemoveMeltSetup"
  64. }
  65. }