mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-09-27 08:57:26 +02:00
Merge pull request #3499 from cfromknecht/mpp-payments-rpc
channeldb+rpcserver: expose legacy payments as multi-path payments
This commit is contained in:
105
channeldb/mp_payment.go
Normal file
105
channeldb/mp_payment.go
Normal file
@@ -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
|
||||
}
|
@@ -8,7 +8,6 @@ import (
|
||||
|
||||
"github.com/coreos/bbolt"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/routing/route"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -192,16 +191,17 @@ func (p *PaymentControl) RegisterAttempt(paymentHash lntypes.Hash,
|
||||
// duplicate payments to the same payment hash. The provided preimage is
|
||||
// atomically saved to the DB for record keeping.
|
||||
func (p *PaymentControl) Success(paymentHash lntypes.Hash,
|
||||
preimage lntypes.Preimage) (*route.Route, error) {
|
||||
preimage lntypes.Preimage) (*MPPayment, error) {
|
||||
|
||||
var (
|
||||
updateErr error
|
||||
route *route.Route
|
||||
payment *MPPayment
|
||||
)
|
||||
err := p.db.Batch(func(tx *bbolt.Tx) error {
|
||||
// Reset the update error, to avoid carrying over an error
|
||||
// from a previous execution of the batched db transaction.
|
||||
updateErr = nil
|
||||
payment = nil
|
||||
|
||||
bucket, err := fetchPaymentBucket(tx, paymentHash)
|
||||
if err == ErrPaymentNotInitiated {
|
||||
@@ -225,20 +225,14 @@ func (p *PaymentControl) Success(paymentHash lntypes.Hash,
|
||||
}
|
||||
|
||||
// Retrieve attempt info for the notification.
|
||||
attempt, err := fetchPaymentAttempt(bucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
route = &attempt.Route
|
||||
|
||||
return nil
|
||||
payment, err = fetchPayment(bucket)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return route, updateErr
|
||||
return payment, updateErr
|
||||
}
|
||||
|
||||
// Fail transitions a payment into the Failed state, and records the reason the
|
||||
@@ -246,16 +240,17 @@ func (p *PaymentControl) Success(paymentHash lntypes.Hash,
|
||||
// its next call for this payment hash, allowing the switch to make a
|
||||
// subsequent payment.
|
||||
func (p *PaymentControl) Fail(paymentHash lntypes.Hash,
|
||||
reason FailureReason) (*route.Route, error) {
|
||||
reason FailureReason) (*MPPayment, error) {
|
||||
|
||||
var (
|
||||
updateErr error
|
||||
route *route.Route
|
||||
payment *MPPayment
|
||||
)
|
||||
err := p.db.Batch(func(tx *bbolt.Tx) error {
|
||||
// Reset the update error, to avoid carrying over an error
|
||||
// from a previous execution of the batched db transaction.
|
||||
updateErr = nil
|
||||
payment = nil
|
||||
|
||||
bucket, err := fetchPaymentBucket(tx, paymentHash)
|
||||
if err == ErrPaymentNotInitiated {
|
||||
@@ -279,28 +274,21 @@ func (p *PaymentControl) Fail(paymentHash lntypes.Hash,
|
||||
}
|
||||
|
||||
// Retrieve attempt info for the notification, if available.
|
||||
attempt, err := fetchPaymentAttempt(bucket)
|
||||
if err != nil && err != errNoAttemptInfo {
|
||||
return err
|
||||
}
|
||||
if err != errNoAttemptInfo {
|
||||
route = &attempt.Route
|
||||
}
|
||||
|
||||
return nil
|
||||
payment, err = fetchPayment(bucket)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return route, updateErr
|
||||
return payment, updateErr
|
||||
}
|
||||
|
||||
// FetchPayment returns information about a payment from the database.
|
||||
func (p *PaymentControl) FetchPayment(paymentHash lntypes.Hash) (
|
||||
*Payment, error) {
|
||||
*MPPayment, error) {
|
||||
|
||||
var payment *Payment
|
||||
var payment *MPPayment
|
||||
err := p.db.View(func(tx *bbolt.Tx) error {
|
||||
bucket, err := fetchPaymentBucket(tx, paymentHash)
|
||||
if err != nil {
|
||||
|
@@ -14,7 +14,6 @@ import (
|
||||
"github.com/coreos/bbolt"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/routing/route"
|
||||
)
|
||||
|
||||
func initDB() (*DB, error) {
|
||||
@@ -132,16 +131,22 @@ func TestPaymentControlSwitchFail(t *testing.T) {
|
||||
)
|
||||
|
||||
// Verifies that status was changed to StatusSucceeded.
|
||||
var route *route.Route
|
||||
route, err = pControl.Success(info.PaymentHash, preimg)
|
||||
var payment *MPPayment
|
||||
payment, err = pControl.Success(info.PaymentHash, preimg)
|
||||
if err != nil {
|
||||
t.Fatalf("error shouldn't have been received, got: %v", err)
|
||||
}
|
||||
|
||||
err = assertRouteEqual(route, &attempt.Route)
|
||||
if len(payment.HTLCs) != 1 {
|
||||
t.Fatalf("payment should have one htlc, got: %d",
|
||||
len(payment.HTLCs))
|
||||
}
|
||||
|
||||
err = assertRouteEqual(&payment.HTLCs[0].Route, &attempt.Route)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected route returned: %v vs %v: %v",
|
||||
spew.Sdump(attempt.Route), spew.Sdump(*route), err)
|
||||
spew.Sdump(attempt.Route),
|
||||
spew.Sdump(payment.HTLCs[0].Route), err)
|
||||
}
|
||||
|
||||
assertPaymentStatus(t, db, info.PaymentHash, StatusSucceeded)
|
||||
|
@@ -101,9 +101,9 @@ const (
|
||||
// payment.
|
||||
FailureReasonError FailureReason = 2
|
||||
|
||||
// FailureReasonIncorrectPaymentDetails indicates that either the hash
|
||||
// is unknown or the final cltv delta or amount is incorrect.
|
||||
FailureReasonIncorrectPaymentDetails FailureReason = 3
|
||||
// FailureReasonPaymentDetails indicates that either the hash is unknown
|
||||
// or the final cltv delta or amount is incorrect.
|
||||
FailureReasonPaymentDetails FailureReason = 3
|
||||
|
||||
// TODO(halseth): cancel state.
|
||||
|
||||
@@ -120,7 +120,7 @@ func (r FailureReason) String() string {
|
||||
return "no_route"
|
||||
case FailureReasonError:
|
||||
return "error"
|
||||
case FailureReasonIncorrectPaymentDetails:
|
||||
case FailureReasonPaymentDetails:
|
||||
return "incorrect_payment_details"
|
||||
}
|
||||
|
||||
@@ -239,11 +239,11 @@ type Payment struct {
|
||||
// NOTE: Can be nil if no attempt is yet made.
|
||||
Attempt *PaymentAttemptInfo
|
||||
|
||||
// PaymentPreimage is the preimage of a successful payment. This serves
|
||||
// as a proof of payment. It will only be non-nil for settled payments.
|
||||
// Preimage 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.
|
||||
PaymentPreimage *lntypes.Preimage
|
||||
Preimage *lntypes.Preimage
|
||||
|
||||
// Failure is a failure reason code indicating the reason the payment
|
||||
// failed. It is only non-nil for failed payments.
|
||||
@@ -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)
|
||||
@@ -318,7 +379,7 @@ func (db *DB) FetchPayments() ([]*Payment, error) {
|
||||
return payments, nil
|
||||
}
|
||||
|
||||
func fetchPayment(bucket *bbolt.Bucket) (*Payment, error) {
|
||||
func fetchPayment(bucket *bbolt.Bucket) (*MPPayment, error) {
|
||||
var (
|
||||
err error
|
||||
p = &Payment{}
|
||||
@@ -363,7 +424,7 @@ func fetchPayment(bucket *bbolt.Bucket) (*Payment, error) {
|
||||
if b != nil {
|
||||
var preimg lntypes.Preimage
|
||||
copy(preimg[:], b[:])
|
||||
p.PaymentPreimage = &preimg
|
||||
p.Preimage = &preimg
|
||||
}
|
||||
|
||||
// Get failure reason if available.
|
||||
@@ -373,7 +434,7 @@ func fetchPayment(bucket *bbolt.Bucket) (*Payment, error) {
|
||||
p.Failure = &reason
|
||||
}
|
||||
|
||||
return p, nil
|
||||
return p.ToMPPayment(), nil
|
||||
}
|
||||
|
||||
// DeletePayments deletes all completed and failed payments from the DB.
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user