mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 18:10:34 +01:00
htlcswitch/control_tower: use one db txn for transitions
Composes the new payment status helper methods such that we only require one db txn per state transition. This also allows us to remove the exclusive lock from the control tower, and enable more concurrent requests.
This commit is contained in:
parent
86b347c996
commit
5dc2a4a4b8
@ -2,8 +2,8 @@ package htlcswitch
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"sync"
|
|
||||||
|
|
||||||
|
"github.com/coreos/bbolt"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
)
|
)
|
||||||
@ -59,11 +59,17 @@ type ControlTower interface {
|
|||||||
type paymentControl struct {
|
type paymentControl struct {
|
||||||
strict bool
|
strict bool
|
||||||
|
|
||||||
mx sync.Mutex
|
|
||||||
db *channeldb.DB
|
db *channeldb.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPaymentControl creates a new instance of the paymentControl.
|
// NewPaymentControl creates a new instance of the paymentControl. The strict
|
||||||
|
// flag indicates whether the controller should require "strict" state
|
||||||
|
// transitions, which would be otherwise intolerant to older databases that may
|
||||||
|
// already have duplicate payments to the same payment hash. It should be
|
||||||
|
// enabled only after sufficient checks have been made to ensure the db does not
|
||||||
|
// contain such payments. In the meantime, non-strict mode enforces a superset
|
||||||
|
// of the state transitions that prevent additional payments to a given payment
|
||||||
|
// hash from being added.
|
||||||
func NewPaymentControl(strict bool, db *channeldb.DB) ControlTower {
|
func NewPaymentControl(strict bool, db *channeldb.DB) ControlTower {
|
||||||
return &paymentControl{
|
return &paymentControl{
|
||||||
strict: strict,
|
strict: strict,
|
||||||
@ -74,114 +80,166 @@ func NewPaymentControl(strict bool, db *channeldb.DB) ControlTower {
|
|||||||
// ClearForTakeoff checks that we don't already have an InFlight or Completed
|
// ClearForTakeoff checks that we don't already have an InFlight or Completed
|
||||||
// payment identified by the same payment hash.
|
// payment identified by the same payment hash.
|
||||||
func (p *paymentControl) ClearForTakeoff(htlc *lnwire.UpdateAddHTLC) error {
|
func (p *paymentControl) ClearForTakeoff(htlc *lnwire.UpdateAddHTLC) error {
|
||||||
p.mx.Lock()
|
var takeoffErr error
|
||||||
defer p.mx.Unlock()
|
err := p.db.Batch(func(tx *bolt.Tx) error {
|
||||||
|
|
||||||
// Retrieve current status of payment from local database.
|
// Retrieve current status of payment from local database.
|
||||||
paymentStatus, err := p.db.FetchPaymentStatus(htlc.PaymentHash)
|
paymentStatus, err := channeldb.FetchPaymentStatusTx(
|
||||||
|
tx, htlc.PaymentHash,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset the takeoff error, to avoid carrying over an error
|
||||||
|
// from a previous execution of the batched db transaction.
|
||||||
|
takeoffErr = nil
|
||||||
|
|
||||||
switch paymentStatus {
|
switch paymentStatus {
|
||||||
|
|
||||||
case channeldb.StatusGrounded:
|
case channeldb.StatusGrounded:
|
||||||
// It is safe to reattempt a payment if we know that we haven't
|
// It is safe to reattempt a payment if we know that we
|
||||||
// left one in flight. Since this one is grounded, Transition
|
// haven't left one in flight. Since this one is
|
||||||
// the payment status to InFlight to prevent others.
|
// grounded, Transition the payment status to InFlight
|
||||||
return p.db.UpdatePaymentStatus(htlc.PaymentHash, channeldb.StatusInFlight)
|
// to prevent others.
|
||||||
|
return channeldb.UpdatePaymentStatusTx(
|
||||||
|
tx, htlc.PaymentHash, channeldb.StatusInFlight,
|
||||||
|
)
|
||||||
|
|
||||||
case channeldb.StatusInFlight:
|
case channeldb.StatusInFlight:
|
||||||
// We already have an InFlight payment on the network. We will
|
// We already have an InFlight payment on the network. We will
|
||||||
// disallow any more payment until a response is received.
|
// disallow any more payment until a response is received.
|
||||||
return ErrPaymentInFlight
|
takeoffErr = ErrPaymentInFlight
|
||||||
|
|
||||||
case channeldb.StatusCompleted:
|
case channeldb.StatusCompleted:
|
||||||
// We've already completed a payment to this payment hash,
|
// We've already completed a payment to this payment hash,
|
||||||
// forbid the switch from sending another.
|
// forbid the switch from sending another.
|
||||||
return ErrAlreadyPaid
|
takeoffErr = ErrAlreadyPaid
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return ErrUnknownPaymentStatus
|
takeoffErr = ErrUnknownPaymentStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return takeoffErr
|
||||||
}
|
}
|
||||||
|
|
||||||
// Success transitions an InFlight payment to Completed, otherwise it returns an
|
// Success transitions an InFlight payment to Completed, otherwise it returns an
|
||||||
// error. After calling Success, ClearForTakeoff should prevent any further
|
// error. After calling Success, ClearForTakeoff should prevent any further
|
||||||
// attempts for the same payment hash.
|
// attempts for the same payment hash.
|
||||||
func (p *paymentControl) Success(paymentHash [32]byte) error {
|
func (p *paymentControl) Success(paymentHash [32]byte) error {
|
||||||
p.mx.Lock()
|
var updateErr error
|
||||||
defer p.mx.Unlock()
|
err := p.db.Batch(func(tx *bolt.Tx) error {
|
||||||
|
paymentStatus, err := channeldb.FetchPaymentStatusTx(
|
||||||
paymentStatus, err := p.db.FetchPaymentStatus(paymentHash)
|
tx, paymentHash,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset the update error, to avoid carrying over an error
|
||||||
|
// from a previous execution of the batched db transaction.
|
||||||
|
updateErr = nil
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
|
|
||||||
case paymentStatus == channeldb.StatusGrounded && p.strict:
|
case paymentStatus == channeldb.StatusGrounded && p.strict:
|
||||||
// Our records show the payment as still being grounded, meaning
|
// Our records show the payment as still being grounded,
|
||||||
// it never should have left the switch.
|
// meaning it never should have left the switch.
|
||||||
return ErrPaymentNotInitiated
|
updateErr = ErrPaymentNotInitiated
|
||||||
|
|
||||||
case paymentStatus == channeldb.StatusGrounded && !p.strict:
|
case paymentStatus == channeldb.StatusGrounded && !p.strict:
|
||||||
// Our records show the payment as still being grounded, meaning
|
// Though our records show the payment as still being
|
||||||
// it never should have left the switch.
|
// grounded, meaning it never should have left the
|
||||||
|
// switch, we permit this transition in non-strict mode
|
||||||
|
// to handle inconsistent db states.
|
||||||
fallthrough
|
fallthrough
|
||||||
|
|
||||||
case paymentStatus == channeldb.StatusInFlight:
|
case paymentStatus == channeldb.StatusInFlight:
|
||||||
// A successful response was received for an InFlight payment,
|
// A successful response was received for an InFlight
|
||||||
// mark it as completed to prevent sending to this payment hash
|
// payment, mark it as completed to prevent sending to
|
||||||
// again.
|
// this payment hash again.
|
||||||
return p.db.UpdatePaymentStatus(paymentHash, channeldb.StatusCompleted)
|
return channeldb.UpdatePaymentStatusTx(
|
||||||
|
tx, paymentHash, channeldb.StatusCompleted,
|
||||||
|
)
|
||||||
|
|
||||||
case paymentStatus == channeldb.StatusCompleted:
|
case paymentStatus == channeldb.StatusCompleted:
|
||||||
// The payment was completed previously, alert the caller that
|
// The payment was completed previously, alert the
|
||||||
// this may be a duplicate call.
|
// caller that this may be a duplicate call.
|
||||||
return ErrPaymentAlreadyCompleted
|
updateErr = ErrPaymentAlreadyCompleted
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return ErrUnknownPaymentStatus
|
updateErr = ErrUnknownPaymentStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return updateErr
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fail transitions an InFlight payment to Grounded, otherwise it returns an
|
// Fail transitions an InFlight payment to Grounded, otherwise it returns an
|
||||||
// error. After calling Fail, ClearForTakeoff should fail any further attempts
|
// error. After calling Fail, ClearForTakeoff should fail any further attempts
|
||||||
// for the same payment hash.
|
// for the same payment hash.
|
||||||
func (p *paymentControl) Fail(paymentHash [32]byte) error {
|
func (p *paymentControl) Fail(paymentHash [32]byte) error {
|
||||||
p.mx.Lock()
|
var updateErr error
|
||||||
defer p.mx.Unlock()
|
err := p.db.Batch(func(tx *bolt.Tx) error {
|
||||||
|
paymentStatus, err := channeldb.FetchPaymentStatusTx(
|
||||||
paymentStatus, err := p.db.FetchPaymentStatus(paymentHash)
|
tx, paymentHash,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset the update error, to avoid carrying over an error
|
||||||
|
// from a previous execution of the batched db transaction.
|
||||||
|
updateErr = nil
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
|
|
||||||
case paymentStatus == channeldb.StatusGrounded && p.strict:
|
case paymentStatus == channeldb.StatusGrounded && p.strict:
|
||||||
// Our records show the payment as still being grounded, meaning
|
// Our records show the payment as still being grounded,
|
||||||
// it never should have left the switch.
|
// meaning it never should have left the switch.
|
||||||
return ErrPaymentNotInitiated
|
updateErr = ErrPaymentNotInitiated
|
||||||
|
|
||||||
case paymentStatus == channeldb.StatusGrounded && !p.strict:
|
case paymentStatus == channeldb.StatusGrounded && !p.strict:
|
||||||
// Our records show the payment as still being grounded, meaning
|
// Though our records show the payment as still being
|
||||||
// it never should have left the switch.
|
// grounded, meaning it never should have left the
|
||||||
|
// switch, we permit this transition in non-strict mode
|
||||||
|
// to handle inconsistent db states.
|
||||||
fallthrough
|
fallthrough
|
||||||
|
|
||||||
case paymentStatus == channeldb.StatusInFlight:
|
case paymentStatus == channeldb.StatusInFlight:
|
||||||
// A failed response was received for an InFlight payment, mark
|
// A failed response was received for an InFlight
|
||||||
// it as Grounded again to allow subsequent attempts.
|
// payment, mark it as Grounded again to allow
|
||||||
return p.db.UpdatePaymentStatus(paymentHash, channeldb.StatusGrounded)
|
// subsequent attempts.
|
||||||
|
return channeldb.UpdatePaymentStatusTx(
|
||||||
|
tx, paymentHash, channeldb.StatusGrounded,
|
||||||
|
)
|
||||||
|
|
||||||
case paymentStatus == channeldb.StatusCompleted:
|
case paymentStatus == channeldb.StatusCompleted:
|
||||||
// The payment was completed previously, and we are now
|
// The payment was completed previously, and we are now
|
||||||
// reporting that it has failed. Leave the status as completed,
|
// reporting that it has failed. Leave the status as
|
||||||
// but alert the user that something is wrong.
|
// completed, but alert the user that something is
|
||||||
return ErrPaymentAlreadyCompleted
|
// wrong.
|
||||||
|
updateErr = ErrPaymentAlreadyCompleted
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return ErrUnknownPaymentStatus
|
updateErr = ErrUnknownPaymentStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return updateErr
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user