From 7d60ac5e3761fb9baa5120f299753388c5dc13b7 Mon Sep 17 00:00:00 2001 From: elnosh Date: Fri, 4 Oct 2024 15:57:08 -0500 Subject: [PATCH] mint - only fail melt for failed status check --- mint/lightning/lightning.go | 5 +- mint/lightning/lnd.go | 6 +- mint/mint.go | 115 ++++++++++++++++++------------------ mint/server.go | 2 +- 4 files changed, 63 insertions(+), 65 deletions(-) diff --git a/mint/lightning/lightning.go b/mint/lightning/lightning.go index 8f27465..c35d0e5 100644 --- a/mint/lightning/lightning.go +++ b/mint/lightning/lightning.go @@ -29,6 +29,7 @@ const ( ) type PaymentStatus struct { - Preimage string - PaymentStatus State + Preimage string + PaymentStatus State + PaymentFailureReason string } diff --git a/mint/lightning/lnd.go b/mint/lightning/lnd.go index b55cee7..414cfc1 100644 --- a/mint/lightning/lnd.go +++ b/mint/lightning/lnd.go @@ -146,14 +146,12 @@ func (lnd *LndClient) OutgoingPaymentStatus(ctx context.Context, hash string) (P if err != nil { if errors.Is(ctx.Err(), context.DeadlineExceeded) || strings.Contains(err.Error(), "context deadline exceeded") { - return PaymentStatus{PaymentStatus: Pending}, nil } - return PaymentStatus{PaymentStatus: Failed}, fmt.Errorf("payment failed: %w", err) + return PaymentStatus{PaymentStatus: Failed}, err } if payment.Status == lnrpc.Payment_UNKNOWN || payment.Status == lnrpc.Payment_FAILED { - return PaymentStatus{PaymentStatus: Failed}, - fmt.Errorf("payment failed: %s", payment.FailureReason.String()) + return PaymentStatus{PaymentStatus: Failed, PaymentFailureReason: payment.FailureReason.String()}, nil } if payment.Status == lnrpc.Payment_IN_FLIGHT { return PaymentStatus{PaymentStatus: Pending}, nil diff --git a/mint/mint.go b/mint/mint.go index 4df5a69..33bb3b6 100644 --- a/mint/mint.go +++ b/mint/mint.go @@ -16,7 +16,6 @@ import ( "reflect" "runtime" "slices" - "strings" "time" "github.com/btcsuite/btcd/btcec/v2" @@ -35,6 +34,8 @@ import ( "github.com/elnosh/gonuts/mint/storage" "github.com/elnosh/gonuts/mint/storage/sqlite" decodepay "github.com/nbd-wtf/ln-decodepay" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) const ( @@ -602,37 +603,18 @@ func (m *Mint) GetMeltQuoteState(ctx context.Context, method, quoteId string) (s meltQuote.PaymentHash, meltQuote.Id) paymentStatus, err := m.lightningClient.OutgoingPaymentStatus(ctx, meltQuote.PaymentHash) - if paymentStatus.PaymentStatus == lightning.Pending { - m.logInfof("payment '%v' melt for quote '%v' is pending", meltQuote.PaymentHash, meltQuote.Id) - return meltQuote, nil - } if err != nil { - // if it gets to here, payment failed. - // mark quote as unpaid and remove pending proofs - if paymentStatus.PaymentStatus == lightning.Failed && strings.Contains(err.Error(), "payment failed") { - m.logInfof("payment %v failed. setting melt quote '%v' to '%s' and removing proofs from pending", - meltQuote.PaymentHash, meltQuote.Id, nut05.Unpaid) - - meltQuote.State = nut05.Unpaid - err = m.db.UpdateMeltQuote(meltQuote.Id, "", meltQuote.State) - if err != nil { - errmsg := fmt.Sprintf("error updating melt quote state: %v", err) - return storage.MeltQuote{}, cashu.BuildCashuError(errmsg, cashu.DBErrCode) - } - - _, err = m.removePendingProofsForQuote(meltQuote.Id) - if err != nil { - errmsg := fmt.Sprintf("error removing pending proofs for quote: %v", err) - return storage.MeltQuote{}, cashu.BuildCashuError(errmsg, cashu.DBErrCode) - } - } + m.logErrorf(`error checking outgoing payment status: %v. Leaving proofs for quote '%v' as pending`, + err, meltQuote.Id) + return meltQuote, nil } + switch paymentStatus.PaymentStatus { // settle proofs (remove pending, and add to used) // mark quote as paid and set preimage - if paymentStatus.PaymentStatus == lightning.Succeeded { - m.logInfof("payment %v succeded. setting melt quote '%v' to '%v' and invalidating proofs", - meltQuote.PaymentHash, meltQuote.Id, nut05.Paid) + case lightning.Succeeded: + m.logInfof("payment %v succeded. setting melt quote '%v' to paid and invalidating proofs", + meltQuote.PaymentHash, meltQuote.Id) proofs, err := m.removePendingProofsForQuote(meltQuote.Id) if err != nil { @@ -652,6 +634,23 @@ func (m *Mint) GetMeltQuoteState(ctx context.Context, method, quoteId string) (s errmsg := fmt.Sprintf("error updating melt quote state: %v", err) return storage.MeltQuote{}, cashu.BuildCashuError(errmsg, cashu.DBErrCode) } + + case lightning.Failed: + m.logInfof("payment %v failed with error: %v. Setting melt quote '%v' to unpaid and removing proofs from pending", + meltQuote.PaymentHash, paymentStatus.PaymentFailureReason, meltQuote.Id) + + meltQuote.State = nut05.Unpaid + err = m.db.UpdateMeltQuote(meltQuote.Id, "", meltQuote.State) + if err != nil { + errmsg := fmt.Sprintf("error updating melt quote state: %v", err) + return storage.MeltQuote{}, cashu.BuildCashuError(errmsg, cashu.DBErrCode) + } + + _, err = m.removePendingProofsForQuote(meltQuote.Id) + if err != nil { + errmsg := fmt.Sprintf("error removing pending proofs for quote: %v", err) + return storage.MeltQuote{}, cashu.BuildCashuError(errmsg, cashu.DBErrCode) + } } } @@ -770,28 +769,7 @@ func (m *Mint) MeltTokens(ctx context.Context, method, quoteId string, proofs ca // if quote can't be settled internally, ask backend to make payment sendPaymentResponse, err := m.lightningClient.SendPayment(ctx, meltQuote.InvoiceRequest, meltQuote.Amount) if err != nil { - // if the payment error field was present in the response from SendPayment - // the payment most likely failed so we can already return unpaid state here - if strings.Contains(err.Error(), "payment error") { - m.logInfof("payment failed with error: %v. Removing pending proofs and marking quote '%v' as '%v'", - err, meltQuote.Id, nut05.Unpaid) - - meltQuote.State = nut05.Unpaid - err = m.db.UpdateMeltQuote(meltQuote.Id, "", meltQuote.State) - if err != nil { - errmsg := fmt.Sprintf("error updating melt quote state: %v", err) - return storage.MeltQuote{}, cashu.BuildCashuError(errmsg, cashu.DBErrCode) - } - err = m.db.RemovePendingProofs(Ys) - if err != nil { - errmsg := fmt.Sprintf("error removing proofs from pending: %v", err) - return storage.MeltQuote{}, cashu.BuildCashuError(errmsg, cashu.DBErrCode) - } - return meltQuote, nil - } - - // if SendPayment failed for something other than payment error - // do not return yet, an extra check will be done + // if SendPayment failed do not return yet, an extra check will be done sendPaymentResponse.PaymentStatus = lightning.Failed m.logDebugf("SendPayment failed with error: %v. Will do extra check", err) } @@ -823,14 +801,36 @@ func (m *Mint) MeltTokens(ctx context.Context, method, quoteId string, proofs ca // if got failed from SendPayment // do additional check by calling to get outgoing payment status paymentStatus, err := m.lightningClient.OutgoingPaymentStatus(ctx, meltQuote.PaymentHash) - if paymentStatus.PaymentStatus == lightning.Pending { + if status.Code(err) == codes.NotFound { + m.logInfof("no outgoing payment found with hash: %v. Removing pending proofs and marking quote '%v' as unpaid", + meltQuote.PaymentHash, meltQuote.Id) + + meltQuote.State = nut05.Unpaid + err = m.db.UpdateMeltQuote(meltQuote.Id, "", meltQuote.State) + if err != nil { + errmsg := fmt.Sprintf("error updating melt quote state: %v", err) + return storage.MeltQuote{}, cashu.BuildCashuError(errmsg, cashu.DBErrCode) + } + err = m.db.RemovePendingProofs(Ys) + if err != nil { + errmsg := fmt.Sprintf("error removing proofs from pending: %v", err) + return storage.MeltQuote{}, cashu.BuildCashuError(errmsg, cashu.DBErrCode) + } return meltQuote, nil } if err != nil { - m.logInfof("payment failed with error: %v. Removing pending proofs and marking quote '%v' as '%v'", - err, meltQuote.Id, nut05.Unpaid) - // if it gets to here, most likely the payment failed - // so mark quote as unpaid and remove proofs from pending + m.logErrorf(`error checking outgoing payment status: %v. Leaving proofs for quote '%v' as pending`, err, meltQuote.Id) + return meltQuote, nil + } + + switch paymentStatus.PaymentStatus { + // only set quote to unpaid and remove pending proofs if OutgoingPaymentStatus + // returned a nil err (meaning it was actually able to check the status) + // and payment status was failed + case lightning.Failed: + m.logInfof("payment failed with error: %v. Removing pending proofs and marking quote '%v' as unpaid", + paymentStatus.PaymentFailureReason, meltQuote.Id) + meltQuote.State = nut05.Unpaid err = m.db.UpdateMeltQuote(meltQuote.Id, "", meltQuote.State) if err != nil { @@ -842,9 +842,8 @@ func (m *Mint) MeltTokens(ctx context.Context, method, quoteId string, proofs ca errmsg := fmt.Sprintf("error removing proofs from pending: %v", err) return storage.MeltQuote{}, cashu.BuildCashuError(errmsg, cashu.DBErrCode) } - } - - if paymentStatus.PaymentStatus == lightning.Succeeded { + return meltQuote, nil + case lightning.Succeeded: m.logInfof("succesfully paid invoice with hash '%v' for melt quote '%v'", meltQuote.PaymentHash, meltQuote.Id) err = m.settleProofs(Ys, proofs) if err != nil { @@ -1049,10 +1048,10 @@ func (m *Mint) verifyProofs(proofs cashu.Proofs, Ys []string) error { // if P2PK locked proof, verify valid witness if nut11.IsSecretP2PK(proof) { - m.logDebugf("verifying P2PK locked proof") if err := verifyP2PKLockedProof(proof); err != nil { return err } + m.logDebugf("verified P2PK locked proof") } Cbytes, err := hex.DecodeString(proof.C) diff --git a/mint/server.go b/mint/server.go index 7bb00f0..863076d 100644 --- a/mint/server.go +++ b/mint/server.go @@ -399,7 +399,7 @@ func (ms *MintServer) meltQuoteState(rw http.ResponseWriter, req *http.Request) method := vars["method"] quoteId := vars["quote_id"] - ctx, cancel := context.WithTimeout(context.Background(), time.Second*20) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() meltQuote, err := ms.mint.GetMeltQuoteState(ctx, method, quoteId)