routing/test: add test to demonstrate stuck payment, single shard
This commit adds a test which demonstrates that payments can get stuck if we receive a payment failure while we're pathfinding for another shard, then try to dispatch a shard after we've recorded a permanent failure. It also updates our mock to only consider payments with no in-flight htlcs as in-flight, to more closely represent our actual RegisterAttempt.
This commit is contained in:
parent
125980afb7
commit
80451afe48
@ -302,20 +302,38 @@ func (m *mockControlTower) RegisterAttempt(phash lntypes.Hash,
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
// Cannot register attempts for successful or failed payments.
|
||||
if _, ok := m.successful[phash]; ok {
|
||||
return channeldb.ErrPaymentAlreadySucceeded
|
||||
}
|
||||
|
||||
if _, ok := m.failed[phash]; ok {
|
||||
return channeldb.ErrPaymentAlreadyFailed
|
||||
}
|
||||
|
||||
// Lookup payment.
|
||||
p, ok := m.payments[phash]
|
||||
if !ok {
|
||||
return channeldb.ErrPaymentNotInitiated
|
||||
}
|
||||
|
||||
var inFlight bool
|
||||
for _, a := range p.attempts {
|
||||
if a.Settle != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if a.Failure != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
inFlight = true
|
||||
}
|
||||
|
||||
// Cannot register attempts for successful or failed payments.
|
||||
_, settled := m.successful[phash]
|
||||
_, failed := m.failed[phash]
|
||||
|
||||
if settled && !inFlight {
|
||||
return channeldb.ErrPaymentAlreadySucceeded
|
||||
}
|
||||
|
||||
if failed && !inFlight {
|
||||
return channeldb.ErrPaymentAlreadyFailed
|
||||
}
|
||||
|
||||
// Add attempt to payment.
|
||||
p.attempts = append(p.attempts, channeldb.HTLCAttempt{
|
||||
HTLCAttemptInfo: *a,
|
||||
})
|
||||
|
@ -615,6 +615,41 @@ func TestRouterPaymentStateMachine(t *testing.T) {
|
||||
},
|
||||
paymentErr: channeldb.FailureReasonPaymentDetails,
|
||||
},
|
||||
{
|
||||
// A MP payment scenario when our path finding returns
|
||||
// after we've just received a terminal failure, and
|
||||
// demonstrates a bug where the payment will return with
|
||||
// and "unexpected" ErrPaymentAlreadyFailed rather than
|
||||
// failing with a permanent error. This results in the
|
||||
// payment getting stuck.
|
||||
name: "MP path found after failure",
|
||||
|
||||
steps: []string{
|
||||
routerInitPayment,
|
||||
|
||||
// shard 0
|
||||
routeRelease,
|
||||
routerRegisterAttempt,
|
||||
sendToSwitchSuccess,
|
||||
|
||||
// The first shard fail with a terminal error.
|
||||
getPaymentResultTerminalFailure,
|
||||
routerFailAttempt,
|
||||
routerFailPayment,
|
||||
|
||||
// shard 1 fails because we've had a terminal
|
||||
// failure.
|
||||
routeRelease,
|
||||
routerRegisterAttempt,
|
||||
|
||||
// Payment fails.
|
||||
paymentError,
|
||||
},
|
||||
routes: []*route.Route{
|
||||
shard, shard,
|
||||
},
|
||||
paymentErr: channeldb.ErrPaymentAlreadyFailed,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
Loading…
Reference in New Issue
Block a user