Skip to content

Commit

Permalink
mint - handle pending mint quotes
Browse files Browse the repository at this point in the history
  • Loading branch information
elnosh committed Nov 26, 2024
1 parent 0b4bfe9 commit 2df0ca3
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 62 deletions.
2 changes: 1 addition & 1 deletion cashu/cashu.go
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
5 changes: 5 additions & 0 deletions cashu/nuts/nut04/nut04.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const (
Unpaid State = iota
Paid
Issued
Pending
Unknown
)

Expand All @@ -26,6 +27,8 @@ func (state State) String() string {
return "PAID"
case Issued:
return "ISSUED"
case Pending:
return "PENDING"
default:
return "unknown"
}
Expand All @@ -39,6 +42,8 @@ func StringToState(state string) State {
return Paid
case "ISSUED":
return Issued
case "PENDING":
return Pending
}
return Unknown
}
Expand Down
121 changes: 62 additions & 59 deletions mint/mint.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions mint/mint_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit 2df0ca3

Please sign in to comment.