d89f51d1d0
Similarly as with kvdb.View this commits adds a reset closure to the kvdb.Update call in order to be able to reset external state if the underlying db backend needs to retry the transaction.
503 lines
12 KiB
Go
503 lines
12 KiB
Go
package migration_01_to_11
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
"sort"
|
|
|
|
"github.com/lightningnetwork/lnd/channeldb/kvdb"
|
|
"github.com/lightningnetwork/lnd/lntypes"
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
)
|
|
|
|
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 kvdb.Update(db, func(tx kvdb.RwTx) error {
|
|
payments, err := tx.CreateTopLevelBucket(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)
|
|
}, func() {})
|
|
}
|
|
|
|
// fetchAllPayments returns all outgoing payments in DB.
|
|
//
|
|
// NOTE: Deprecated. Kept around for migration purposes.
|
|
func (db *DB) fetchAllPayments() ([]*outgoingPayment, error) {
|
|
var payments []*outgoingPayment
|
|
|
|
err := kvdb.View(db, func(tx kvdb.RTx) error {
|
|
bucket := tx.ReadBucket(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
|
|
})
|
|
}, func() {
|
|
payments = nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return payments, nil
|
|
}
|
|
|
|
// fetchPaymentStatus returns the payment status for outgoing payment.
|
|
// If status of the payment isn't found, it will default to "StatusUnknown".
|
|
//
|
|
// NOTE: Deprecated. Kept around for migration purposes.
|
|
func (db *DB) fetchPaymentStatus(paymentHash [32]byte) (PaymentStatus, error) {
|
|
var paymentStatus = StatusUnknown
|
|
err := kvdb.View(db, func(tx kvdb.RTx) error {
|
|
var err error
|
|
paymentStatus, err = fetchPaymentStatusTx(tx, paymentHash)
|
|
return err
|
|
}, func() {
|
|
paymentStatus = StatusUnknown
|
|
})
|
|
if err != nil {
|
|
return StatusUnknown, err
|
|
}
|
|
|
|
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
|
|
// "StatusUnknown". It accepts the boltdb transactions such that this method
|
|
// can be composed into other atomic operations.
|
|
//
|
|
// NOTE: Deprecated. Kept around for migration purposes.
|
|
func fetchPaymentStatusTx(tx kvdb.RTx, paymentHash [32]byte) (PaymentStatus, error) {
|
|
// The default status for all payments that aren't recorded in database.
|
|
var paymentStatus = StatusUnknown
|
|
|
|
bucket := tx.ReadBucket(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
|
|
|
|
if err := serializeInvoiceLegacy(w, &p.Invoice); err != nil {
|
|
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{}
|
|
|
|
inv, err := deserializeInvoiceLegacy(r)
|
|
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
|
|
}
|
|
|
|
// 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 *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) 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
|
|
}
|
|
|
|
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, error) {
|
|
rt := 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 []*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) (*Hop, error) {
|
|
h := &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 := kvdb.View(db, func(tx kvdb.RTx) error {
|
|
paymentsBucket := tx.ReadBucket(paymentsRootBucket)
|
|
if paymentsBucket == nil {
|
|
return nil
|
|
}
|
|
|
|
return paymentsBucket.ForEach(func(k, v []byte) error {
|
|
bucket := paymentsBucket.NestedReadBucket(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.NestedReadBucket(paymentDuplicateBucket)
|
|
if dup == nil {
|
|
return nil
|
|
}
|
|
|
|
return dup.ForEach(func(k, v []byte) error {
|
|
subBucket := dup.NestedReadBucket(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
|
|
})
|
|
})
|
|
}, func() {
|
|
payments = 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 kvdb.RBucket) (*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
|
|
}
|