560 lines
14 KiB
Go
560 lines
14 KiB
Go
package channeldb
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"sort"
|
|
"time"
|
|
|
|
"github.com/btcsuite/btcd/btcec"
|
|
"github.com/coreos/bbolt"
|
|
"github.com/lightningnetwork/lnd/lntypes"
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
"github.com/lightningnetwork/lnd/routing/route"
|
|
)
|
|
|
|
var (
|
|
// paymentsRootBucket is the name of the top-level bucket within the
|
|
// database that stores all data related to payments. Within this
|
|
// bucket, each payment hash its own sub-bucket keyed by its payment
|
|
// hash.
|
|
//
|
|
// Bucket hierarchy:
|
|
//
|
|
// root-bucket
|
|
// |
|
|
// |-- <paymenthash>
|
|
// | |--sequence-key: <sequence number>
|
|
// | |--creation-info-key: <creation info>
|
|
// | |--attempt-info-key: <attempt info>
|
|
// | |--settle-info-key: <settle info>
|
|
// | |--fail-info-key: <fail info>
|
|
// | |
|
|
// | |--duplicate-bucket (only for old, completed payments)
|
|
// | |
|
|
// | |-- <seq-num>
|
|
// | | |--sequence-key: <sequence number>
|
|
// | | |--creation-info-key: <creation info>
|
|
// | | |--attempt-info-key: <attempt info>
|
|
// | | |--settle-info-key: <settle info>
|
|
// | | |--fail-info-key: <fail info>
|
|
// | |
|
|
// | |-- <seq-num>
|
|
// | | |
|
|
// | ... ...
|
|
// |
|
|
// |-- <paymenthash>
|
|
// | |
|
|
// | ...
|
|
// ...
|
|
//
|
|
paymentsRootBucket = []byte("payments-root-bucket")
|
|
|
|
// paymentDublicateBucket is the name of a optional sub-bucket within
|
|
// the payment hash bucket, that is used to hold duplicate payments to
|
|
// a payment hash. This is needed to support information from earlier
|
|
// versions of lnd, where it was possible to pay to a payment hash more
|
|
// than once.
|
|
paymentDuplicateBucket = []byte("payment-duplicate-bucket")
|
|
|
|
// paymentSequenceKey is a key used in the payment's sub-bucket to
|
|
// store the sequence number of the payment.
|
|
paymentSequenceKey = []byte("payment-sequence-key")
|
|
|
|
// paymentCreationInfoKey is a key used in the payment's sub-bucket to
|
|
// store the creation info of the payment.
|
|
paymentCreationInfoKey = []byte("payment-creation-info")
|
|
|
|
// paymentAttemptInfoKey is a key used in the payment's sub-bucket to
|
|
// store the info about the latest attempt that was done for the
|
|
// payment in question.
|
|
paymentAttemptInfoKey = []byte("payment-attempt-info")
|
|
|
|
// paymentSettleInfoKey is a key used in the payment's sub-bucket to
|
|
// store the settle info of the payment.
|
|
paymentSettleInfoKey = []byte("payment-settle-info")
|
|
|
|
// paymentFailInfoKey is a key used in the payment's sub-bucket to
|
|
// store information about the reason a payment failed.
|
|
paymentFailInfoKey = []byte("payment-fail-info")
|
|
)
|
|
|
|
// FailureReason encodes the reason a payment ultimately failed.
|
|
type FailureReason byte
|
|
|
|
const (
|
|
// FailureReasonTimeout indicates that the payment did timeout before a
|
|
// successful payment attempt was made.
|
|
FailureReasonTimeout FailureReason = 0
|
|
|
|
// FailureReasonNoRoute indicates no successful route to the
|
|
// destination was found during path finding.
|
|
FailureReasonNoRoute FailureReason = 1
|
|
|
|
// TODO(halseth): cancel state.
|
|
)
|
|
|
|
// PaymentStatus represent current status of payment
|
|
type PaymentStatus byte
|
|
|
|
const (
|
|
// StatusUnknown is the status where a payment has never been initiated
|
|
// and hence is unknown.
|
|
StatusUnknown PaymentStatus = 0
|
|
|
|
// StatusInFlight is the status where a payment has been initiated, but
|
|
// a response has not been received.
|
|
StatusInFlight PaymentStatus = 1
|
|
|
|
// StatusSucceeded is the status where a payment has been initiated and
|
|
// the payment was completed successfully.
|
|
StatusSucceeded PaymentStatus = 2
|
|
|
|
// StatusFailed is the status where a payment has been initiated and a
|
|
// failure result has come back.
|
|
StatusFailed PaymentStatus = 3
|
|
)
|
|
|
|
// Bytes returns status as slice of bytes.
|
|
func (ps PaymentStatus) Bytes() []byte {
|
|
return []byte{byte(ps)}
|
|
}
|
|
|
|
// FromBytes sets status from slice of bytes.
|
|
func (ps *PaymentStatus) FromBytes(status []byte) error {
|
|
if len(status) != 1 {
|
|
return errors.New("payment status is empty")
|
|
}
|
|
|
|
switch PaymentStatus(status[0]) {
|
|
case StatusUnknown, StatusInFlight, StatusSucceeded, StatusFailed:
|
|
*ps = PaymentStatus(status[0])
|
|
default:
|
|
return errors.New("unknown payment status")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// String returns readable representation of payment status.
|
|
func (ps PaymentStatus) String() string {
|
|
switch ps {
|
|
case StatusUnknown:
|
|
return "Unknown"
|
|
case StatusInFlight:
|
|
return "In Flight"
|
|
case StatusSucceeded:
|
|
return "Succeeded"
|
|
case StatusFailed:
|
|
return "Failed"
|
|
default:
|
|
return "Unknown"
|
|
}
|
|
}
|
|
|
|
// PaymentCreationInfo is the information necessary to have ready when
|
|
// initiating a payment, moving it into state InFlight.
|
|
type PaymentCreationInfo struct {
|
|
// PaymentHash is the hash this payment is paying to.
|
|
PaymentHash lntypes.Hash
|
|
|
|
// Value is the amount we are paying.
|
|
Value lnwire.MilliSatoshi
|
|
|
|
// CreatingDate is the time when this payment was initiated.
|
|
CreationDate time.Time
|
|
|
|
// PaymentRequest is the full payment request, if any.
|
|
PaymentRequest []byte
|
|
}
|
|
|
|
// PaymentAttemptInfo contains information about a specific payment 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 PaymentAttemptInfo 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
|
|
}
|
|
|
|
// Payment is a wrapper around a payment's PaymentCreationInfo,
|
|
// PaymentAttemptInfo, and preimage. All payments will have the
|
|
// PaymentCreationInfo set, the PaymentAttemptInfo will be set only if at least
|
|
// one payment attempt has been made, while only completed payments will have a
|
|
// non-zero payment preimage.
|
|
type Payment struct {
|
|
// sequenceNum is a unique identifier used to sort the payments in
|
|
// order of creation.
|
|
sequenceNum uint64
|
|
|
|
// Status is the current PaymentStatus of this payment.
|
|
Status PaymentStatus
|
|
|
|
// Info holds all static information about this payment, and is
|
|
// populated when the payment is initiated.
|
|
Info *PaymentCreationInfo
|
|
|
|
// Attempt is the information about the last payment attempt made.
|
|
//
|
|
// 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.
|
|
//
|
|
// NOTE: Can be nil if payment is not settled.
|
|
PaymentPreimage *lntypes.Preimage
|
|
|
|
// Failure 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 *FailureReason
|
|
}
|
|
|
|
// FetchPayments returns all sent payments found in the DB.
|
|
func (db *DB) FetchPayments() ([]*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 := fetchPayment(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 := fetchPayment(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 fetchPayment(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 = deserializePaymentAttemptInfo(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
|
|
}
|
|
|
|
// DeletePayments deletes all completed and failed payments from the DB.
|
|
func (db *DB) DeletePayments() error {
|
|
return db.Update(func(tx *bbolt.Tx) error {
|
|
payments := tx.Bucket(paymentsRootBucket)
|
|
if payments == nil {
|
|
return nil
|
|
}
|
|
|
|
var deleteBuckets [][]byte
|
|
err := payments.ForEach(func(k, _ []byte) error {
|
|
bucket := payments.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")
|
|
}
|
|
|
|
// If the status is InFlight, we cannot safely delete
|
|
// the payment information, so we return early.
|
|
paymentStatus := fetchPaymentStatus(bucket)
|
|
if paymentStatus == StatusInFlight {
|
|
return nil
|
|
}
|
|
|
|
deleteBuckets = append(deleteBuckets, k)
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, k := range deleteBuckets {
|
|
if err := payments.DeleteBucket(k); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func serializePaymentCreationInfo(w io.Writer, c *PaymentCreationInfo) error {
|
|
var scratch [8]byte
|
|
|
|
if _, err := w.Write(c.PaymentHash[:]); err != nil {
|
|
return err
|
|
}
|
|
|
|
byteOrder.PutUint64(scratch[:], uint64(c.Value))
|
|
if _, err := w.Write(scratch[:]); err != nil {
|
|
return err
|
|
}
|
|
|
|
byteOrder.PutUint64(scratch[:], uint64(c.CreationDate.Unix()))
|
|
if _, err := w.Write(scratch[:]); err != nil {
|
|
return err
|
|
}
|
|
|
|
byteOrder.PutUint32(scratch[:4], uint32(len(c.PaymentRequest)))
|
|
if _, err := w.Write(scratch[:4]); err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err := w.Write(c.PaymentRequest[:]); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func deserializePaymentCreationInfo(r io.Reader) (*PaymentCreationInfo, error) {
|
|
var scratch [8]byte
|
|
|
|
c := &PaymentCreationInfo{}
|
|
|
|
if _, err := io.ReadFull(r, c.PaymentHash[:]); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if _, err := io.ReadFull(r, scratch[:]); err != nil {
|
|
return nil, err
|
|
}
|
|
c.Value = lnwire.MilliSatoshi(byteOrder.Uint64(scratch[:]))
|
|
|
|
if _, err := io.ReadFull(r, scratch[:]); err != nil {
|
|
return nil, err
|
|
}
|
|
c.CreationDate = time.Unix(int64(byteOrder.Uint64(scratch[:])), 0)
|
|
|
|
if _, err := io.ReadFull(r, scratch[:4]); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
reqLen := uint32(byteOrder.Uint32(scratch[:4]))
|
|
payReq := make([]byte, reqLen)
|
|
if reqLen > 0 {
|
|
if _, err := io.ReadFull(r, payReq[:]); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
c.PaymentRequest = payReq
|
|
|
|
return c, nil
|
|
}
|
|
|
|
func serializePaymentAttemptInfo(w io.Writer, a *PaymentAttemptInfo) error {
|
|
if err := WriteElements(w, a.PaymentID, a.SessionKey); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := serializeRoute(w, a.Route); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func deserializePaymentAttemptInfo(r io.Reader) (*PaymentAttemptInfo, error) {
|
|
a := &PaymentAttemptInfo{}
|
|
err := ReadElements(r, &a.PaymentID, &a.SessionKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
a.Route, err = deserializeRoute(r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return a, nil
|
|
}
|
|
|
|
func serializeHop(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 deserializeHop(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
|
|
}
|
|
|
|
func serializeRoute(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 := serializeHop(w, h); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func deserializeRoute(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 := deserializeHop(r)
|
|
if err != nil {
|
|
return rt, err
|
|
}
|
|
hops = append(hops, hop)
|
|
}
|
|
rt.Hops = hops
|
|
|
|
return rt, nil
|
|
}
|