2019-05-23 21:05:26 +03:00
|
|
|
package channeldb
|
2018-08-12 16:18:35 +03:00
|
|
|
|
|
|
|
import (
|
2019-05-23 21:05:28 +03:00
|
|
|
"bytes"
|
|
|
|
"encoding/binary"
|
2018-08-12 16:18:35 +03:00
|
|
|
"errors"
|
2019-05-23 21:05:29 +03:00
|
|
|
"fmt"
|
2018-08-12 16:18:35 +03:00
|
|
|
|
2018-08-21 07:14:52 +03:00
|
|
|
"github.com/coreos/bbolt"
|
2019-05-23 21:05:26 +03:00
|
|
|
"github.com/lightningnetwork/lnd/lntypes"
|
2018-08-12 16:18:35 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2018-08-11 00:00:50 +03:00
|
|
|
// ErrAlreadyPaid signals we have already paid this payment hash.
|
|
|
|
ErrAlreadyPaid = errors.New("invoice is already paid")
|
2018-08-12 16:18:35 +03:00
|
|
|
|
2018-08-11 00:00:50 +03:00
|
|
|
// ErrPaymentInFlight signals that payment for this payment hash is
|
|
|
|
// already "in flight" on the network.
|
2018-08-12 16:18:35 +03:00
|
|
|
ErrPaymentInFlight = errors.New("payment is in transition")
|
|
|
|
|
2018-08-11 00:00:50 +03:00
|
|
|
// ErrPaymentNotInitiated is returned if payment wasn't initiated in
|
|
|
|
// switch.
|
2018-08-12 16:18:35 +03:00
|
|
|
ErrPaymentNotInitiated = errors.New("payment isn't initiated")
|
|
|
|
|
2019-05-23 21:05:31 +03:00
|
|
|
// ErrPaymentAlreadySucceeded is returned in the event we attempt to
|
|
|
|
// change the status of a payment already succeeded.
|
|
|
|
ErrPaymentAlreadySucceeded = errors.New("payment is already succeeded")
|
2018-08-11 00:00:50 +03:00
|
|
|
|
2019-05-23 21:05:28 +03:00
|
|
|
// ErrPaymentAlreadyFailed is returned in the event we attempt to
|
|
|
|
// re-fail a failed payment.
|
|
|
|
ErrPaymentAlreadyFailed = errors.New("payment has already failed")
|
|
|
|
|
2018-08-11 00:00:50 +03:00
|
|
|
// ErrUnknownPaymentStatus is returned when we do not recognize the
|
|
|
|
// existing state of a payment.
|
|
|
|
ErrUnknownPaymentStatus = errors.New("unknown payment status")
|
2019-04-30 14:24:37 +03:00
|
|
|
|
|
|
|
// errNoAttemptInfo is returned when no attempt info is stored yet.
|
|
|
|
errNoAttemptInfo = errors.New("unable to find attempt info for " +
|
|
|
|
"inflight payment")
|
2018-08-12 16:18:35 +03:00
|
|
|
)
|
|
|
|
|
2019-05-29 09:57:04 +03:00
|
|
|
// PaymentControl implements persistence for payments and payment attempts.
|
|
|
|
type PaymentControl struct {
|
2019-05-23 21:05:26 +03:00
|
|
|
db *DB
|
2018-08-12 16:18:35 +03:00
|
|
|
}
|
|
|
|
|
2019-05-29 09:57:04 +03:00
|
|
|
// NewPaymentControl creates a new instance of the PaymentControl.
|
|
|
|
func NewPaymentControl(db *DB) *PaymentControl {
|
|
|
|
return &PaymentControl{
|
2019-05-23 21:05:28 +03:00
|
|
|
db: db,
|
2018-08-12 16:18:35 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-23 21:05:28 +03:00
|
|
|
// InitPayment checks or records the given PaymentCreationInfo with the DB,
|
|
|
|
// making sure it does not already exist as an in-flight payment. Then this
|
|
|
|
// method returns successfully, the payment is guranteeed to be in the InFlight
|
|
|
|
// state.
|
2019-05-29 09:57:04 +03:00
|
|
|
func (p *PaymentControl) InitPayment(paymentHash lntypes.Hash,
|
2019-05-23 21:05:28 +03:00
|
|
|
info *PaymentCreationInfo) error {
|
|
|
|
|
|
|
|
var b bytes.Buffer
|
|
|
|
if err := serializePaymentCreationInfo(&b, info); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
infoBytes := b.Bytes()
|
|
|
|
|
2019-05-23 21:05:31 +03:00
|
|
|
var updateErr error
|
2018-11-30 07:04:21 +03:00
|
|
|
err := p.db.Batch(func(tx *bbolt.Tx) error {
|
2019-05-23 21:05:31 +03:00
|
|
|
// Reset the update error, to avoid carrying over an error
|
|
|
|
// from a previous execution of the batched db transaction.
|
|
|
|
updateErr = nil
|
|
|
|
|
|
|
|
bucket, err := createPaymentBucket(tx, paymentHash)
|
2018-08-21 07:14:52 +03:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-05-23 21:05:26 +03:00
|
|
|
// Get the existing status of this payment, if any.
|
2020-02-20 12:13:23 +03:00
|
|
|
paymentStatus, err := fetchPaymentStatus(bucket)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-05-23 21:05:26 +03:00
|
|
|
|
2018-08-21 07:14:52 +03:00
|
|
|
switch paymentStatus {
|
|
|
|
|
2019-05-23 21:05:28 +03:00
|
|
|
// We allow retrying failed payments.
|
|
|
|
case StatusFailed:
|
|
|
|
|
2019-05-23 21:05:28 +03:00
|
|
|
// This is a new payment that is being initialized for the
|
|
|
|
// first time.
|
2019-05-23 21:05:31 +03:00
|
|
|
case StatusUnknown:
|
2018-08-21 07:14:52 +03:00
|
|
|
|
2019-05-23 21:05:28 +03:00
|
|
|
// We already have an InFlight payment on the network. We will
|
2019-05-23 21:05:28 +03:00
|
|
|
// disallow any new payments.
|
2019-05-23 21:05:26 +03:00
|
|
|
case StatusInFlight:
|
2019-05-23 21:05:31 +03:00
|
|
|
updateErr = ErrPaymentInFlight
|
2019-05-23 21:05:28 +03:00
|
|
|
return nil
|
2018-08-21 07:14:52 +03:00
|
|
|
|
2019-05-23 21:05:31 +03:00
|
|
|
// We've already succeeded a payment to this payment hash,
|
2019-05-23 21:05:28 +03:00
|
|
|
// forbid the switch from sending another.
|
2019-05-23 21:05:31 +03:00
|
|
|
case StatusSucceeded:
|
2019-05-23 21:05:31 +03:00
|
|
|
updateErr = ErrAlreadyPaid
|
2019-05-23 21:05:28 +03:00
|
|
|
return nil
|
2018-08-21 07:14:52 +03:00
|
|
|
|
|
|
|
default:
|
2019-05-23 21:05:31 +03:00
|
|
|
updateErr = ErrUnknownPaymentStatus
|
2019-05-23 21:05:28 +03:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Obtain a new sequence number for this payment. This is used
|
|
|
|
// to sort the payments in order of creation, and also acts as
|
|
|
|
// a unique identifier for each payment.
|
|
|
|
sequenceNum, err := nextPaymentSequence(tx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = bucket.Put(paymentSequenceKey, sequenceNum)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add the payment info to the bucket, which contains the
|
|
|
|
// static information for this payment
|
|
|
|
err = bucket.Put(paymentCreationInfoKey, infoBytes)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-02-20 20:08:01 +03:00
|
|
|
// We'll delete any lingering HTLCs to start with, in case we
|
|
|
|
// are initializing a payment that was attempted earlier, but
|
|
|
|
// left in a state where we could retry.
|
|
|
|
err = bucket.DeleteBucket(paymentHtlcsBucket)
|
|
|
|
if err != nil && err != bbolt.ErrBucketNotFound {
|
2019-05-23 21:05:28 +03:00
|
|
|
return err
|
2018-08-21 07:14:52 +03:00
|
|
|
}
|
|
|
|
|
2019-05-23 21:05:30 +03:00
|
|
|
// Also delete any lingering failure info now that we are
|
|
|
|
// re-attempting.
|
|
|
|
return bucket.Delete(paymentFailInfoKey)
|
2018-08-21 07:14:52 +03:00
|
|
|
})
|
2018-08-12 16:18:35 +03:00
|
|
|
if err != nil {
|
2019-09-09 12:41:43 +03:00
|
|
|
return err
|
2018-08-12 16:18:35 +03:00
|
|
|
}
|
|
|
|
|
2019-05-23 21:05:31 +03:00
|
|
|
return updateErr
|
2018-08-12 16:18:35 +03:00
|
|
|
}
|
|
|
|
|
2020-02-07 12:31:27 +03:00
|
|
|
// RegisterAttempt atomically records the provided HTLCAttemptInfo to the
|
2019-05-23 21:05:28 +03:00
|
|
|
// DB.
|
2019-05-29 09:57:04 +03:00
|
|
|
func (p *PaymentControl) RegisterAttempt(paymentHash lntypes.Hash,
|
2020-02-07 12:31:27 +03:00
|
|
|
attempt *HTLCAttemptInfo) error {
|
2019-05-23 21:05:28 +03:00
|
|
|
|
|
|
|
// Serialize the information before opening the db transaction.
|
|
|
|
var a bytes.Buffer
|
2020-02-20 20:08:01 +03:00
|
|
|
err := serializeHTLCAttemptInfo(&a, attempt)
|
|
|
|
if err != nil {
|
2019-05-23 21:05:28 +03:00
|
|
|
return err
|
|
|
|
}
|
2020-02-20 20:08:01 +03:00
|
|
|
htlcInfoBytes := a.Bytes()
|
2019-05-23 21:05:28 +03:00
|
|
|
|
2020-02-20 20:08:01 +03:00
|
|
|
htlcIDBytes := make([]byte, 8)
|
|
|
|
binary.BigEndian.PutUint64(htlcIDBytes, attempt.AttemptID)
|
2019-05-23 21:05:28 +03:00
|
|
|
|
2020-02-20 20:08:01 +03:00
|
|
|
return p.db.Update(func(tx *bbolt.Tx) error {
|
|
|
|
// Get the payment bucket to register this new attempt in.
|
2019-05-23 21:05:28 +03:00
|
|
|
bucket, err := fetchPaymentBucket(tx, paymentHash)
|
2020-02-20 20:08:01 +03:00
|
|
|
if err != nil {
|
2019-05-23 21:05:28 +03:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// We can only register attempts for payments that are
|
|
|
|
// in-flight.
|
|
|
|
if err := ensureInFlight(bucket); err != nil {
|
2020-02-20 20:08:01 +03:00
|
|
|
return err
|
2019-05-23 21:05:28 +03:00
|
|
|
}
|
|
|
|
|
2020-02-20 20:08:01 +03:00
|
|
|
htlcsBucket, err := bucket.CreateBucketIfNotExists(
|
|
|
|
paymentHtlcsBucket,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create bucket for this attempt. Fail if the bucket already
|
|
|
|
// exists.
|
|
|
|
htlcBucket, err := htlcsBucket.CreateBucket(htlcIDBytes)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return htlcBucket.Put(htlcAttemptInfoKey, htlcInfoBytes)
|
2019-05-23 21:05:28 +03:00
|
|
|
})
|
2020-02-20 20:08:01 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// SettleAttempt marks the given attempt settled with the preimage. If this is
|
|
|
|
// a multi shard payment, this might implicitly mean that the full payment
|
|
|
|
// succeeded.
|
|
|
|
//
|
|
|
|
// After invoking this method, InitPayment should always return an error to
|
|
|
|
// prevent us from making duplicate payments to the same payment hash. The
|
|
|
|
// provided preimage is atomically saved to the DB for record keeping.
|
|
|
|
func (p *PaymentControl) SettleAttempt(hash lntypes.Hash,
|
|
|
|
attemptID uint64, settleInfo *HTLCSettleInfo) (*MPPayment, error) {
|
|
|
|
|
|
|
|
var b bytes.Buffer
|
|
|
|
if err := serializeHTLCSettleInfo(&b, settleInfo); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
settleBytes := b.Bytes()
|
|
|
|
|
|
|
|
return p.updateHtlcKey(hash, attemptID, htlcSettleInfoKey, settleBytes)
|
|
|
|
}
|
|
|
|
|
|
|
|
// FailAttempt marks the given payment attempt failed.
|
|
|
|
func (p *PaymentControl) FailAttempt(hash lntypes.Hash,
|
|
|
|
attemptID uint64, failInfo *HTLCFailInfo) error {
|
|
|
|
|
|
|
|
var b bytes.Buffer
|
|
|
|
if err := serializeHTLCFailInfo(&b, failInfo); err != nil {
|
2019-05-23 21:05:28 +03:00
|
|
|
return err
|
|
|
|
}
|
2020-02-20 20:08:01 +03:00
|
|
|
failBytes := b.Bytes()
|
2019-05-23 21:05:28 +03:00
|
|
|
|
2020-02-20 20:08:01 +03:00
|
|
|
_, err := p.updateHtlcKey(hash, attemptID, htlcFailInfoKey, failBytes)
|
|
|
|
return err
|
2019-05-23 21:05:28 +03:00
|
|
|
}
|
|
|
|
|
2020-02-20 20:08:01 +03:00
|
|
|
// updateHtlcKey updates a database key for the specified htlc.
|
|
|
|
func (p *PaymentControl) updateHtlcKey(paymentHash lntypes.Hash,
|
|
|
|
attemptID uint64, key, value []byte) (*MPPayment, error) {
|
2019-05-23 21:05:28 +03:00
|
|
|
|
2020-02-20 20:08:01 +03:00
|
|
|
htlcIDBytes := make([]byte, 8)
|
|
|
|
binary.BigEndian.PutUint64(htlcIDBytes, attemptID)
|
2018-08-21 07:14:52 +03:00
|
|
|
|
2020-02-20 20:08:01 +03:00
|
|
|
var payment *MPPayment
|
|
|
|
err := p.db.Batch(func(tx *bbolt.Tx) error {
|
|
|
|
// Fetch bucket that contains all information for the payment
|
|
|
|
// with this hash.
|
2019-05-23 21:05:28 +03:00
|
|
|
bucket, err := fetchPaymentBucket(tx, paymentHash)
|
2020-02-20 20:08:01 +03:00
|
|
|
if err != nil {
|
2019-05-23 21:05:28 +03:00
|
|
|
return err
|
|
|
|
}
|
2018-08-21 07:14:52 +03:00
|
|
|
|
2020-02-20 20:08:01 +03:00
|
|
|
// We can only update keys of in-flight payments.
|
2019-05-23 21:05:28 +03:00
|
|
|
if err := ensureInFlight(bucket); err != nil {
|
2020-02-20 20:08:01 +03:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
htlcsBucket := bucket.Bucket(paymentHtlcsBucket)
|
|
|
|
if htlcsBucket == nil {
|
|
|
|
return fmt.Errorf("htlcs bucket not found")
|
2018-08-21 07:14:52 +03:00
|
|
|
}
|
|
|
|
|
2020-02-20 20:08:01 +03:00
|
|
|
htlcBucket := htlcsBucket.Bucket(htlcIDBytes)
|
|
|
|
if htlcBucket == nil {
|
|
|
|
return fmt.Errorf("HTLC with ID %v not registered",
|
|
|
|
attemptID)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add or update the key for this htlc.
|
|
|
|
err = htlcBucket.Put(key, value)
|
2019-04-30 14:24:37 +03:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Retrieve attempt info for the notification.
|
2019-11-08 14:39:51 +03:00
|
|
|
payment, err = fetchPayment(bucket)
|
|
|
|
return err
|
2018-08-21 07:14:52 +03:00
|
|
|
})
|
2018-08-12 16:18:35 +03:00
|
|
|
if err != nil {
|
2019-04-30 14:24:37 +03:00
|
|
|
return nil, err
|
2018-08-12 16:18:35 +03:00
|
|
|
}
|
|
|
|
|
2020-02-20 20:08:01 +03:00
|
|
|
return payment, err
|
2018-08-12 16:18:35 +03:00
|
|
|
}
|
|
|
|
|
2019-05-23 21:05:30 +03:00
|
|
|
// Fail transitions a payment into the Failed state, and records the reason the
|
|
|
|
// payment failed. After invoking this method, InitPayment should return nil on
|
|
|
|
// its next call for this payment hash, allowing the switch to make a
|
|
|
|
// subsequent payment.
|
2019-05-29 09:57:04 +03:00
|
|
|
func (p *PaymentControl) Fail(paymentHash lntypes.Hash,
|
2019-11-08 14:39:51 +03:00
|
|
|
reason FailureReason) (*MPPayment, error) {
|
2019-05-23 21:05:30 +03:00
|
|
|
|
2019-06-04 18:18:41 +03:00
|
|
|
var (
|
|
|
|
updateErr error
|
2019-11-08 14:39:51 +03:00
|
|
|
payment *MPPayment
|
2019-06-04 18:18:41 +03:00
|
|
|
)
|
2018-11-30 07:04:21 +03:00
|
|
|
err := p.db.Batch(func(tx *bbolt.Tx) error {
|
2018-08-21 07:14:52 +03:00
|
|
|
// Reset the update error, to avoid carrying over an error
|
|
|
|
// from a previous execution of the batched db transaction.
|
|
|
|
updateErr = nil
|
2019-11-08 14:39:51 +03:00
|
|
|
payment = nil
|
2018-08-21 07:14:52 +03:00
|
|
|
|
2019-05-23 21:05:28 +03:00
|
|
|
bucket, err := fetchPaymentBucket(tx, paymentHash)
|
2019-05-23 21:05:31 +03:00
|
|
|
if err == ErrPaymentNotInitiated {
|
|
|
|
updateErr = ErrPaymentNotInitiated
|
|
|
|
return nil
|
|
|
|
} else if err != nil {
|
2019-05-23 21:05:28 +03:00
|
|
|
return err
|
|
|
|
}
|
2018-08-21 07:14:52 +03:00
|
|
|
|
2019-05-23 21:05:28 +03:00
|
|
|
// We can only mark in-flight payments as failed.
|
|
|
|
if err := ensureInFlight(bucket); err != nil {
|
|
|
|
updateErr = err
|
|
|
|
return nil
|
2018-08-21 07:14:52 +03:00
|
|
|
}
|
|
|
|
|
2019-05-23 21:05:30 +03:00
|
|
|
// Put the failure reason in the bucket for record keeping.
|
|
|
|
v := []byte{byte(reason)}
|
2019-06-04 18:18:41 +03:00
|
|
|
err = bucket.Put(paymentFailInfoKey, v)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Retrieve attempt info for the notification, if available.
|
2019-11-08 14:39:51 +03:00
|
|
|
payment, err = fetchPayment(bucket)
|
2020-02-20 20:08:01 +03:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Final sanity check to see if there are no in-flight htlcs.
|
|
|
|
for _, htlc := range payment.HTLCs {
|
|
|
|
if htlc.Settle == nil && htlc.Failure == nil {
|
|
|
|
return errors.New("payment failed with " +
|
|
|
|
"in-flight htlc(s)")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2018-08-21 07:14:52 +03:00
|
|
|
})
|
2018-08-12 16:18:35 +03:00
|
|
|
if err != nil {
|
2019-06-04 18:18:41 +03:00
|
|
|
return nil, err
|
2018-08-12 16:18:35 +03:00
|
|
|
}
|
|
|
|
|
2019-11-08 14:39:51 +03:00
|
|
|
return payment, updateErr
|
2018-08-12 16:18:35 +03:00
|
|
|
}
|
2019-05-23 21:05:26 +03:00
|
|
|
|
2019-04-30 14:24:37 +03:00
|
|
|
// FetchPayment returns information about a payment from the database.
|
|
|
|
func (p *PaymentControl) FetchPayment(paymentHash lntypes.Hash) (
|
2019-11-08 14:39:51 +03:00
|
|
|
*MPPayment, error) {
|
2019-04-30 14:24:37 +03:00
|
|
|
|
2019-11-08 14:39:51 +03:00
|
|
|
var payment *MPPayment
|
2019-04-30 14:24:37 +03:00
|
|
|
err := p.db.View(func(tx *bbolt.Tx) error {
|
|
|
|
bucket, err := fetchPaymentBucket(tx, paymentHash)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
payment, err = fetchPayment(bucket)
|
|
|
|
|
|
|
|
return err
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return payment, nil
|
|
|
|
}
|
|
|
|
|
2019-05-23 21:05:31 +03:00
|
|
|
// createPaymentBucket creates or fetches the sub-bucket assigned to this
|
2019-05-23 21:05:26 +03:00
|
|
|
// payment hash.
|
2019-05-23 21:05:31 +03:00
|
|
|
func createPaymentBucket(tx *bbolt.Tx, paymentHash lntypes.Hash) (
|
2019-05-23 21:05:26 +03:00
|
|
|
*bbolt.Bucket, error) {
|
|
|
|
|
|
|
|
payments, err := tx.CreateBucketIfNotExists(paymentsRootBucket)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return payments.CreateBucketIfNotExists(paymentHash[:])
|
|
|
|
}
|
|
|
|
|
2019-05-23 21:05:31 +03:00
|
|
|
// fetchPaymentBucket fetches the sub-bucket assigned to this payment hash. If
|
|
|
|
// the bucket does not exist, it returns ErrPaymentNotInitiated.
|
|
|
|
func fetchPaymentBucket(tx *bbolt.Tx, paymentHash lntypes.Hash) (
|
|
|
|
*bbolt.Bucket, error) {
|
|
|
|
|
|
|
|
payments := tx.Bucket(paymentsRootBucket)
|
|
|
|
if payments == nil {
|
|
|
|
return nil, ErrPaymentNotInitiated
|
|
|
|
}
|
|
|
|
|
|
|
|
bucket := payments.Bucket(paymentHash[:])
|
|
|
|
if bucket == nil {
|
|
|
|
return nil, ErrPaymentNotInitiated
|
|
|
|
}
|
|
|
|
|
|
|
|
return bucket, nil
|
|
|
|
}
|
|
|
|
|
2019-05-23 21:05:28 +03:00
|
|
|
// nextPaymentSequence returns the next sequence number to store for a new
|
|
|
|
// payment.
|
|
|
|
func nextPaymentSequence(tx *bbolt.Tx) ([]byte, error) {
|
|
|
|
payments, err := tx.CreateBucketIfNotExists(paymentsRootBucket)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
seq, err := payments.NextSequence()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
b := make([]byte, 8)
|
|
|
|
binary.BigEndian.PutUint64(b, seq)
|
|
|
|
return b, nil
|
|
|
|
}
|
|
|
|
|
2019-05-23 21:05:31 +03:00
|
|
|
// fetchPaymentStatus fetches the payment status of the payment. If the payment
|
|
|
|
// isn't found, it will default to "StatusUnknown".
|
2020-02-20 12:13:23 +03:00
|
|
|
func fetchPaymentStatus(bucket *bbolt.Bucket) (PaymentStatus, error) {
|
2020-02-20 20:08:01 +03:00
|
|
|
htlcsBucket := bucket.Bucket(paymentHtlcsBucket)
|
|
|
|
if htlcsBucket != nil {
|
|
|
|
htlcs, err := fetchHtlcAttempts(htlcsBucket)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Go through all HTLCs, and return StatusSucceeded if any of
|
|
|
|
// them did succeed.
|
|
|
|
for _, h := range htlcs {
|
|
|
|
if h.Settle != nil {
|
|
|
|
return StatusSucceeded, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-23 21:05:31 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if bucket.Get(paymentFailInfoKey) != nil {
|
2020-02-20 12:13:23 +03:00
|
|
|
return StatusFailed, nil
|
2019-05-23 21:05:31 +03:00
|
|
|
}
|
2019-05-23 21:05:26 +03:00
|
|
|
|
2019-05-23 21:05:31 +03:00
|
|
|
if bucket.Get(paymentCreationInfoKey) != nil {
|
2020-02-20 12:13:23 +03:00
|
|
|
return StatusInFlight, nil
|
2019-05-23 21:05:26 +03:00
|
|
|
}
|
|
|
|
|
2020-02-20 12:13:23 +03:00
|
|
|
return StatusUnknown, nil
|
2019-05-23 21:05:26 +03:00
|
|
|
}
|
2019-05-23 21:05:28 +03:00
|
|
|
|
|
|
|
// ensureInFlight checks whether the payment found in the given bucket has
|
|
|
|
// status InFlight, and returns an error otherwise. This should be used to
|
|
|
|
// ensure we only mark in-flight payments as succeeded or failed.
|
|
|
|
func ensureInFlight(bucket *bbolt.Bucket) error {
|
2020-02-20 12:13:23 +03:00
|
|
|
paymentStatus, err := fetchPaymentStatus(bucket)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-05-23 21:05:28 +03:00
|
|
|
|
|
|
|
switch {
|
|
|
|
|
|
|
|
// The payment was indeed InFlight, return.
|
|
|
|
case paymentStatus == StatusInFlight:
|
|
|
|
return nil
|
|
|
|
|
|
|
|
// Our records show the payment as unknown, meaning it never
|
|
|
|
// should have left the switch.
|
2019-05-23 21:05:31 +03:00
|
|
|
case paymentStatus == StatusUnknown:
|
2019-05-23 21:05:28 +03:00
|
|
|
return ErrPaymentNotInitiated
|
|
|
|
|
|
|
|
// The payment succeeded previously.
|
2019-05-23 21:05:31 +03:00
|
|
|
case paymentStatus == StatusSucceeded:
|
|
|
|
return ErrPaymentAlreadySucceeded
|
2019-05-23 21:05:28 +03:00
|
|
|
|
|
|
|
// The payment was already failed.
|
|
|
|
case paymentStatus == StatusFailed:
|
|
|
|
return ErrPaymentAlreadyFailed
|
|
|
|
|
|
|
|
default:
|
|
|
|
return ErrUnknownPaymentStatus
|
|
|
|
}
|
|
|
|
}
|
2019-05-23 21:05:29 +03:00
|
|
|
|
|
|
|
// InFlightPayment is a wrapper around a payment that has status InFlight.
|
|
|
|
type InFlightPayment struct {
|
|
|
|
// Info is the PaymentCreationInfo of the in-flight payment.
|
|
|
|
Info *PaymentCreationInfo
|
|
|
|
|
2020-02-20 20:08:01 +03:00
|
|
|
// Attempts is the set of payment attempts that was made to this
|
|
|
|
// payment hash.
|
2019-05-23 21:05:29 +03:00
|
|
|
//
|
2020-02-20 20:08:01 +03:00
|
|
|
// NOTE: Might be empty.
|
|
|
|
Attempts []HTLCAttemptInfo
|
2019-05-23 21:05:29 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// FetchInFlightPayments returns all payments with status InFlight.
|
2019-05-29 09:57:04 +03:00
|
|
|
func (p *PaymentControl) FetchInFlightPayments() ([]*InFlightPayment, error) {
|
2019-05-23 21:05:29 +03:00
|
|
|
var inFlights []*InFlightPayment
|
|
|
|
err := p.db.View(func(tx *bbolt.Tx) error {
|
|
|
|
payments := tx.Bucket(paymentsRootBucket)
|
|
|
|
if payments == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return payments.ForEach(func(k, _ []byte) error {
|
|
|
|
bucket := payments.Bucket(k)
|
|
|
|
if bucket == nil {
|
|
|
|
return fmt.Errorf("non bucket element")
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the status is not InFlight, we can 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
|
|
|
|
}
|
|
|
|
|
2020-02-20 12:13:23 +03:00
|
|
|
inFlight := &InFlightPayment{}
|
2019-05-23 21:05:29 +03:00
|
|
|
|
|
|
|
// Get the CreationInfo.
|
|
|
|
b := bucket.Get(paymentCreationInfoKey)
|
|
|
|
if b == nil {
|
|
|
|
return fmt.Errorf("unable to find creation " +
|
|
|
|
"info for inflight payment")
|
|
|
|
}
|
|
|
|
|
|
|
|
r := bytes.NewReader(b)
|
|
|
|
inFlight.Info, err = deserializePaymentCreationInfo(r)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-02-20 20:08:01 +03:00
|
|
|
htlcsBucket := bucket.Bucket(paymentHtlcsBucket)
|
|
|
|
if htlcsBucket == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fetch all HTLCs attempted for this payment.
|
|
|
|
htlcs, err := fetchHtlcAttempts(htlcsBucket)
|
|
|
|
if err != nil {
|
2019-04-30 14:24:37 +03:00
|
|
|
return err
|
2019-05-23 21:05:29 +03:00
|
|
|
}
|
|
|
|
|
2020-02-20 20:08:01 +03:00
|
|
|
// We only care about the static info for the HTLCs
|
|
|
|
// still in flight, so convert the result to a slice of
|
|
|
|
// HTLCAttemptInfos.
|
|
|
|
for _, h := range htlcs {
|
|
|
|
// Skip HTLCs not in flight.
|
|
|
|
if h.Settle != nil || h.Failure != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
inFlight.Attempts = append(
|
|
|
|
inFlight.Attempts, h.HTLCAttemptInfo,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2019-05-23 21:05:29 +03:00
|
|
|
inFlights = append(inFlights, inFlight)
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return inFlights, nil
|
|
|
|
}
|