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:
carla 2021-04-23 08:39:44 +02:00
parent 125980afb7
commit 80451afe48
No known key found for this signature in database
GPG Key ID: 4CA7FE54A6213C91
2 changed files with 62 additions and 9 deletions

@ -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 {