From 8514b2736a82eda895c5cfb27017a8997a9c5092 Mon Sep 17 00:00:00 2001 From: elnosh Date: Mon, 25 Nov 2024 16:32:14 -0500 Subject: [PATCH] mint - handle pending mint quotes --- cashu/cashu.go | 2 +- cashu/nuts/nut04/nut04.go | 5 ++ mint/mint.go | 121 +++++++++++++++++----------------- mint/mint_integration_test.go | 4 +- 4 files changed, 70 insertions(+), 62 deletions(-) diff --git a/cashu/cashu.go b/cashu/cashu.go index d1fc55e..435e71e 100644 --- a/cashu/cashu.go +++ b/cashu/cashu.go @@ -499,7 +499,7 @@ var ( NoProofsProvided = Error{Detail: "no proofs provided", Code: InvalidProofErrCode} DuplicateProofs = Error{Detail: "duplicate proofs", Code: InvalidProofErrCode} QuoteNotExistErr = Error{Detail: "quote does not exist", Code: MeltQuoteErrCode} - MeltQuotePending = Error{Detail: "quote is pending", Code: MeltQuotePendingErrCode} + QuotePending = Error{Detail: "quote is pending", Code: MeltQuotePendingErrCode} MeltQuoteAlreadyPaid = Error{Detail: "quote already paid", Code: MeltQuoteAlreadyPaidErrCode} MeltAmountExceededErr = Error{Detail: "max amount for melting exceeded", Code: AmountLimitExceeded} MeltQuoteForRequestExists = Error{Detail: "melt quote for payment request already exists", Code: MeltQuoteErrCode} diff --git a/cashu/nuts/nut04/nut04.go b/cashu/nuts/nut04/nut04.go index cc43975..122a227 100644 --- a/cashu/nuts/nut04/nut04.go +++ b/cashu/nuts/nut04/nut04.go @@ -15,6 +15,7 @@ const ( Unpaid State = iota Paid Issued + Pending Unknown ) @@ -26,6 +27,8 @@ func (state State) String() string { return "PAID" case Issued: return "ISSUED" + case Pending: + return "PENDING" default: return "unknown" } @@ -39,6 +42,8 @@ func StringToState(state string) State { return Paid case "ISSUED": return Issued + case "PENDING": + return Pending } return Unknown } diff --git a/mint/mint.go b/mint/mint.go index 19fb74a..bd0e2c9 100644 --- a/mint/mint.go +++ b/mint/mint.go @@ -302,7 +302,6 @@ func (m *Mint) RequestMintQuote(mintQuoteRequest nut04.PostMintQuoteBolt11Reques } // GetMintQuoteState returns the state of a mint quote. -// Used to check whether a mint quote has been paid. func (m *Mint) GetMintQuoteState(quoteId string) (storage.MintQuote, error) { mintQuote, err := m.db.GetMintQuote(quoteId) if err != nil { @@ -335,78 +334,82 @@ func (m *Mint) GetMintQuoteState(quoteId string) (storage.MintQuote, error) { // MintTokens verifies whether the mint quote with id has been paid and proceeds to // sign the blindedMessages and return the BlindedSignatures if it was paid. func (m *Mint) MintTokens(mintTokensRequest nut04.PostMintBolt11Request) (cashu.BlindedSignatures, error) { - mintQuote, err := m.db.GetMintQuote(mintTokensRequest.Quote) + mintQuote, err := m.GetMintQuoteState(mintTokensRequest.Quote) if err != nil { - return nil, cashu.QuoteNotExistErr + return nil, err } - blindedMessages := mintTokensRequest.Outputs var blindedSignatures cashu.BlindedSignatures - invoicePaid := false - if mintQuote.State == nut04.Unpaid { - m.logDebugf("checking status of invoice with hash '%v'", mintQuote.PaymentHash) - invoiceStatus, err := m.lightningClient.InvoiceStatus(mintQuote.PaymentHash) - if err != nil { - errmsg := fmt.Sprintf("error getting invoice status: %v", err) - return nil, cashu.BuildCashuError(errmsg, cashu.LightningBackendErrCode) - } - if invoiceStatus.Settled { - m.logInfof("mint quote '%v' with invoice payment hash '%v' was paid", mintQuote.Id, mintQuote.PaymentHash) - invoicePaid = true - } - } else { - invoicePaid = true - } - - if invoicePaid { - if mintQuote.State == nut04.Issued { - return nil, cashu.MintQuoteAlreadyIssued - } + switch mintQuote.State { + case nut04.Unpaid: + return nil, cashu.MintQuoteRequestNotPaid + case nut04.Issued: + return nil, cashu.MintQuoteAlreadyIssued + case nut04.Pending: + return nil, cashu.QuotePending + case nut04.Paid: + err := func() error { + // set quote as pending while validating blinded messages and signing + err = m.db.UpdateMintQuoteState(mintQuote.Id, nut04.Pending) + if err != nil { + errmsg := fmt.Sprintf("error mint quote state: %v", err) + return cashu.BuildCashuError(errmsg, cashu.DBErrCode) + } - var blindedMessagesAmount uint64 - B_s := make([]string, len(blindedMessages)) - for i, bm := range blindedMessages { - blindedMessagesAmount += bm.Amount - B_s[i] = bm.B_ - } + blindedMessages := mintTokensRequest.Outputs + var blindedMessagesAmount uint64 + B_s := make([]string, len(blindedMessages)) + for i, bm := range blindedMessages { + blindedMessagesAmount += bm.Amount + B_s[i] = bm.B_ + } - if len(blindedMessages) > 0 { - for _, msg := range blindedMessages { - if blindedMessagesAmount < msg.Amount { - return nil, cashu.InvalidBlindedMessageAmount + if len(blindedMessages) > 0 { + for _, msg := range blindedMessages { + if blindedMessagesAmount < msg.Amount { + return cashu.InvalidBlindedMessageAmount + } } } - } - // verify that amount from blinded messages is less - // than quote amount - if blindedMessagesAmount > mintQuote.Amount { - return nil, cashu.OutputsOverQuoteAmountErr - } + // verify that amount from blinded messages is less + // than quote amount + if blindedMessagesAmount > mintQuote.Amount { + return cashu.OutputsOverQuoteAmountErr + } - sigs, err := m.db.GetBlindSignatures(B_s) - if err != nil { - errmsg := fmt.Sprintf("error getting blind signatures from db: %v", err) - return nil, cashu.BuildCashuError(errmsg, cashu.DBErrCode) - } - if len(sigs) > 0 { - return nil, cashu.BlindedMessageAlreadySigned - } + sigs, err := m.db.GetBlindSignatures(B_s) + if err != nil { + errmsg := fmt.Sprintf("error getting blind signatures from db: %v", err) + return cashu.BuildCashuError(errmsg, cashu.DBErrCode) + } - blindedSignatures, err = m.signBlindedMessages(blindedMessages) - if err != nil { - return nil, err - } + if len(sigs) > 0 { + return cashu.BlindedMessageAlreadySigned + } - // mark quote as issued after signing the blinded messages - err = m.db.UpdateMintQuoteState(mintQuote.Id, nut04.Issued) + blindedSignatures, err = m.signBlindedMessages(blindedMessages) + if err != nil { + return err + } + + // mark quote as issued after signing the blinded messages + err = m.db.UpdateMintQuoteState(mintQuote.Id, nut04.Issued) + if err != nil { + errmsg := fmt.Sprintf("error mint quote state: %v", err) + return cashu.BuildCashuError(errmsg, cashu.DBErrCode) + } + return nil + }() + + // update mint quote to previous state if there was an error if err != nil { - errmsg := fmt.Sprintf("error mint quote state: %v", err) - return nil, cashu.BuildCashuError(errmsg, cashu.DBErrCode) + if err := m.db.UpdateMintQuoteState(mintQuote.Id, mintQuote.State); err != nil { + return nil, err + } + return nil, err } - } else { - return nil, cashu.MintQuoteRequestNotPaid } return blindedSignatures, nil @@ -685,7 +688,7 @@ func (m *Mint) MeltTokens(ctx context.Context, meltTokensRequest nut05.PostMeltB return storage.MeltQuote{}, cashu.MeltQuoteAlreadyPaid } if meltQuote.State == nut05.Pending { - return storage.MeltQuote{}, cashu.MeltQuotePending + return storage.MeltQuote{}, cashu.QuotePending } err = m.verifyProofs(proofs, Ys) diff --git a/mint/mint_integration_test.go b/mint/mint_integration_test.go index 2b7b6f3..3c6b262 100644 --- a/mint/mint_integration_test.go +++ b/mint/mint_integration_test.go @@ -739,8 +739,8 @@ func TestPendingProofs(t *testing.T) { meltTokensRequest = nut05.PostMeltBolt11Request{Quote: meltQuote.Id, Inputs: validProofs} _, err = testMint.MeltTokens(ctx, meltTokensRequest) - if !errors.Is(err, cashu.MeltQuotePending) { - t.Fatalf("expected error '%v' but got '%v' instead", cashu.MeltQuotePending, err) + if !errors.Is(err, cashu.QuotePending) { + t.Fatalf("expected error '%v' but got '%v' instead", cashu.QuotePending, err) } // try to use currently pending proofs in another op.