staticaddr/deposit: ignore queued expiry in final states

A block notification can queue OnExpiry before a deposit reaches a final
state. If the final transition wins that race first, the stale expiry event
must not overwrite the terminal outcome.

Keep LoopedIn and Withdrawn as self-loops on OnExpiry, matching the other
final states. Add a focused FSM test that sends OnExpiry directly to each
final state and verifies the state is preserved.
This commit is contained in:
Slyghtning 2026-07-01 13:16:49 +02:00
parent d8b24d31c0
commit f468f24e6f
No known key found for this signature in database
GPG key ID: F82D456EA023C9BF
2 changed files with 49 additions and 2 deletions

View file

@ -393,7 +393,7 @@ func (f *FSM) DepositStatesV0() fsm.States {
},
LoopedIn: fsm.State{
Transitions: fsm.Transitions{
OnExpiry: Expired,
OnExpiry: LoopedIn,
},
Action: f.FinalizeDepositAction,
},
@ -412,7 +412,7 @@ func (f *FSM) DepositStatesV0() fsm.States {
},
Withdrawn: fsm.State{
Transitions: fsm.Transitions{
OnExpiry: Expired,
OnExpiry: Withdrawn,
OnWithdrawn: Withdrawn,
},
Action: f.FinalizeDepositAction,

View file

@ -71,6 +71,53 @@ func TestHandleBlockNotificationIgnoresFinalStates(t *testing.T) {
}
}
// TestFinalStatesIgnoreQueuedExpiry verifies that a queued OnExpiry event cannot
// overwrite a deposit that already reached a final state.
func TestFinalStatesIgnoreQueuedExpiry(t *testing.T) {
t.Parallel()
finalStates := []fsm.StateType{
Expired,
Withdrawn,
LoopedIn,
HtlcTimeoutSwept,
ChannelPublished,
}
for i, state := range finalStates {
t.Run(string(state), func(t *testing.T) {
t.Parallel()
outpoint := wire.OutPoint{
Hash: chainhash.Hash{byte(i + 1)},
Index: uint32(i),
}
deposit := &Deposit{
OutPoint: outpoint,
}
deposit.SetState(state)
depositFSM := &FSM{
cfg: &ManagerConfig{
Store: new(mockStore),
},
deposit: deposit,
quitChan: make(chan struct{}),
finalizedDepositChan: make(chan wire.OutPoint, 1),
}
depositFSM.StateMachine = fsm.NewStateMachineWithState(
depositFSM.DepositStatesV0(), state,
DefaultObserverSize,
)
depositFSM.ActionEntryFunc = depositFSM.updateDeposit
err := depositFSM.SendEvent(t.Context(), OnExpiry, nil)
require.NoError(t, err)
require.Equal(t, state, deposit.GetState())
})
}
}
// TestLoopingInTransitionsToSweepHtlcTimeout verifies that a deposit selected
// by a loop-in can be moved into the timeout sweep state if the server confirms
// the HTLC without paying the invoice.