mirror of
https://github.com/lightninglabs/loop.git
synced 2026-08-13 12:33:03 +02:00
staticaddr/loopin: recover deposits by current outpoint
Recovered loop-ins carry two outpoint views. DepositOutpoints is the immutable swap input snapshot sent to the server and used to validate sweep requests. Deposits comes from the store's swap_hash/deposit-id join and reflects the current deposit rows. The active-deposit lookup takes a detour through the reconstructed deposit rows before asking the deposit manager for active deposits. That keeps recovery from depending on the historical input snapshot. A future replacement path can RBF a deposit from its original funding outpoint to a replacement outpoint while the swap still needs to retain the original input list. Looking up active deposits by DepositOutpoints would then fail recovery even though the store still maps the correct deposit IDs to the swap hash. Keep list responses on the store reconstruction too, so they do not re-resolve deposits through historical outpoints.
This commit is contained in:
parent
67252a84de
commit
3fdd9e2250
2 changed files with 85 additions and 33 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue