channeldb+routing: store all payment htlcs

This commit converts the database structure of a payment so that it can
not just store the last htlc attempt, but all attempts that have been
made. This is a preparation for mpp sending.

In addition to that, we now also persist the fail time of an htlc. In a
later commit, the full failure reason will be added as well.

A key change is made to the control tower interface. Previously the
control tower wasn't aware of individual htlc outcomes. The payment
remained in-flight with the latest attempt recorded, but an outcome was
only set when the payment finished. With this commit, the outcome of
every htlc is expected by the control tower and recorded in the
database.

Co-authored-by: Johan T. Halseth <johanth@gmail.com>
This commit is contained in:
Joost Jager
2020-02-20 18:08:01 +01:00
parent 8558534417
commit 48c0e42c26
10 changed files with 550 additions and 274 deletions

View File

@@ -30,9 +30,19 @@ var (
// |-- <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>
// | |--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>
// | | | |
// | | ... ...
// | |
// | |
// | |--duplicate-bucket (only for old, completed payments)
// | |
@@ -62,14 +72,21 @@ var (
// 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")
// paymentHtlcsBucket is a bucket where we'll store the information
// about the HTLCs that were attempted for a payment.
paymentHtlcsBucket = []byte("payment-htlcs-bucket")
// paymentSettleInfoKey is a key used in the payment's sub-bucket to
// store the settle info of the payment.
paymentSettleInfoKey = []byte("payment-settle-info")
// 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")
// 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")
// paymentFailInfoKey is a key used in the payment's sub-bucket to
// store information about the reason a payment failed.
@@ -177,98 +194,6 @@ type PaymentCreationInfo struct {
PaymentRequest []byte
}
// Payment is a wrapper around a payment's PaymentCreationInfo,
// HTLCAttemptInfo, and preimage. All payments will have the
// PaymentCreationInfo set, the HTLCAttemptInfo 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 *HTLCAttemptInfo
// 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.
Preimage *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
}
// 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{
{
HTLCAttemptInfo: *p.Attempt,
Settle: settle,
Failure: failure,
},
}
}
return &MPPayment{
sequenceNum: p.sequenceNum,
Info: &PaymentCreationInfo{
PaymentHash: p.Info.PaymentHash,
Value: p.Info.Value,
CreationTime: p.Info.CreationTime,
PaymentRequest: p.Info.PaymentRequest,
},
HTLCs: htlcs,
FailureReason: reason,
Status: p.Status,
}
}
// FetchPayments returns all sent payments found in the DB.
//
// nolint: dupl
@@ -324,20 +249,15 @@ func (db *DB) FetchPayments() ([]*MPPayment, error) {
}
func fetchPayment(bucket *bbolt.Bucket) (*MPPayment, 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)
sequenceNum := binary.BigEndian.Uint64(seqBytes)
// Get the payment status.
p.Status, err = fetchPaymentStatus(bucket)
paymentStatus, err := fetchPaymentStatus(bucket)
if err != nil {
return nil, err
}
@@ -349,39 +269,118 @@ func fetchPayment(bucket *bbolt.Bucket) (*MPPayment, error) {
}
r := bytes.NewReader(b)
p.Info, err = deserializePaymentCreationInfo(r)
creationInfo, err := deserializePaymentCreationInfo(r)
if err != nil {
return nil, err
}
// Get the HTLCAttemptInfo. This can be unset.
b = bucket.Get(paymentAttemptInfoKey)
if b != nil {
r = bytes.NewReader(b)
p.Attempt, err = deserializeHTLCAttemptInfo(r)
var htlcs []HTLCAttempt
htlcsBucket := bucket.Bucket(paymentHtlcsBucket)
if htlcsBucket != nil {
// Get the payment attempts. This can be empty.
htlcs, err = fetchHtlcAttempts(htlcsBucket)
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.Preimage = &preimg
}
// Get failure reason if available.
var failureReason *FailureReason
b = bucket.Get(paymentFailInfoKey)
if b != nil {
reason := FailureReason(b[0])
p.Failure = &reason
failureReason = &reason
}
return p.ToMPPayment(), nil
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
}
r := bytes.NewReader(b)
return deserializeHTLCFailInfo(r)
}
// DeletePayments deletes all completed and failed payments from the DB.
@@ -430,6 +429,7 @@ func (db *DB) DeletePayments() error {
})
}
// nolint: dupl
func serializePaymentCreationInfo(w io.Writer, c *PaymentCreationInfo) error {
var scratch [8]byte
@@ -442,8 +442,7 @@ func serializePaymentCreationInfo(w io.Writer, c *PaymentCreationInfo) error {
return err
}
byteOrder.PutUint64(scratch[:], uint64(c.CreationTime.Unix()))
if _, err := w.Write(scratch[:]); err != nil {
if err := serializeTime(w, c.CreationTime); err != nil {
return err
}
@@ -473,10 +472,11 @@ func deserializePaymentCreationInfo(r io.Reader) (*PaymentCreationInfo, error) {
}
c.Value = lnwire.MilliSatoshi(byteOrder.Uint64(scratch[:]))
if _, err := io.ReadFull(r, scratch[:]); err != nil {
creationTime, err := deserializeTime(r)
if err != nil {
return nil, err
}
c.CreationTime = time.Unix(int64(byteOrder.Uint64(scratch[:])), 0)
c.CreationTime = creationTime
if _, err := io.ReadFull(r, scratch[:4]); err != nil {
return nil, err
@@ -495,7 +495,7 @@ func deserializePaymentCreationInfo(r io.Reader) (*PaymentCreationInfo, error) {
}
func serializeHTLCAttemptInfo(w io.Writer, a *HTLCAttemptInfo) error {
if err := WriteElements(w, a.AttemptID, a.SessionKey); err != nil {
if err := WriteElements(w, a.SessionKey); err != nil {
return err
}
@@ -503,12 +503,12 @@ func serializeHTLCAttemptInfo(w io.Writer, a *HTLCAttemptInfo) error {
return err
}
return nil
return serializeTime(w, a.AttemptTime)
}
func deserializeHTLCAttemptInfo(r io.Reader) (*HTLCAttemptInfo, error) {
a := &HTLCAttemptInfo{}
err := ReadElements(r, &a.AttemptID, &a.SessionKey)
err := ReadElements(r, &a.SessionKey)
if err != nil {
return nil, err
}
@@ -516,6 +516,12 @@ func deserializeHTLCAttemptInfo(r io.Reader) (*HTLCAttemptInfo, error) {
if err != nil {
return nil, err
}
a.AttemptTime, err = deserializeTime(r)
if err != nil {
return nil, err
}
return a, nil
}