diff --git a/staticaddr/loopin/manager.go b/staticaddr/loopin/manager.go index b1074195..9914c2d5 100644 --- a/staticaddr/loopin/manager.go +++ b/staticaddr/loopin/manager.go @@ -533,17 +533,15 @@ func (m *Manager) recoverLoopIns(ctx context.Context) error { for _, loopIn := range pendingLoopIns { log.Debugf("Recovering loopIn %x", loopIn.SwapHash[:]) - // Retrieve all deposits regardless of deposit state. If any of - // the deposits is not active in the in-mem map of the deposits - // manager we log it, but continue to recover the loop-in. - var allActive bool - loopIn.Deposits, allActive = - m.cfg.DepositManager.AllStringOutpointsActiveDeposits( - loopIn.DepositOutpoints, fsm.EmptyState, - ) - + // Retrieve all deposits regardless of deposit state. If all + // deposits are active in the in-mem map of the deposits manager, + // use those active instances. Otherwise, keep the store's + // swap_hash/deposit-id reconstruction and continue recovery. + activeDeposits, allActive := m.activeDepositsForLoopIn(loopIn) if !allActive { log.Errorf("one or more deposits are not active") + } else { + loopIn.Deposits = activeDeposits } loopIn.AddressParams, err = @@ -818,35 +816,29 @@ func (m *Manager) startLoopInFsm(ctx context.Context, func (m *Manager) GetAllSwaps(ctx context.Context) ([]*StaticAddressLoopIn, error) { - swaps, err := m.cfg.Store.GetStaticAddressLoopInSwapsByStates( + return m.cfg.Store.GetStaticAddressLoopInSwapsByStates( ctx, AllStates, ) - if err != nil { - return nil, err - } +} - allDeposits, err := m.cfg.DepositManager.GetAllDeposits(ctx) - if err != nil { - return nil, err - } +// activeDepositsForLoopIn returns the active deposit instances for a loop-in +// using the current deposit outpoints reconstructed by the store. The stored +// deposit outpoint snapshots remain the original swap inputs and are not the +// source of truth for current deposit rows. +func (m *Manager) activeDepositsForLoopIn(loopIn *StaticAddressLoopIn) ( + []*deposit.Deposit, bool) { - var depositLookup = make(map[string]*deposit.Deposit) - for i, d := range allDeposits { - depositLookup[d.OutPoint.String()] = allDeposits[i] - } - - for i, s := range swaps { - var deposits []*deposit.Deposit - for _, outpoint := range s.DepositOutpoints { - if d, ok := depositLookup[outpoint]; ok { - deposits = append(deposits, d) - } + outpoints := loopIn.DepositOutpoints + if len(loopIn.Deposits) > 0 { + outpoints = make([]string, 0, len(loopIn.Deposits)) + for _, d := range loopIn.Deposits { + outpoints = append(outpoints, d.OutPoint.String()) } - - swaps[i].Deposits = deposits } - return swaps, nil + return m.cfg.DepositManager.AllStringOutpointsActiveDeposits( + outpoints, fsm.EmptyState, + ) } // SelectDeposits sorts the deposits by amount in descending order, then by diff --git a/staticaddr/loopin/manager_test.go b/staticaddr/loopin/manager_test.go index 85178a05..ed31df8a 100644 --- a/staticaddr/loopin/manager_test.go +++ b/staticaddr/loopin/manager_test.go @@ -298,6 +298,65 @@ func TestHandleLoopInSweepReqRejectsInvalidServerNonce(t *testing.T) { require.ErrorContains(t, err, depOutpoint) } +// TestActiveDepositsForLoopInUsesCurrentDepositOutpoints verifies that +// recovery checks the current deposit outpoints reconstructed by the store +// rather than the original outpoint snapshot persisted on the swap. +func TestActiveDepositsForLoopInUsesCurrentDepositOutpoints(t *testing.T) { + oldOutpoint := wire.OutPoint{ + Hash: chainhash.Hash{0xaa}, + Index: 0, + } + currentDeposit := makeDeposit(0xbb, 1, 10_000, 42) + + manager := &Manager{ + cfg: &Config{ + DepositManager: &mockDepositManager{ + byOutpoint: map[string]*deposit.Deposit{ + currentDeposit.OutPoint.String(): currentDeposit, + }, + }, + }, + } + + deposits, allActive := manager.activeDepositsForLoopIn( + &StaticAddressLoopIn{ + DepositOutpoints: []string{oldOutpoint.String()}, + Deposits: []*deposit.Deposit{currentDeposit}, + }, + ) + require.True(t, allActive) + require.Equal(t, []*deposit.Deposit{currentDeposit}, deposits) +} + +// TestGetAllSwapsPreservesStoreDeposits verifies that list responses keep the +// store's swap_hash/deposit-id reconstruction even when DepositOutpoints is an +// original input snapshot and the deposit's current outpoint has changed. +func TestGetAllSwapsPreservesStoreDeposits(t *testing.T) { + oldOutpoint := wire.OutPoint{ + Hash: chainhash.Hash{0xcc}, + Index: 0, + } + currentDeposit := makeDeposit(0xdd, 1, 10_000, 42) + swap := &StaticAddressLoopIn{ + DepositOutpoints: []string{oldOutpoint.String()}, + Deposits: []*deposit.Deposit{currentDeposit}, + } + + manager := &Manager{ + cfg: &Config{ + Store: &mockStore{ + swaps: []*StaticAddressLoopIn{swap}, + }, + }, + } + + swaps, err := manager.GetAllSwaps(t.Context()) + require.NoError(t, err) + require.Len(t, swaps, 1) + require.Equal(t, []string{oldOutpoint.String()}, swaps[0].DepositOutpoints) + require.Equal(t, []*deposit.Deposit{currentDeposit}, swaps[0].Deposits) +} + // mockDepositManager implements DepositManager for tests. type mockDepositManager struct { // activeDeposits is the set returned by GetActiveDepositsInState. @@ -316,7 +375,7 @@ func (m *mockDepositManager) GetAllDeposits(_ context.Context) ( func (m *mockDepositManager) AllStringOutpointsActiveDeposits(outpoints []string, state fsm.StateType) ([]*deposit.Deposit, bool) { - if state != deposit.Deposited { + if state != deposit.Deposited && state != fsm.EmptyState { return nil, false } @@ -408,6 +467,7 @@ func (m *mockQuoteGetter) GetLoopInQuote(_ context.Context, // mockStore implements StaticAddressLoopInStore for tests. type mockStore struct { + swaps []*StaticAddressLoopIn loopIns map[lntypes.Hash]*StaticAddressLoopIn mapIDs map[lntypes.Hash][]deposit.ID } @@ -427,7 +487,7 @@ func (s *mockStore) UpdateLoopIn(_ context.Context, func (s *mockStore) GetStaticAddressLoopInSwapsByStates(_ context.Context, _ []fsm.StateType) ([]*StaticAddressLoopIn, error) { - return nil, nil + return s.swaps, nil } func (s *mockStore) IsStored(_ context.Context, _ lntypes.Hash) (bool, error) { return false, nil