diff --git a/channeldb/mp_payment.go b/channeldb/mp_payment.go new file mode 100644 index 00000000..9a26cdbd --- /dev/null +++ b/channeldb/mp_payment.go @@ -0,0 +1,105 @@ +package channeldb + +import ( + "time" + + "github.com/btcsuite/btcd/btcec" + "github.com/lightningnetwork/lnd/lntypes" + "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/routing/route" +) + +// MPPaymentCreationInfo is the information necessary to have ready when +// initiating a payment, moving it into state InFlight. +type MPPaymentCreationInfo struct { + // PaymentHash is the hash this payment is paying to. + PaymentHash lntypes.Hash + + // Value is the amount we are paying. + Value lnwire.MilliSatoshi + + // CreatingTime is the time at which this payment was started. + CreationTime time.Time + + // PaymentRequest is the full payment request, if any. + PaymentRequest []byte +} + +// HTLCAttempt contains information about a specific HTLC attempt for a given +// payment. This information is used by the router to handle any errors coming +// back after an attempt is made, and to query the switch about the status of a +// payment. For settled payment this will be the information for the succeeding +// payment attempt. +type HTLCAttempt struct { + // PaymentID is the unique ID used for this attempt. + PaymentID uint64 + + // SessionKey is the ephemeral key used for this payment attempt. + SessionKey *btcec.PrivateKey + + // Route is the route attempted to send the HTLC. + Route route.Route + + // AttemptTime is the time at which this HTLC was attempted. + AttemptTime time.Time + + // Settle is the preimage of a successful payment. This serves as a + // proof of payment. It will only be non-nil for settled payments. + // + // NOTE: Can be nil if payment is not settled. + Settle *HTLCSettleInfo + + // Fail is a failure reason code indicating the reason the payment + // failed. It is only non-nil for failed payments. + // + // NOTE: Can be nil if payment is not failed. + Failure *HTLCFailInfo +} + +// HTLCSettleInfo encapsulates the information that augments an HTLCAttempt in +// the event that the HTLC is successful. +type HTLCSettleInfo struct { + // Preimage is the preimage of a successful HTLC. This serves as a proof + // of payment. + Preimage lntypes.Preimage + + // SettleTime is the time at which this HTLC was settled. + SettleTime time.Time +} + +// HTLCFailInfo encapsulates the information that augments an HTLCAttempt in the +// event that the HTLC fails. +type HTLCFailInfo struct { + // FailTime is the time at which this HTLC was failed. + FailTime time.Time +} + +// MPPayment is a wrapper around a payment's MPPaymentCreationInfo and +// HTLCAttempts. All payments will have the MPPPaymentCreationInfo set, any +// HTLCs made in attempts to be completed will populated in the HTLCs slice. +// Each populated HTLCAttempt represents an attempted HTLC, each of which may +// have the associated Settle or Fail struct populated if the HTLC is no longer +// in-flight. +type MPPayment struct { + // sequenceNum is a unique identifier used to sort the payments in + // order of creation. + sequenceNum uint64 + + // Info holds all static information about this payment, and is + // populated when the payment is initiated. + Info *MPPaymentCreationInfo + + // HTLCs holds the information about individual HTLCs that we send in + // order to make the payment. + HTLCs []HTLCAttempt + + // FailureReason is the failure reason code indicating the reason the + // payment failed. + // + // NOTE: Will only be set once the daemon has given up on the payment + // altogether. + FailureReason *FailureReason + + // Status is the current PaymentStatus of this payment. + Status PaymentStatus +} diff --git a/channeldb/payments.go b/channeldb/payments.go index ddf80ed0..afe5e1dc 100644 --- a/channeldb/payments.go +++ b/channeldb/payments.go @@ -252,9 +252,70 @@ type Payment struct { Failure *FailureReason } +// ToMPPayment converts a legacy payment into an MPPayment. +func (p *Payment) ToMPPayment() *MPPayment { + var ( + htlcs []HTLCAttempt + reason *FailureReason + settle *HTLCSettleInfo + failure *HTLCFailInfo + ) + + // Promote the payment failure to a proper fail struct, if it exists. + if p.Failure != nil { + // NOTE: FailTime is not set for legacy payments. + failure = &HTLCFailInfo{} + reason = p.Failure + } + + // Promote the payment preimage to proper settle struct, if it exists. + if p.Preimage != nil { + // NOTE: SettleTime is not set for legacy payments. + settle = &HTLCSettleInfo{ + Preimage: *p.Preimage, + } + } + + // Either a settle or a failure may be set, but not both. + if settle != nil && failure != nil { + panic("htlc attempt has both settle and failure info") + } + + // Populate a single HTLC on the MPPayment if an attempt exists on the + // legacy payment. If none exists we will leave the attempt info blank + // since we cannot recover it. + if p.Attempt != nil { + // NOTE: AttemptTime is not set for legacy payments. + htlcs = []HTLCAttempt{ + { + PaymentID: p.Attempt.PaymentID, + SessionKey: p.Attempt.SessionKey, + Route: p.Attempt.Route, + Settle: settle, + Failure: failure, + }, + } + } + + return &MPPayment{ + sequenceNum: p.sequenceNum, + Info: &MPPaymentCreationInfo{ + PaymentHash: p.Info.PaymentHash, + Value: p.Info.Value, + CreationTime: p.Info.CreationDate, + PaymentRequest: p.Info.PaymentRequest, + }, + HTLCs: htlcs, + FailureReason: reason, + Status: p.Status, + } +} + // FetchPayments returns all sent payments found in the DB. -func (db *DB) FetchPayments() ([]*Payment, error) { - var payments []*Payment +// +// nolint: dupl +func (db *DB) FetchPayments() ([]*MPPayment, error) { + var payments []*MPPayment err := db.View(func(tx *bbolt.Tx) error { paymentsBucket := tx.Bucket(paymentsRootBucket) @@ -276,7 +337,7 @@ func (db *DB) FetchPayments() ([]*Payment, error) { return err } - payments = append(payments, p) + payments = append(payments, p.ToMPPayment()) // For older versions of lnd, duplicate payments to a // payment has was possible. These will be found in a @@ -301,7 +362,7 @@ func (db *DB) FetchPayments() ([]*Payment, error) { return err } - payments = append(payments, p) + payments = append(payments, p.ToMPPayment()) return nil }) }) @@ -473,7 +534,7 @@ func deserializePaymentCreationInfo(r io.Reader) (*PaymentCreationInfo, error) { reqLen := uint32(byteOrder.Uint32(scratch[:4])) payReq := make([]byte, reqLen) if reqLen > 0 { - if _, err := io.ReadFull(r, payReq[:]); err != nil { + if _, err := io.ReadFull(r, payReq); err != nil { return nil, err } } diff --git a/rpcserver.go b/rpcserver.go index 157a714a..6c770b78 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -4348,24 +4348,29 @@ func (r *rpcServer) ListPayments(ctx context.Context, continue } - // Fetch the payment's route, which will be empty if an attempt - // has not been made. - var route route.Route - if payment.Attempt != nil { - route = payment.Attempt.Route + // Fetch the payment's route and preimage. If no HTLC was + // successful, an empty route and preimage will be used. + var ( + route route.Route + preimage lntypes.Preimage + ) + for _, htlc := range payment.HTLCs { + // Display the last route attempted. + route = htlc.Route + + // If any of the htlcs have settled, extract a valid + // preimage. + if htlc.Settle != nil { + preimage = htlc.Settle.Preimage + } } + + // Encode the hops from the successful route, if any. path := make([]string, len(route.Hops)) for i, hop := range route.Hops { path[i] = hex.EncodeToString(hop.PubKeyBytes[:]) } - // Fetch the preimage if the payment was successful, otherwise a - // zero-value preimage will be used. - var preimage lntypes.Preimage - if payment.Preimage != nil { - preimage = *payment.Preimage - } - msatValue := int64(payment.Info.Value) satValue := int64(payment.Info.Value.ToSatoshis()) @@ -4380,7 +4385,7 @@ func (r *rpcServer) ListPayments(ctx context.Context, Value: satValue, ValueMsat: msatValue, ValueSat: satValue, - CreationDate: payment.Info.CreationDate.Unix(), + CreationDate: payment.Info.CreationTime.Unix(), Path: path, Fee: int64(route.TotalFees().ToSatoshis()), FeeSat: int64(route.TotalFees().ToSatoshis()),