htlcswitch: control tower implementation
This commit is contained in:
parent
41e31e0909
commit
a3d32be808
142
htlcswitch/switch_control.go
Normal file
142
htlcswitch/switch_control.go
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
package htlcswitch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrAlreadyPaid is used when we have already paid
|
||||||
|
ErrAlreadyPaid = errors.New("invoice was already paid")
|
||||||
|
|
||||||
|
// ErrPaymentInFlight returns in case if payment is already "in flight"
|
||||||
|
ErrPaymentInFlight = errors.New("payment is in transition")
|
||||||
|
|
||||||
|
// ErrPaymentNotInitiated returns in case if payment wasn't initiated
|
||||||
|
// in switch
|
||||||
|
ErrPaymentNotInitiated = errors.New("payment isn't initiated")
|
||||||
|
|
||||||
|
// ErrPaymentAlreadyCompleted returns in case of attempt to complete
|
||||||
|
// completed payment
|
||||||
|
ErrPaymentAlreadyCompleted = errors.New("payment is already completed")
|
||||||
|
)
|
||||||
|
|
||||||
|
// ControlTower is a controller interface of sending HTLC messages to switch
|
||||||
|
type ControlTower interface {
|
||||||
|
// CheckSend intercepts incoming message to provide checks
|
||||||
|
// and fail if specific message is not allowed by implementation
|
||||||
|
CheckSend(htlc *lnwire.UpdateAddHTLC) error
|
||||||
|
|
||||||
|
// Success marks message transition as successful
|
||||||
|
Success(paymentHash [32]byte) error
|
||||||
|
|
||||||
|
// Fail marks message transition as failed
|
||||||
|
Fail(paymentHash [32]byte) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// paymentControl is implementation of ControlTower to restrict double payment
|
||||||
|
// sending.
|
||||||
|
type paymentControl struct {
|
||||||
|
mx sync.Mutex
|
||||||
|
|
||||||
|
db *channeldb.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPaymentControl creates a new instance of the paymentControl.
|
||||||
|
func NewPaymentControl(db *channeldb.DB) ControlTower {
|
||||||
|
return &paymentControl{
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckSend checks that a sending htlc wasn't triggered before for specific
|
||||||
|
// payment hash, if so, should trigger error depends on current status
|
||||||
|
func (p *paymentControl) CheckSend(htlc *lnwire.UpdateAddHTLC) error {
|
||||||
|
p.mx.Lock()
|
||||||
|
defer p.mx.Unlock()
|
||||||
|
|
||||||
|
// Retrieve current status of payment from local database.
|
||||||
|
paymentStatus, err := p.db.FetchPaymentStatus(htlc.PaymentHash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch paymentStatus {
|
||||||
|
case channeldb.StatusGrounded:
|
||||||
|
// It is safe to reattempt a payment if we know that we haven't
|
||||||
|
// left one in flight prior to restarting and switch.
|
||||||
|
return p.db.UpdatePaymentStatus(htlc.PaymentHash,
|
||||||
|
channeldb.StatusInFlight)
|
||||||
|
|
||||||
|
case channeldb.StatusInFlight:
|
||||||
|
// Not clear if it's safe to reinitiate a payment if there
|
||||||
|
// is already a payment in flight, so we should withhold any
|
||||||
|
// additional attempts to send to that payment hash.
|
||||||
|
return ErrPaymentInFlight
|
||||||
|
|
||||||
|
case channeldb.StatusCompleted:
|
||||||
|
// It has been already paid and don't want to pay again.
|
||||||
|
return ErrAlreadyPaid
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success proceed status changing of payment to next successful status
|
||||||
|
func (p *paymentControl) Success(paymentHash [32]byte) error {
|
||||||
|
p.mx.Lock()
|
||||||
|
defer p.mx.Unlock()
|
||||||
|
|
||||||
|
paymentStatus, err := p.db.FetchPaymentStatus(paymentHash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch paymentStatus {
|
||||||
|
case channeldb.StatusGrounded:
|
||||||
|
// Payment isn't initiated but received.
|
||||||
|
return ErrPaymentNotInitiated
|
||||||
|
|
||||||
|
case channeldb.StatusInFlight:
|
||||||
|
// Successful transition from InFlight transition to Completed.
|
||||||
|
return p.db.UpdatePaymentStatus(paymentHash, channeldb.StatusCompleted)
|
||||||
|
|
||||||
|
case channeldb.StatusCompleted:
|
||||||
|
// Payment is completed before in should be ignored.
|
||||||
|
return ErrPaymentAlreadyCompleted
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fail proceed status changing of payment to initial status in case of failure
|
||||||
|
func (p *paymentControl) Fail(paymentHash [32]byte) error {
|
||||||
|
p.mx.Lock()
|
||||||
|
defer p.mx.Unlock()
|
||||||
|
|
||||||
|
paymentStatus, err := p.db.FetchPaymentStatus(paymentHash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch paymentStatus {
|
||||||
|
case channeldb.StatusGrounded:
|
||||||
|
// Unpredictable behavior when payment wasn't transited to
|
||||||
|
// StatusInFlight status and was failed.
|
||||||
|
return ErrPaymentNotInitiated
|
||||||
|
|
||||||
|
case channeldb.StatusInFlight:
|
||||||
|
// If payment wasn't processed by some reason should return to
|
||||||
|
// default status to unlock retrying option for the same payment hash.
|
||||||
|
return p.db.UpdatePaymentStatus(paymentHash, channeldb.StatusGrounded)
|
||||||
|
|
||||||
|
case channeldb.StatusCompleted:
|
||||||
|
// Payment is completed before and can't be moved to another status.
|
||||||
|
return ErrPaymentAlreadyCompleted
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user