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()
|
m.Lock()
|
||||||
defer m.Unlock()
|
defer m.Unlock()
|
||||||
|
|
||||||
// Cannot register attempts for successful or failed payments.
|
// Lookup payment.
|
||||||
if _, ok := m.successful[phash]; ok {
|
|
||||||
return channeldb.ErrPaymentAlreadySucceeded
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := m.failed[phash]; ok {
|
|
||||||
return channeldb.ErrPaymentAlreadyFailed
|
|
||||||
}
|
|
||||||
|
|
||||||
p, ok := m.payments[phash]
|
p, ok := m.payments[phash]
|
||||||
if !ok {
|
if !ok {
|
||||||
return channeldb.ErrPaymentNotInitiated
|
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{
|
p.attempts = append(p.attempts, channeldb.HTLCAttempt{
|
||||||
HTLCAttemptInfo: *a,
|
HTLCAttemptInfo: *a,
|
||||||
})
|
})
|
||||||
|
@ -615,6 +615,41 @@ func TestRouterPaymentStateMachine(t *testing.T) {
|
|||||||
},
|
},
|
||||||
paymentErr: channeldb.FailureReasonPaymentDetails,
|
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 {
|
for _, test := range tests {
|
||||||
|
Loading…
Reference in New Issue
Block a user