htlcswitch/switch_control: expand godocs and add ErrUnknownPaymentStatus

This commit is contained in:
Conner Fromknecht 2018-08-10 14:00:50 -07:00
parent 9b52c510e9
commit 875128539c
No known key found for this signature in database
GPG Key ID: E7D737B67FA592C7

@ -9,36 +9,53 @@ import (
) )
var ( var (
// ErrAlreadyPaid is used when we have already paid // ErrAlreadyPaid signals we have already paid this payment hash.
ErrAlreadyPaid = errors.New("invoice was already paid") ErrAlreadyPaid = errors.New("invoice is already paid")
// ErrPaymentInFlight returns in case if payment is already "in flight" // ErrPaymentInFlight signals that payment for this payment hash is
// already "in flight" on the network.
ErrPaymentInFlight = errors.New("payment is in transition") ErrPaymentInFlight = errors.New("payment is in transition")
// ErrPaymentNotInitiated returns in case if payment wasn't initiated // ErrPaymentNotInitiated is returned if payment wasn't initiated in
// in switch // switch.
ErrPaymentNotInitiated = errors.New("payment isn't initiated") ErrPaymentNotInitiated = errors.New("payment isn't initiated")
// ErrPaymentAlreadyCompleted returns in case of attempt to complete // ErrPaymentAlreadyCompleted is returned in the event we attempt to
// completed payment // recomplete a completed payment.
ErrPaymentAlreadyCompleted = errors.New("payment is already completed") ErrPaymentAlreadyCompleted = errors.New("payment is already completed")
// ErrUnknownPaymentStatus is returned when we do not recognize the
// existing state of a payment.
ErrUnknownPaymentStatus = errors.New("unknown payment status")
) )
// ControlTower is a controller interface of sending HTLC messages to switch // ControlTower tracks all outgoing payments made by the switch, whose primary
// purpose is to prevent duplicate payments to the same payment hash. In
// production, a persistent implementation is preferred so that tracking can
// survive across restarts. Payments are transition through various payment
// states, and the ControlTower interface provides access to driving the state
// transitions.
type ControlTower interface { type ControlTower interface {
// CheckSend intercepts incoming message to provide checks // ClearForTakeoff atomically checks that no inflight or completed
// and fail if specific message is not allowed by implementation // payments exist for this payment hash. If none are found, this method
CheckSend(htlc *lnwire.UpdateAddHTLC) error // atomically transitions the status for this payment hash as InFlight.
ClearForTakeoff(htlc *lnwire.UpdateAddHTLC) error
// Success marks message transition as successful // Success transitions an InFlight payment into a Completed payment.
// After invoking this method, ClearForTakeoff should always return an
// error to prevent us from making duplicate payments to the same
// payment hash.
Success(paymentHash [32]byte) error Success(paymentHash [32]byte) error
// Fail marks message transition as failed // Fail transitions an InFlight payment into a Grounded Payment. After
// invoking this method, ClearForTakeoff should return nil on its next
// call for this payment hash, allowing the switch to make a subsequent
// payment.
Fail(paymentHash [32]byte) error Fail(paymentHash [32]byte) error
} }
// paymentControl is implementation of ControlTower to restrict double payment // paymentControl is persistent implementation of ControlTower to restrict
// sending. // double payment sending.
type paymentControl struct { type paymentControl struct {
mx sync.Mutex mx sync.Mutex
@ -52,9 +69,9 @@ func NewPaymentControl(db *channeldb.DB) ControlTower {
} }
} }
// CheckSend checks that a sending htlc wasn't triggered before for specific // ClearForTakeoff checks that we don't already have an InFlight or Completed
// payment hash, if so, should trigger error depends on current status // payment identified by the same payment hash.
func (p *paymentControl) CheckSend(htlc *lnwire.UpdateAddHTLC) error { func (p *paymentControl) ClearForTakeoff(htlc *lnwire.UpdateAddHTLC) error {
p.mx.Lock() p.mx.Lock()
defer p.mx.Unlock() defer p.mx.Unlock()
@ -65,27 +82,31 @@ func (p *paymentControl) CheckSend(htlc *lnwire.UpdateAddHTLC) error {
} }
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 haven't
// left one in flight prior to restarting and switch. // left one in flight. Since this one is grounded, Transition
return p.db.UpdatePaymentStatus(htlc.PaymentHash, // the payment status to InFlight to prevent others.
channeldb.StatusInFlight) return p.db.UpdatePaymentStatus(htlc.PaymentHash, channeldb.StatusInFlight)
case channeldb.StatusInFlight: case channeldb.StatusInFlight:
// Not clear if it's safe to reinitiate a payment if there // We already have an InFlight payment on the network. We will
// is already a payment in flight, so we should withhold any // disallow any more payment until a response is received.
// additional attempts to send to that payment hash.
return ErrPaymentInFlight return ErrPaymentInFlight
case channeldb.StatusCompleted: case channeldb.StatusCompleted:
// It has been already paid and don't want to pay again. // We've already completed a payment to this payment hash,
// forbid the switch from sending another.
return ErrAlreadyPaid return ErrAlreadyPaid
}
return nil default:
return ErrUnknownPaymentStatus
}
} }
// Success proceed status changing of payment to next successful status // Success transitions an InFlight payment to Completed, otherwise it returns an
// error. After calling Success, ClearForTakeoff should prevent any further
// 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() p.mx.Lock()
defer p.mx.Unlock() defer p.mx.Unlock()
@ -97,22 +118,29 @@ func (p *paymentControl) Success(paymentHash [32]byte) error {
switch paymentStatus { switch paymentStatus {
case channeldb.StatusGrounded: case channeldb.StatusGrounded:
// Payment isn't initiated but received. // Our records show the payment as still being grounded, meaning
// it never should have left the switch.
return ErrPaymentNotInitiated return ErrPaymentNotInitiated
case channeldb.StatusInFlight: case channeldb.StatusInFlight:
// Successful transition from InFlight transition to Completed. // A successful response was received for an InFlight payment,
// mark it as completed to prevent sending to this payment hash
// again.
return p.db.UpdatePaymentStatus(paymentHash, channeldb.StatusCompleted) return p.db.UpdatePaymentStatus(paymentHash, channeldb.StatusCompleted)
case channeldb.StatusCompleted: case channeldb.StatusCompleted:
// Payment is completed before in should be ignored. // The payment was completed previously, alert the caller that
// this may be a duplicate call.
return ErrPaymentAlreadyCompleted return ErrPaymentAlreadyCompleted
}
return nil default:
return ErrUnknownPaymentStatus
}
} }
// Fail proceed status changing of payment to initial status in case of failure // Fail transitions an InFlight payment to Grounded, otherwise it returns an
// error. After calling Fail, ClearForTakeoff should fail any further attempts
// for the same payment hash.
func (p *paymentControl) Fail(paymentHash [32]byte) error { func (p *paymentControl) Fail(paymentHash [32]byte) error {
p.mx.Lock() p.mx.Lock()
defer p.mx.Unlock() defer p.mx.Unlock()
@ -124,19 +152,22 @@ func (p *paymentControl) Fail(paymentHash [32]byte) error {
switch paymentStatus { switch paymentStatus {
case channeldb.StatusGrounded: case channeldb.StatusGrounded:
// Unpredictable behavior when payment wasn't transited to // Our records show the payment as still being grounded, meaning
// StatusInFlight status and was failed. // it never should have left the switch.
return ErrPaymentNotInitiated return ErrPaymentNotInitiated
case channeldb.StatusInFlight: case channeldb.StatusInFlight:
// If payment wasn't processed by some reason should return to // A failed response was received for an InFlight payment, mark
// default status to unlock retrying option for the same payment hash. // it as Grounded again to allow subsequent attempts.
return p.db.UpdatePaymentStatus(paymentHash, channeldb.StatusGrounded) return p.db.UpdatePaymentStatus(paymentHash, channeldb.StatusGrounded)
case channeldb.StatusCompleted: case channeldb.StatusCompleted:
// Payment is completed before and can't be moved to another status. // The payment was completed previously, and we are now
// reporting that it has failed. Leave the status as completed,
// but alert the user that something is wrong.
return ErrPaymentAlreadyCompleted return ErrPaymentAlreadyCompleted
}
return nil default:
return ErrUnknownPaymentStatus
}
} }