2019-05-23 21:05:30 +03:00
|
|
|
package channeldb
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"encoding/binary"
|
2019-09-09 13:57:46 +03:00
|
|
|
"fmt"
|
2019-05-23 21:05:30 +03:00
|
|
|
"io"
|
2019-09-09 13:57:46 +03:00
|
|
|
"sort"
|
2019-05-23 21:05:30 +03:00
|
|
|
|
|
|
|
"github.com/coreos/bbolt"
|
2019-09-09 13:57:46 +03:00
|
|
|
"github.com/lightningnetwork/lnd/lntypes"
|
2019-05-23 21:05:30 +03:00
|
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
channeldb: fix migration bug due to interplay between migration #9 and #10
In this commit, we fix an issue that was recently introduced as a result
of migration #10. The new TLV format ended up modifying the
serialization functions called in `serializePaymentAttemptInfo`.
Migration #9, also used this `serializePaymentAttemptInfo` method to
serialize the _new_ (pre TLV, but new payment attempt structure) routes
into the database during its migration. However, migration #10 failed to
copy over the existing unmodified `serializePaymentAttemptInfo` method
into the legacy serialization for migration #9. As a result, once
migration #9 was run, the routes/payments were serialized using the
_new_ format, rather than the format used for v0.7.1. This then lead to
de-serialization either failing, or causing partial payment corruption
as migration #10 was expecting the "legacy" format (no TLV info).
We fix this issue by adding a new fully enclosed
`serializePaymentAttemptInfoMigration9`method that will be used for
migration #9. Note that our tests didn't catch this, as they test the
migration in isolation, rather than in series which is how users will
encounter the migrations.
Fixes #3463.
2019-09-07 03:21:54 +03:00
|
|
|
"github.com/lightningnetwork/lnd/routing/route"
|
2019-05-23 21:05:30 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
// paymentBucket is the name of the bucket within the database that
|
|
|
|
// stores all data related to payments.
|
|
|
|
//
|
|
|
|
// Within the payments bucket, each invoice is keyed by its invoice ID
|
|
|
|
// which is a monotonically increasing uint64. BoltDB's sequence
|
|
|
|
// feature is used for generating monotonically increasing id.
|
|
|
|
//
|
|
|
|
// NOTE: Deprecated. Kept around for migration purposes.
|
|
|
|
paymentBucket = []byte("payments")
|
|
|
|
|
|
|
|
// paymentStatusBucket is the name of the bucket within the database
|
|
|
|
// that stores the status of a payment indexed by the payment's
|
|
|
|
// preimage.
|
|
|
|
//
|
|
|
|
// NOTE: Deprecated. Kept around for migration purposes.
|
|
|
|
paymentStatusBucket = []byte("payment-status")
|
|
|
|
)
|
|
|
|
|
|
|
|
// outgoingPayment represents a successful payment between the daemon and a
|
|
|
|
// remote node. Details such as the total fee paid, and the time of the payment
|
|
|
|
// are stored.
|
|
|
|
//
|
|
|
|
// NOTE: Deprecated. Kept around for migration purposes.
|
|
|
|
type outgoingPayment struct {
|
|
|
|
Invoice
|
|
|
|
|
|
|
|
// Fee is the total fee paid for the payment in milli-satoshis.
|
|
|
|
Fee lnwire.MilliSatoshi
|
|
|
|
|
|
|
|
// TotalTimeLock is the total cumulative time-lock in the HTLC extended
|
|
|
|
// from the second-to-last hop to the destination.
|
|
|
|
TimeLockLength uint32
|
|
|
|
|
|
|
|
// Path encodes the path the payment took through the network. The path
|
|
|
|
// excludes the outgoing node and consists of the hex-encoded
|
|
|
|
// compressed public key of each of the nodes involved in the payment.
|
|
|
|
Path [][33]byte
|
|
|
|
|
|
|
|
// PaymentPreimage is the preImage of a successful payment. This is used
|
|
|
|
// to calculate the PaymentHash as well as serve as a proof of payment.
|
|
|
|
PaymentPreimage [32]byte
|
|
|
|
}
|
|
|
|
|
|
|
|
// addPayment saves a successful payment to the database. It is assumed that
|
|
|
|
// all payment are sent using unique payment hashes.
|
|
|
|
//
|
|
|
|
// NOTE: Deprecated. Kept around for migration purposes.
|
|
|
|
func (db *DB) addPayment(payment *outgoingPayment) error {
|
|
|
|
// Validate the field of the inner voice within the outgoing payment,
|
|
|
|
// these must also adhere to the same constraints as regular invoices.
|
|
|
|
if err := validateInvoice(&payment.Invoice); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// We first serialize the payment before starting the database
|
|
|
|
// transaction so we can avoid creating a DB payment in the case of a
|
|
|
|
// serialization error.
|
|
|
|
var b bytes.Buffer
|
|
|
|
if err := serializeOutgoingPayment(&b, payment); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
paymentBytes := b.Bytes()
|
|
|
|
|
|
|
|
return db.Batch(func(tx *bbolt.Tx) error {
|
|
|
|
payments, err := tx.CreateBucketIfNotExists(paymentBucket)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Obtain the new unique sequence number for this payment.
|
|
|
|
paymentID, err := payments.NextSequence()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// We use BigEndian for keys as it orders keys in
|
|
|
|
// ascending order. This allows bucket scans to order payments
|
|
|
|
// in the order in which they were created.
|
|
|
|
paymentIDBytes := make([]byte, 8)
|
|
|
|
binary.BigEndian.PutUint64(paymentIDBytes, paymentID)
|
|
|
|
|
|
|
|
return payments.Put(paymentIDBytes, paymentBytes)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// fetchAllPayments returns all outgoing payments in DB.
|
|
|
|
//
|
|
|
|
// NOTE: Deprecated. Kept around for migration purposes.
|
|
|
|
func (db *DB) fetchAllPayments() ([]*outgoingPayment, error) {
|
|
|
|
var payments []*outgoingPayment
|
|
|
|
|
|
|
|
err := db.View(func(tx *bbolt.Tx) error {
|
|
|
|
bucket := tx.Bucket(paymentBucket)
|
|
|
|
if bucket == nil {
|
|
|
|
return ErrNoPaymentsCreated
|
|
|
|
}
|
|
|
|
|
|
|
|
return bucket.ForEach(func(k, v []byte) error {
|
|
|
|
// If the value is nil, then we ignore it as it may be
|
|
|
|
// a sub-bucket.
|
|
|
|
if v == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
r := bytes.NewReader(v)
|
|
|
|
payment, err := deserializeOutgoingPayment(r)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
payments = append(payments, payment)
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return payments, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// fetchPaymentStatus returns the payment status for outgoing payment.
|
2019-05-23 21:05:31 +03:00
|
|
|
// If status of the payment isn't found, it will default to "StatusUnknown".
|
2019-05-23 21:05:30 +03:00
|
|
|
//
|
|
|
|
// NOTE: Deprecated. Kept around for migration purposes.
|
|
|
|
func (db *DB) fetchPaymentStatus(paymentHash [32]byte) (PaymentStatus, error) {
|
2019-05-23 21:05:31 +03:00
|
|
|
var paymentStatus = StatusUnknown
|
2019-05-23 21:05:30 +03:00
|
|
|
err := db.View(func(tx *bbolt.Tx) error {
|
|
|
|
var err error
|
|
|
|
paymentStatus, err = fetchPaymentStatusTx(tx, paymentHash)
|
|
|
|
return err
|
|
|
|
})
|
|
|
|
if err != nil {
|
2019-05-23 21:05:31 +03:00
|
|
|
return StatusUnknown, err
|
2019-05-23 21:05:30 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return paymentStatus, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// fetchPaymentStatusTx is a helper method that returns the payment status for
|
|
|
|
// outgoing payment. If status of the payment isn't found, it will default to
|
2019-05-23 21:05:31 +03:00
|
|
|
// "StatusUnknown". It accepts the boltdb transactions such that this method
|
2019-05-23 21:05:30 +03:00
|
|
|
// can be composed into other atomic operations.
|
|
|
|
//
|
|
|
|
// NOTE: Deprecated. Kept around for migration purposes.
|
|
|
|
func fetchPaymentStatusTx(tx *bbolt.Tx, paymentHash [32]byte) (PaymentStatus, error) {
|
|
|
|
// The default status for all payments that aren't recorded in database.
|
2019-05-23 21:05:31 +03:00
|
|
|
var paymentStatus = StatusUnknown
|
2019-05-23 21:05:30 +03:00
|
|
|
|
|
|
|
bucket := tx.Bucket(paymentStatusBucket)
|
|
|
|
if bucket == nil {
|
|
|
|
return paymentStatus, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
paymentStatusBytes := bucket.Get(paymentHash[:])
|
|
|
|
if paymentStatusBytes == nil {
|
|
|
|
return paymentStatus, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
paymentStatus.FromBytes(paymentStatusBytes)
|
|
|
|
|
|
|
|
return paymentStatus, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func serializeOutgoingPayment(w io.Writer, p *outgoingPayment) error {
|
|
|
|
var scratch [8]byte
|
|
|
|
|
2019-08-08 17:58:28 +03:00
|
|
|
if err := serializeInvoiceLegacy(w, &p.Invoice); err != nil {
|
2019-05-23 21:05:30 +03:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
byteOrder.PutUint64(scratch[:], uint64(p.Fee))
|
|
|
|
if _, err := w.Write(scratch[:]); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// First write out the length of the bytes to prefix the value.
|
|
|
|
pathLen := uint32(len(p.Path))
|
|
|
|
byteOrder.PutUint32(scratch[:4], pathLen)
|
|
|
|
if _, err := w.Write(scratch[:4]); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Then with the path written, we write out the series of public keys
|
|
|
|
// involved in the path.
|
|
|
|
for _, hop := range p.Path {
|
|
|
|
if _, err := w.Write(hop[:]); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
byteOrder.PutUint32(scratch[:4], p.TimeLockLength)
|
|
|
|
if _, err := w.Write(scratch[:4]); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err := w.Write(p.PaymentPreimage[:]); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func deserializeOutgoingPayment(r io.Reader) (*outgoingPayment, error) {
|
|
|
|
var scratch [8]byte
|
|
|
|
|
|
|
|
p := &outgoingPayment{}
|
|
|
|
|
2019-08-08 17:58:28 +03:00
|
|
|
inv, err := deserializeInvoiceLegacy(r)
|
2019-05-23 21:05:30 +03:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
p.Invoice = inv
|
|
|
|
|
|
|
|
if _, err := r.Read(scratch[:]); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
p.Fee = lnwire.MilliSatoshi(byteOrder.Uint64(scratch[:]))
|
|
|
|
|
|
|
|
if _, err = r.Read(scratch[:4]); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
pathLen := byteOrder.Uint32(scratch[:4])
|
|
|
|
|
|
|
|
path := make([][33]byte, pathLen)
|
|
|
|
for i := uint32(0); i < pathLen; i++ {
|
|
|
|
if _, err := r.Read(path[i][:]); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
p.Path = path
|
|
|
|
|
|
|
|
if _, err = r.Read(scratch[:4]); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
p.TimeLockLength = byteOrder.Uint32(scratch[:4])
|
|
|
|
|
|
|
|
if _, err := r.Read(p.PaymentPreimage[:]); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return p, nil
|
|
|
|
}
|
channeldb: fix migration bug due to interplay between migration #9 and #10
In this commit, we fix an issue that was recently introduced as a result
of migration #10. The new TLV format ended up modifying the
serialization functions called in `serializePaymentAttemptInfo`.
Migration #9, also used this `serializePaymentAttemptInfo` method to
serialize the _new_ (pre TLV, but new payment attempt structure) routes
into the database during its migration. However, migration #10 failed to
copy over the existing unmodified `serializePaymentAttemptInfo` method
into the legacy serialization for migration #9. As a result, once
migration #9 was run, the routes/payments were serialized using the
_new_ format, rather than the format used for v0.7.1. This then lead to
de-serialization either failing, or causing partial payment corruption
as migration #10 was expecting the "legacy" format (no TLV info).
We fix this issue by adding a new fully enclosed
`serializePaymentAttemptInfoMigration9`method that will be used for
migration #9. Note that our tests didn't catch this, as they test the
migration in isolation, rather than in series which is how users will
encounter the migrations.
Fixes #3463.
2019-09-07 03:21:54 +03:00
|
|
|
|
|
|
|
// serializePaymentAttemptInfoMigration9 is the serializePaymentAttemptInfo
|
|
|
|
// version as existed when migration #9 was created. We keep this around, along
|
|
|
|
// with the methods below to ensure that clients that upgrade will use the
|
|
|
|
// correct version of this method.
|
|
|
|
func serializePaymentAttemptInfoMigration9(w io.Writer, a *PaymentAttemptInfo) error {
|
|
|
|
if err := WriteElements(w, a.PaymentID, a.SessionKey); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := serializeRouteMigration9(w, a.Route); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func serializeHopMigration9(w io.Writer, h *route.Hop) error {
|
|
|
|
if err := WriteElements(w,
|
|
|
|
h.PubKeyBytes[:], h.ChannelID, h.OutgoingTimeLock,
|
|
|
|
h.AmtToForward,
|
|
|
|
); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func serializeRouteMigration9(w io.Writer, r route.Route) error {
|
|
|
|
if err := WriteElements(w,
|
|
|
|
r.TotalTimeLock, r.TotalAmount, r.SourcePubKey[:],
|
|
|
|
); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := WriteElements(w, uint32(len(r.Hops))); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, h := range r.Hops {
|
|
|
|
if err := serializeHopMigration9(w, h); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2019-09-09 13:57:46 +03:00
|
|
|
|
|
|
|
func deserializePaymentAttemptInfoMigration9(r io.Reader) (*PaymentAttemptInfo, error) {
|
|
|
|
a := &PaymentAttemptInfo{}
|
|
|
|
err := ReadElements(r, &a.PaymentID, &a.SessionKey)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
a.Route, err = deserializeRouteMigration9(r)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return a, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func deserializeRouteMigration9(r io.Reader) (route.Route, error) {
|
|
|
|
rt := route.Route{}
|
|
|
|
if err := ReadElements(r,
|
|
|
|
&rt.TotalTimeLock, &rt.TotalAmount,
|
|
|
|
); err != nil {
|
|
|
|
return rt, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var pub []byte
|
|
|
|
if err := ReadElements(r, &pub); err != nil {
|
|
|
|
return rt, err
|
|
|
|
}
|
|
|
|
copy(rt.SourcePubKey[:], pub)
|
|
|
|
|
|
|
|
var numHops uint32
|
|
|
|
if err := ReadElements(r, &numHops); err != nil {
|
|
|
|
return rt, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var hops []*route.Hop
|
|
|
|
for i := uint32(0); i < numHops; i++ {
|
|
|
|
hop, err := deserializeHopMigration9(r)
|
|
|
|
if err != nil {
|
|
|
|
return rt, err
|
|
|
|
}
|
|
|
|
hops = append(hops, hop)
|
|
|
|
}
|
|
|
|
rt.Hops = hops
|
|
|
|
|
|
|
|
return rt, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func deserializeHopMigration9(r io.Reader) (*route.Hop, error) {
|
|
|
|
h := &route.Hop{}
|
|
|
|
|
|
|
|
var pub []byte
|
|
|
|
if err := ReadElements(r, &pub); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
copy(h.PubKeyBytes[:], pub)
|
|
|
|
|
|
|
|
if err := ReadElements(r,
|
|
|
|
&h.ChannelID, &h.OutgoingTimeLock, &h.AmtToForward,
|
|
|
|
); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return h, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// fetchPaymentsMigration9 returns all sent payments found in the DB using the
|
|
|
|
// payment attempt info format that was present as of migration #9. We need
|
|
|
|
// this as otherwise, the current FetchPayments version will use the latest
|
|
|
|
// decoding format. Note that we only need this for the
|
|
|
|
// TestOutgoingPaymentsMigration migration test case.
|
|
|
|
func (db *DB) fetchPaymentsMigration9() ([]*Payment, error) {
|
|
|
|
var payments []*Payment
|
|
|
|
|
|
|
|
err := db.View(func(tx *bbolt.Tx) error {
|
|
|
|
paymentsBucket := tx.Bucket(paymentsRootBucket)
|
|
|
|
if paymentsBucket == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return paymentsBucket.ForEach(func(k, v []byte) error {
|
|
|
|
bucket := paymentsBucket.Bucket(k)
|
|
|
|
if bucket == nil {
|
|
|
|
// We only expect sub-buckets to be found in
|
|
|
|
// this top-level bucket.
|
|
|
|
return fmt.Errorf("non bucket element in " +
|
|
|
|
"payments bucket")
|
|
|
|
}
|
|
|
|
|
|
|
|
p, err := fetchPaymentMigration9(bucket)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
payments = append(payments, p)
|
|
|
|
|
|
|
|
// For older versions of lnd, duplicate payments to a
|
|
|
|
// payment has was possible. These will be found in a
|
|
|
|
// sub-bucket indexed by their sequence number if
|
|
|
|
// available.
|
|
|
|
dup := bucket.Bucket(paymentDuplicateBucket)
|
|
|
|
if dup == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return dup.ForEach(func(k, v []byte) error {
|
|
|
|
subBucket := dup.Bucket(k)
|
|
|
|
if subBucket == nil {
|
|
|
|
// We one bucket for each duplicate to
|
|
|
|
// be found.
|
|
|
|
return fmt.Errorf("non bucket element" +
|
|
|
|
"in duplicate bucket")
|
|
|
|
}
|
|
|
|
|
|
|
|
p, err := fetchPaymentMigration9(subBucket)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
payments = append(payments, p)
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Before returning, sort the payments by their sequence number.
|
|
|
|
sort.Slice(payments, func(i, j int) bool {
|
|
|
|
return payments[i].sequenceNum < payments[j].sequenceNum
|
|
|
|
})
|
|
|
|
|
|
|
|
return payments, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func fetchPaymentMigration9(bucket *bbolt.Bucket) (*Payment, error) {
|
|
|
|
var (
|
|
|
|
err error
|
|
|
|
p = &Payment{}
|
|
|
|
)
|
|
|
|
|
|
|
|
seqBytes := bucket.Get(paymentSequenceKey)
|
|
|
|
if seqBytes == nil {
|
|
|
|
return nil, fmt.Errorf("sequence number not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
p.sequenceNum = binary.BigEndian.Uint64(seqBytes)
|
|
|
|
|
|
|
|
// Get the payment status.
|
|
|
|
p.Status = fetchPaymentStatus(bucket)
|
|
|
|
|
|
|
|
// Get the PaymentCreationInfo.
|
|
|
|
b := bucket.Get(paymentCreationInfoKey)
|
|
|
|
if b == nil {
|
|
|
|
return nil, fmt.Errorf("creation info not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
r := bytes.NewReader(b)
|
|
|
|
p.Info, err = deserializePaymentCreationInfo(r)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the PaymentAttemptInfo. This can be unset.
|
|
|
|
b = bucket.Get(paymentAttemptInfoKey)
|
|
|
|
if b != nil {
|
|
|
|
r = bytes.NewReader(b)
|
|
|
|
p.Attempt, err = deserializePaymentAttemptInfoMigration9(r)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the payment preimage. This is only found for
|
|
|
|
// completed payments.
|
|
|
|
b = bucket.Get(paymentSettleInfoKey)
|
|
|
|
if b != nil {
|
|
|
|
var preimg lntypes.Preimage
|
|
|
|
copy(preimg[:], b[:])
|
|
|
|
p.PaymentPreimage = &preimg
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get failure reason if available.
|
|
|
|
b = bucket.Get(paymentFailInfoKey)
|
|
|
|
if b != nil {
|
|
|
|
reason := FailureReason(b[0])
|
|
|
|
p.Failure = &reason
|
|
|
|
}
|
|
|
|
|
|
|
|
return p, nil
|
|
|
|
}
|