2016-12-05 14:59:36 +03:00
|
|
|
package channeldb
|
|
|
|
|
|
|
|
import (
|
2016-12-21 12:19:01 +03:00
|
|
|
"bytes"
|
|
|
|
"encoding/binary"
|
2019-05-23 21:05:28 +03:00
|
|
|
"fmt"
|
2016-12-31 03:32:20 +03:00
|
|
|
"io"
|
2019-05-23 21:05:28 +03:00
|
|
|
"sort"
|
2019-05-23 21:05:27 +03:00
|
|
|
"time"
|
2016-12-31 03:32:20 +03:00
|
|
|
|
2019-07-31 07:44:50 +03:00
|
|
|
"github.com/btcsuite/btcd/wire"
|
2018-03-11 06:00:57 +03:00
|
|
|
"github.com/coreos/bbolt"
|
2019-05-23 21:05:27 +03:00
|
|
|
"github.com/lightningnetwork/lnd/lntypes"
|
2017-08-22 08:49:56 +03:00
|
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
2019-11-05 01:11:28 +03:00
|
|
|
"github.com/lightningnetwork/lnd/record"
|
2019-05-08 16:00:19 +03:00
|
|
|
"github.com/lightningnetwork/lnd/routing/route"
|
2019-07-31 07:44:50 +03:00
|
|
|
"github.com/lightningnetwork/lnd/tlv"
|
2016-12-05 14:59:36 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2019-05-23 21:05:26 +03:00
|
|
|
// 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.
|
2019-05-23 21:05:27 +03:00
|
|
|
//
|
|
|
|
// Bucket hierarchy:
|
|
|
|
//
|
|
|
|
// root-bucket
|
|
|
|
// |
|
|
|
|
// |-- <paymenthash>
|
|
|
|
// | |--sequence-key: <sequence number>
|
|
|
|
// | |--creation-info-key: <creation info>
|
2020-02-20 20:08:01 +03:00
|
|
|
// | |--fail-info-key: <(optional) fail info>
|
|
|
|
// | |
|
|
|
|
// | |--payment-htlcs-bucket (shard-bucket)
|
|
|
|
// | | |
|
|
|
|
// | | |-- <htlc attempt ID>
|
|
|
|
// | | | |--htlc-attempt-info-key: <htlc attempt info>
|
|
|
|
// | | | |--htlc-settle-info-key: <(optional) settle info>
|
|
|
|
// | | | |--htlc-fail-info-key: <(optional) fail info>
|
|
|
|
// | | |
|
|
|
|
// | | |-- <htlc attempt ID>
|
|
|
|
// | | | |
|
|
|
|
// | | ... ...
|
|
|
|
// | |
|
2019-05-23 21:05:27 +03:00
|
|
|
// | |
|
|
|
|
// | |--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>
|
|
|
|
// | |
|
|
|
|
// | ...
|
|
|
|
// ...
|
|
|
|
//
|
2019-05-23 21:05:26 +03:00
|
|
|
paymentsRootBucket = []byte("payments-root-bucket")
|
|
|
|
|
2019-05-23 21:05:27 +03:00
|
|
|
// 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")
|
|
|
|
|
2020-02-20 20:08:01 +03:00
|
|
|
// paymentHtlcsBucket is a bucket where we'll store the information
|
|
|
|
// about the HTLCs that were attempted for a payment.
|
|
|
|
paymentHtlcsBucket = []byte("payment-htlcs-bucket")
|
|
|
|
|
|
|
|
// htlcAttemptInfoKey is a key used in a HTLC's sub-bucket to store the
|
|
|
|
// info about the attempt that was done for the HTLC in question.
|
|
|
|
htlcAttemptInfoKey = []byte("htlc-attempt-info")
|
2019-05-23 21:05:27 +03:00
|
|
|
|
2020-02-20 20:08:01 +03:00
|
|
|
// htlcSettleInfoKey is a key used in a HTLC's sub-bucket to store the
|
|
|
|
// settle info, if any.
|
|
|
|
htlcSettleInfoKey = []byte("htlc-settle-info")
|
|
|
|
|
|
|
|
// htlcFailInfoKey is a key used in a HTLC's sub-bucket to store
|
|
|
|
// failure information, if any.
|
|
|
|
htlcFailInfoKey = []byte("htlc-fail-info")
|
2019-05-23 21:05:27 +03:00
|
|
|
|
2019-05-23 21:05:30 +03:00
|
|
|
// 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")
|
2018-08-12 16:12:37 +03:00
|
|
|
)
|
|
|
|
|
2019-05-23 21:05:30 +03:00
|
|
|
// 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
|
|
|
|
|
2019-06-04 12:22:23 +03:00
|
|
|
// FailureReasonError indicates that an unexpected error happened during
|
|
|
|
// payment.
|
|
|
|
FailureReasonError FailureReason = 2
|
|
|
|
|
2019-11-20 07:42:21 +03:00
|
|
|
// FailureReasonPaymentDetails indicates that either the hash is unknown
|
|
|
|
// or the final cltv delta or amount is incorrect.
|
|
|
|
FailureReasonPaymentDetails FailureReason = 3
|
2019-06-04 12:22:23 +03:00
|
|
|
|
2019-11-21 13:59:17 +03:00
|
|
|
// FailureReasonInsufficientBalance indicates that we didn't have enough
|
|
|
|
// balance to complete the payment.
|
|
|
|
FailureReasonInsufficientBalance FailureReason = 4
|
|
|
|
|
2019-05-23 21:05:30 +03:00
|
|
|
// TODO(halseth): cancel state.
|
2019-05-01 15:31:24 +03:00
|
|
|
|
|
|
|
// TODO(joostjager): Add failure reasons for:
|
|
|
|
// LocalLiquidityInsufficient, RemoteCapacityInsufficient.
|
2019-05-23 21:05:30 +03:00
|
|
|
)
|
|
|
|
|
2019-03-22 12:21:25 +03:00
|
|
|
// String returns a human readable FailureReason
|
|
|
|
func (r FailureReason) String() string {
|
|
|
|
switch r {
|
|
|
|
case FailureReasonTimeout:
|
|
|
|
return "timeout"
|
|
|
|
case FailureReasonNoRoute:
|
|
|
|
return "no_route"
|
2019-06-04 12:22:23 +03:00
|
|
|
case FailureReasonError:
|
|
|
|
return "error"
|
2019-11-20 07:42:21 +03:00
|
|
|
case FailureReasonPaymentDetails:
|
2019-06-04 12:22:23 +03:00
|
|
|
return "incorrect_payment_details"
|
2019-11-21 13:59:17 +03:00
|
|
|
case FailureReasonInsufficientBalance:
|
|
|
|
return "insufficient_balance"
|
2019-03-22 12:21:25 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return "unknown"
|
|
|
|
}
|
|
|
|
|
2018-08-12 16:12:37 +03:00
|
|
|
// PaymentStatus represent current status of payment
|
|
|
|
type PaymentStatus byte
|
|
|
|
|
|
|
|
const (
|
2019-05-23 21:05:31 +03:00
|
|
|
// StatusUnknown is the status where a payment has never been initiated
|
|
|
|
// and hence is unknown.
|
|
|
|
StatusUnknown PaymentStatus = 0
|
2018-08-12 16:12:37 +03:00
|
|
|
|
2018-08-11 00:31:47 +03:00
|
|
|
// StatusInFlight is the status where a payment has been initiated, but
|
|
|
|
// a response has not been received.
|
2018-08-12 16:12:37 +03:00
|
|
|
StatusInFlight PaymentStatus = 1
|
|
|
|
|
2019-05-23 21:05:31 +03:00
|
|
|
// StatusSucceeded is the status where a payment has been initiated and
|
2018-08-11 00:31:47 +03:00
|
|
|
// the payment was completed successfully.
|
2019-05-23 21:05:31 +03:00
|
|
|
StatusSucceeded PaymentStatus = 2
|
2019-05-23 21:05:28 +03:00
|
|
|
|
|
|
|
// StatusFailed is the status where a payment has been initiated and a
|
|
|
|
// failure result has come back.
|
|
|
|
StatusFailed PaymentStatus = 3
|
2016-12-05 14:59:36 +03:00
|
|
|
)
|
|
|
|
|
2018-08-11 00:31:47 +03:00
|
|
|
// String returns readable representation of payment status.
|
2018-08-12 16:12:37 +03:00
|
|
|
func (ps PaymentStatus) String() string {
|
|
|
|
switch ps {
|
2019-05-23 21:05:31 +03:00
|
|
|
case StatusUnknown:
|
|
|
|
return "Unknown"
|
2018-08-12 16:12:37 +03:00
|
|
|
case StatusInFlight:
|
|
|
|
return "In Flight"
|
2019-05-23 21:05:31 +03:00
|
|
|
case StatusSucceeded:
|
|
|
|
return "Succeeded"
|
2019-05-23 21:05:28 +03:00
|
|
|
case StatusFailed:
|
|
|
|
return "Failed"
|
2018-08-12 16:12:37 +03:00
|
|
|
default:
|
|
|
|
return "Unknown"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-23 21:05:27 +03:00
|
|
|
// 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
|
|
|
|
|
2020-02-19 11:53:13 +03:00
|
|
|
// CreationTime is the time when this payment was initiated.
|
|
|
|
CreationTime time.Time
|
2019-05-23 21:05:27 +03:00
|
|
|
|
|
|
|
// PaymentRequest is the full payment request, if any.
|
|
|
|
PaymentRequest []byte
|
|
|
|
}
|
|
|
|
|
2019-05-23 21:05:28 +03:00
|
|
|
// FetchPayments returns all sent payments found in the DB.
|
2019-11-20 07:41:08 +03:00
|
|
|
//
|
|
|
|
// nolint: dupl
|
|
|
|
func (db *DB) FetchPayments() ([]*MPPayment, error) {
|
|
|
|
var payments []*MPPayment
|
2019-05-23 21:05:28 +03:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-11-08 14:39:51 +03:00
|
|
|
payments = append(payments, p)
|
2019-05-23 21:05:28 +03:00
|
|
|
|
|
|
|
// 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.
|
2020-02-19 16:34:27 +03:00
|
|
|
duplicatePayments, err := fetchDuplicatePayments(bucket)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2019-05-23 21:05:28 +03:00
|
|
|
}
|
|
|
|
|
2020-02-19 16:34:27 +03:00
|
|
|
payments = append(payments, duplicatePayments...)
|
|
|
|
|
|
|
|
return nil
|
2019-05-23 21:05:28 +03:00
|
|
|
})
|
|
|
|
})
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-11-08 14:39:51 +03:00
|
|
|
func fetchPayment(bucket *bbolt.Bucket) (*MPPayment, error) {
|
2019-05-23 21:05:28 +03:00
|
|
|
seqBytes := bucket.Get(paymentSequenceKey)
|
|
|
|
if seqBytes == nil {
|
|
|
|
return nil, fmt.Errorf("sequence number not found")
|
|
|
|
}
|
|
|
|
|
2020-02-20 20:08:01 +03:00
|
|
|
sequenceNum := binary.BigEndian.Uint64(seqBytes)
|
2019-05-23 21:05:28 +03:00
|
|
|
|
|
|
|
// Get the payment status.
|
2020-02-20 20:08:01 +03:00
|
|
|
paymentStatus, err := fetchPaymentStatus(bucket)
|
2020-02-20 12:13:23 +03:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-05-23 21:05:28 +03:00
|
|
|
|
|
|
|
// Get the PaymentCreationInfo.
|
|
|
|
b := bucket.Get(paymentCreationInfoKey)
|
|
|
|
if b == nil {
|
|
|
|
return nil, fmt.Errorf("creation info not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
r := bytes.NewReader(b)
|
2020-02-20 20:08:01 +03:00
|
|
|
creationInfo, err := deserializePaymentCreationInfo(r)
|
2019-05-23 21:05:28 +03:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-02-20 20:08:01 +03:00
|
|
|
var htlcs []HTLCAttempt
|
|
|
|
htlcsBucket := bucket.Bucket(paymentHtlcsBucket)
|
|
|
|
if htlcsBucket != nil {
|
|
|
|
// Get the payment attempts. This can be empty.
|
|
|
|
htlcs, err = fetchHtlcAttempts(htlcsBucket)
|
2019-05-23 21:05:28 +03:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-23 21:05:30 +03:00
|
|
|
// Get failure reason if available.
|
2020-02-20 20:08:01 +03:00
|
|
|
var failureReason *FailureReason
|
2019-05-23 21:05:30 +03:00
|
|
|
b = bucket.Get(paymentFailInfoKey)
|
|
|
|
if b != nil {
|
|
|
|
reason := FailureReason(b[0])
|
2020-02-20 20:08:01 +03:00
|
|
|
failureReason = &reason
|
|
|
|
}
|
|
|
|
|
|
|
|
return &MPPayment{
|
|
|
|
sequenceNum: sequenceNum,
|
|
|
|
Info: creationInfo,
|
|
|
|
HTLCs: htlcs,
|
|
|
|
FailureReason: failureReason,
|
|
|
|
Status: paymentStatus,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// fetchHtlcAttempts retrives all htlc attempts made for the payment found in
|
|
|
|
// the given bucket.
|
|
|
|
func fetchHtlcAttempts(bucket *bbolt.Bucket) ([]HTLCAttempt, error) {
|
|
|
|
htlcs := make([]HTLCAttempt, 0)
|
|
|
|
|
|
|
|
err := bucket.ForEach(func(k, _ []byte) error {
|
|
|
|
aid := byteOrder.Uint64(k)
|
|
|
|
htlcBucket := bucket.Bucket(k)
|
|
|
|
|
|
|
|
attemptInfo, err := fetchHtlcAttemptInfo(
|
|
|
|
htlcBucket,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
attemptInfo.AttemptID = aid
|
|
|
|
|
|
|
|
htlc := HTLCAttempt{
|
|
|
|
HTLCAttemptInfo: *attemptInfo,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Settle info might be nil.
|
|
|
|
htlc.Settle, err = fetchHtlcSettleInfo(htlcBucket)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Failure info might be nil.
|
|
|
|
htlc.Failure, err = fetchHtlcFailInfo(htlcBucket)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
htlcs = append(htlcs, htlc)
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return htlcs, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// fetchHtlcAttemptInfo fetches the payment attempt info for this htlc from the
|
|
|
|
// bucket.
|
|
|
|
func fetchHtlcAttemptInfo(bucket *bbolt.Bucket) (*HTLCAttemptInfo, error) {
|
|
|
|
b := bucket.Get(htlcAttemptInfoKey)
|
|
|
|
if b == nil {
|
|
|
|
return nil, errNoAttemptInfo
|
|
|
|
}
|
|
|
|
|
|
|
|
r := bytes.NewReader(b)
|
|
|
|
return deserializeHTLCAttemptInfo(r)
|
|
|
|
}
|
|
|
|
|
|
|
|
// fetchHtlcSettleInfo retrieves the settle info for the htlc. If the htlc isn't
|
|
|
|
// settled, nil is returned.
|
|
|
|
func fetchHtlcSettleInfo(bucket *bbolt.Bucket) (*HTLCSettleInfo, error) {
|
|
|
|
b := bucket.Get(htlcSettleInfoKey)
|
|
|
|
if b == nil {
|
|
|
|
// Settle info is optional.
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
r := bytes.NewReader(b)
|
|
|
|
return deserializeHTLCSettleInfo(r)
|
|
|
|
}
|
|
|
|
|
|
|
|
// fetchHtlcFailInfo retrieves the failure info for the htlc. If the htlc hasn't
|
|
|
|
// failed, nil is returned.
|
|
|
|
func fetchHtlcFailInfo(bucket *bbolt.Bucket) (*HTLCFailInfo, error) {
|
|
|
|
b := bucket.Get(htlcFailInfoKey)
|
|
|
|
if b == nil {
|
|
|
|
// Fail info is optional.
|
|
|
|
return nil, nil
|
2019-05-23 21:05:30 +03:00
|
|
|
}
|
|
|
|
|
2020-02-20 20:08:01 +03:00
|
|
|
r := bytes.NewReader(b)
|
|
|
|
return deserializeHTLCFailInfo(r)
|
2019-05-23 21:05:28 +03:00
|
|
|
}
|
|
|
|
|
2019-05-23 21:05:29 +03:00
|
|
|
// 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.
|
2020-02-20 12:13:23 +03:00
|
|
|
paymentStatus, err := fetchPaymentStatus(bucket)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-05-23 21:05:29 +03:00
|
|
|
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
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-02-20 20:08:01 +03:00
|
|
|
// nolint: dupl
|
2019-05-23 21:05:27 +03:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2020-02-20 20:08:01 +03:00
|
|
|
if err := serializeTime(w, c.CreationTime); err != nil {
|
2019-05-23 21:05:27 +03:00
|
|
|
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[:]))
|
|
|
|
|
2020-02-20 20:08:01 +03:00
|
|
|
creationTime, err := deserializeTime(r)
|
|
|
|
if err != nil {
|
2019-05-23 21:05:27 +03:00
|
|
|
return nil, err
|
|
|
|
}
|
2020-02-20 20:08:01 +03:00
|
|
|
c.CreationTime = creationTime
|
2019-05-23 21:05:27 +03:00
|
|
|
|
|
|
|
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 {
|
2019-11-20 07:41:08 +03:00
|
|
|
if _, err := io.ReadFull(r, payReq); err != nil {
|
2019-05-23 21:05:27 +03:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
c.PaymentRequest = payReq
|
|
|
|
|
|
|
|
return c, nil
|
|
|
|
}
|
|
|
|
|
2020-02-07 12:31:27 +03:00
|
|
|
func serializeHTLCAttemptInfo(w io.Writer, a *HTLCAttemptInfo) error {
|
2020-02-20 20:08:01 +03:00
|
|
|
if err := WriteElements(w, a.SessionKey); err != nil {
|
2019-05-23 21:05:27 +03:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-06-14 16:01:48 +03:00
|
|
|
if err := SerializeRoute(w, a.Route); err != nil {
|
2019-05-23 21:05:27 +03:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-02-20 20:08:01 +03:00
|
|
|
return serializeTime(w, a.AttemptTime)
|
2019-05-23 21:05:27 +03:00
|
|
|
}
|
|
|
|
|
2020-02-07 12:31:27 +03:00
|
|
|
func deserializeHTLCAttemptInfo(r io.Reader) (*HTLCAttemptInfo, error) {
|
|
|
|
a := &HTLCAttemptInfo{}
|
2020-02-20 20:08:01 +03:00
|
|
|
err := ReadElements(r, &a.SessionKey)
|
2019-05-23 21:05:27 +03:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-06-14 16:01:48 +03:00
|
|
|
a.Route, err = DeserializeRoute(r)
|
2019-05-23 21:05:27 +03:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-02-20 20:08:01 +03:00
|
|
|
|
|
|
|
a.AttemptTime, err = deserializeTime(r)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-05-23 21:05:27 +03:00
|
|
|
return a, nil
|
|
|
|
}
|
|
|
|
|
2019-05-08 16:00:19 +03:00
|
|
|
func serializeHop(w io.Writer, h *route.Hop) error {
|
|
|
|
if err := WriteElements(w,
|
2019-11-05 01:11:28 +03:00
|
|
|
h.PubKeyBytes[:],
|
|
|
|
h.ChannelID,
|
|
|
|
h.OutgoingTimeLock,
|
2019-05-08 16:00:19 +03:00
|
|
|
h.AmtToForward,
|
|
|
|
); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-07-31 07:44:50 +03:00
|
|
|
if err := binary.Write(w, byteOrder, h.LegacyPayload); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// For legacy payloads, we don't need to write any TLV records, so
|
|
|
|
// we'll write a zero indicating the our serialized TLV map has no
|
|
|
|
// records.
|
|
|
|
if h.LegacyPayload {
|
|
|
|
return WriteElements(w, uint32(0))
|
|
|
|
}
|
|
|
|
|
2019-11-05 01:11:28 +03:00
|
|
|
// Gather all non-primitive TLV records so that they can be serialized
|
|
|
|
// as a single blob.
|
|
|
|
//
|
|
|
|
// TODO(conner): add migration to unify all fields in a single TLV
|
|
|
|
// blobs. The split approach will cause headaches down the road as more
|
|
|
|
// fields are added, which we can avoid by having a single TLV stream
|
|
|
|
// for all payload fields.
|
|
|
|
var records []tlv.Record
|
|
|
|
if h.MPP != nil {
|
|
|
|
records = append(records, h.MPP.Record())
|
|
|
|
}
|
2019-12-11 12:52:27 +03:00
|
|
|
|
2019-12-12 16:47:46 +03:00
|
|
|
// Final sanity check to absolutely rule out custom records that are not
|
|
|
|
// custom and write into the standard range.
|
|
|
|
if err := h.CustomRecords.Validate(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Convert custom records to tlv and add to the record list.
|
|
|
|
// MapToRecords sorts the list, so adding it here will keep the list
|
|
|
|
// canonical.
|
2019-12-11 12:52:27 +03:00
|
|
|
tlvRecords := tlv.MapToRecords(h.CustomRecords)
|
|
|
|
records = append(records, tlvRecords...)
|
2019-11-05 01:11:28 +03:00
|
|
|
|
2019-07-31 07:44:50 +03:00
|
|
|
// Otherwise, we'll transform our slice of records into a map of the
|
|
|
|
// raw bytes, then serialize them in-line with a length (number of
|
|
|
|
// elements) prefix.
|
2019-11-05 01:11:28 +03:00
|
|
|
mapRecords, err := tlv.RecordsToMap(records)
|
2019-07-31 07:44:50 +03:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
numRecords := uint32(len(mapRecords))
|
|
|
|
if err := WriteElements(w, numRecords); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for recordType, rawBytes := range mapRecords {
|
|
|
|
if err := WriteElements(w, recordType); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := wire.WriteVarBytes(w, 0, rawBytes); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-08 16:00:19 +03:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-07-31 07:44:50 +03:00
|
|
|
// maxOnionPayloadSize is the largest Sphinx payload possible, so we don't need
|
|
|
|
// to read/write a TLV stream larger than this.
|
|
|
|
const maxOnionPayloadSize = 1300
|
|
|
|
|
2019-05-08 16:00:19 +03:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-07-31 07:44:50 +03:00
|
|
|
// TODO(roasbeef): change field to allow LegacyPayload false to be the
|
|
|
|
// legacy default?
|
|
|
|
err := binary.Read(r, byteOrder, &h.LegacyPayload)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var numElements uint32
|
|
|
|
if err := ReadElements(r, &numElements); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// If there're no elements, then we can return early.
|
|
|
|
if numElements == 0 {
|
|
|
|
return h, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
tlvMap := make(map[uint64][]byte)
|
|
|
|
for i := uint32(0); i < numElements; i++ {
|
|
|
|
var tlvType uint64
|
|
|
|
if err := ReadElements(r, &tlvType); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
rawRecordBytes, err := wire.ReadVarBytes(
|
|
|
|
r, 0, maxOnionPayloadSize, "tlv",
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
tlvMap[tlvType] = rawRecordBytes
|
|
|
|
}
|
|
|
|
|
2019-11-05 01:11:28 +03:00
|
|
|
// If the MPP type is present, remove it from the generic TLV map and
|
|
|
|
// parse it back into a proper MPP struct.
|
|
|
|
//
|
|
|
|
// TODO(conner): add migration to unify all fields in a single TLV
|
|
|
|
// blobs. The split approach will cause headaches down the road as more
|
|
|
|
// fields are added, which we can avoid by having a single TLV stream
|
|
|
|
// for all payload fields.
|
|
|
|
mppType := uint64(record.MPPOnionType)
|
|
|
|
if mppBytes, ok := tlvMap[mppType]; ok {
|
|
|
|
delete(tlvMap, mppType)
|
|
|
|
|
|
|
|
var (
|
|
|
|
mpp = &record.MPP{}
|
|
|
|
mppRec = mpp.Record()
|
|
|
|
r = bytes.NewReader(mppBytes)
|
|
|
|
)
|
|
|
|
err := mppRec.Decode(r, uint64(len(mppBytes)))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
h.MPP = mpp
|
|
|
|
}
|
|
|
|
|
2019-12-11 12:52:27 +03:00
|
|
|
h.CustomRecords = tlvMap
|
2019-07-31 07:44:50 +03:00
|
|
|
|
2019-05-08 16:00:19 +03:00
|
|
|
return h, nil
|
|
|
|
}
|
|
|
|
|
2019-06-14 16:01:48 +03:00
|
|
|
// SerializeRoute serializes a route.
|
|
|
|
func SerializeRoute(w io.Writer, r route.Route) error {
|
2019-05-08 16:00:19 +03:00
|
|
|
if err := WriteElements(w,
|
2019-05-15 15:37:58 +03:00
|
|
|
r.TotalTimeLock, r.TotalAmount, r.SourcePubKey[:],
|
2019-05-08 16:00:19 +03:00
|
|
|
); 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
|
|
|
|
}
|
|
|
|
|
2019-06-14 16:01:48 +03:00
|
|
|
// DeserializeRoute deserializes a route.
|
|
|
|
func DeserializeRoute(r io.Reader) (route.Route, error) {
|
2019-05-08 16:00:19 +03:00
|
|
|
rt := route.Route{}
|
|
|
|
if err := ReadElements(r,
|
2019-05-15 15:37:58 +03:00
|
|
|
&rt.TotalTimeLock, &rt.TotalAmount,
|
2019-05-08 16:00:19 +03:00
|
|
|
); 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
|
|
|
|
}
|