Skip to content

Commit c535a07

Browse files
committed
feat(tbtcpg): add bounded lookback window for deposit sweep event queries
Add a configurable look-back period (DepositSweepLookBackBlocks = 216000 blocks, ~30 days) to FindDepositsToSweep so it only scans recent blocks for DepositRevealed events instead of the full chain history. When the current block exceeds the look-back window, filterStartBlock is computed as currentBlock - lookBackBlocks; otherwise it falls back to 0 to guard against underflow. FindDeposits (used by the MovingFunds safety guard) continues to scan from block 0 to preserve full-history coverage. Add tests for both the bounded lookback path (currentBlock=300000, filterStartBlock=84000) and the underflow guard path (currentBlock=100000, filterStartBlock=0). Wire MockBlockCounter into the existing JSON-driven scenario tests so they pass with the new BlockCounter dependency.
1 parent 4c83762 commit c535a07

2 files changed

Lines changed: 229 additions & 3 deletions

File tree

pkg/tbtcpg/deposit_sweep.go

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ import (
1919
// This will ensure that deposit sweep transaction fees are not underestimated.
2020
const depositScriptByteSize = 126
2121

22+
// DepositSweepLookBackBlocks is the look-back period in blocks used
23+
// when searching for submitted deposit-related events. It's equal to
24+
// 30 days assuming 12 seconds per block.
25+
const DepositSweepLookBackBlocks = uint64(216000)
26+
2227
// DepositSweepTask is a task that may produce a deposit sweep proposal.
2328
type DepositSweepTask struct {
2429
chain Chain
@@ -110,7 +115,9 @@ type Deposit struct {
110115
Confirmations uint
111116
}
112117

113-
// FindDeposits finds deposits according to the given criteria.
118+
// FindDeposits finds deposits according to the given criteria. It always
119+
// performs a full-history scan (from block 0) to ensure all matching deposits
120+
// are returned regardless of age.
114121
func FindDeposits(
115122
chain Chain,
116123
btcChain bitcoin.Chain,
@@ -127,10 +134,13 @@ func FindDeposits(
127134
maxNumberOfDeposits,
128135
skipSwept,
129136
skipUnconfirmed,
137+
0,
130138
)
131139
}
132140

133141
// findDeposits finds deposits according to the given criteria.
142+
// The filterStartBlock parameter controls the earliest block from which
143+
// deposit-revealed events are queried.
134144
func findDeposits(
135145
fnLogger log.StandardLogger,
136146
chain Chain,
@@ -139,6 +149,7 @@ func findDeposits(
139149
maxNumberOfDeposits int,
140150
skipSwept bool,
141151
skipUnconfirmed bool,
152+
filterStartBlock uint64,
142153
) ([]*Deposit, error) {
143154
fnLogger.Infof("reading revealed deposits from chain")
144155

@@ -151,7 +162,9 @@ func findDeposits(
151162
}
152163
depositMinAge := time.Duration(depositMinAgeSeconds) * time.Second
153164

154-
filter := &tbtc.DepositRevealedEventFilter{}
165+
filter := &tbtc.DepositRevealedEventFilter{
166+
StartBlock: filterStartBlock,
167+
}
155168
if walletPublicKeyHash != [20]byte{} {
156169
filter.WalletPublicKeyHash = [][20]byte{walletPublicKeyHash}
157170
}
@@ -280,6 +293,27 @@ func (dst *DepositSweepTask) FindDepositsToSweep(
280293

281294
taskLogger.Infof("fetching max [%d] deposits", maxNumberOfDeposits)
282295

296+
blockCounter, err := dst.chain.BlockCounter()
297+
if err != nil {
298+
return nil, fmt.Errorf(
299+
"failed to get block counter: [%w]",
300+
err,
301+
)
302+
}
303+
304+
currentBlockNumber, err := blockCounter.CurrentBlock()
305+
if err != nil {
306+
return nil, fmt.Errorf(
307+
"failed to get current block number: [%w]",
308+
err,
309+
)
310+
}
311+
312+
filterStartBlock := uint64(0)
313+
if currentBlockNumber > DepositSweepLookBackBlocks {
314+
filterStartBlock = currentBlockNumber - DepositSweepLookBackBlocks
315+
}
316+
283317
unsweptDeposits, err := findDeposits(
284318
taskLogger,
285319
dst.chain,
@@ -288,6 +322,7 @@ func (dst *DepositSweepTask) FindDepositsToSweep(
288322
int(maxNumberOfDeposits),
289323
true,
290324
true,
325+
filterStartBlock,
291326
)
292327
if err != nil {
293328
return nil, err

pkg/tbtcpg/deposit_sweep_test.go

Lines changed: 192 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package tbtcpg_test
33
import (
44
"reflect"
55
"testing"
6+
"time"
67

78
"github.com/go-test/deep"
89
"github.com/ipfs/go-log"
@@ -13,6 +14,179 @@ import (
1314
"github.com/keep-network/keep-core/pkg/tbtcpg/internal/test"
1415
)
1516

17+
func TestDepositSweepLookBackBlocks(t *testing.T) {
18+
// The look-back period should be 30 days at 12 seconds per block,
19+
// matching the value used by MovedFundsSweepLookBackBlocks.
20+
expectedValue := uint64(216000)
21+
22+
if tbtcpg.DepositSweepLookBackBlocks != expectedValue {
23+
t.Errorf(
24+
"unexpected DepositSweepLookBackBlocks\n"+
25+
"expected: %d\n"+
26+
"actual: %d",
27+
expectedValue,
28+
tbtcpg.DepositSweepLookBackBlocks,
29+
)
30+
}
31+
}
32+
33+
func TestDepositSweepTask_FindDepositsToSweep_BoundedLookback(t *testing.T) {
34+
// When the current block (300000) exceeds the look-back window
35+
// (216000 blocks), the filter start block should be bounded:
36+
// filterStartBlock = 300000 - 216000 = 84000.
37+
currentBlock := uint64(300000)
38+
expectedStartBlock := currentBlock - tbtcpg.DepositSweepLookBackBlocks
39+
40+
walletPublicKeyHash := hexToByte20(
41+
"7670343fc00ccc2d0cd65360e6ad400697ea0fed",
42+
)
43+
44+
tbtcChain := tbtcpg.NewLocalChain()
45+
btcChain := tbtcpg.NewLocalBitcoinChain()
46+
47+
blockCounter := tbtcpg.NewMockBlockCounter()
48+
blockCounter.SetCurrentBlock(currentBlock)
49+
tbtcChain.SetBlockCounter(blockCounter)
50+
51+
tbtcChain.SetDepositMinAge(3600)
52+
53+
fundingTxHash := hashFromString(
54+
"a8c3b3c1975094550d481bdffdee1b7b7613dd74dbce37a5f6dce7fd9ac0ace1",
55+
)
56+
57+
tbtcChain.SetDepositRequest(
58+
fundingTxHash,
59+
uint32(1),
60+
&tbtc.DepositChainRequest{
61+
RevealedAt: time.Now().Add(-2 * time.Hour),
62+
SweptAt: time.Unix(0, 0),
63+
},
64+
)
65+
66+
btcChain.SetTransaction(fundingTxHash, &bitcoin.Transaction{})
67+
btcChain.SetTransactionConfirmations(
68+
fundingTxHash,
69+
tbtc.DepositSweepRequiredFundingTxConfirmations,
70+
)
71+
72+
// Register events under the filter with the bounded start block.
73+
// The production code will query with StartBlock = expectedStartBlock,
74+
// so the event registration must use the same filter key.
75+
err := tbtcChain.AddPastDepositRevealedEvent(
76+
&tbtc.DepositRevealedEventFilter{
77+
StartBlock: expectedStartBlock,
78+
WalletPublicKeyHash: [][20]byte{walletPublicKeyHash},
79+
},
80+
&tbtc.DepositRevealedEvent{
81+
BlockNumber: 290000,
82+
WalletPublicKeyHash: walletPublicKeyHash,
83+
FundingTxHash: fundingTxHash,
84+
FundingOutputIndex: 1,
85+
},
86+
)
87+
if err != nil {
88+
t.Fatal(err)
89+
}
90+
91+
task := tbtcpg.NewDepositSweepTask(tbtcChain, btcChain)
92+
93+
deposits, err := task.FindDepositsToSweep(
94+
&testutils.MockLogger{},
95+
walletPublicKeyHash,
96+
5,
97+
)
98+
if err != nil {
99+
t.Fatal(err)
100+
}
101+
102+
if len(deposits) != 1 {
103+
t.Fatalf("expected 1 deposit, got %d", len(deposits))
104+
}
105+
106+
if deposits[0].FundingTxHash != fundingTxHash {
107+
t.Errorf("unexpected funding tx hash")
108+
}
109+
110+
if deposits[0].FundingOutputIndex != 1 {
111+
t.Errorf("unexpected funding output index")
112+
}
113+
}
114+
115+
func TestDepositSweepTask_FindDepositsToSweep_UnderflowGuard(t *testing.T) {
116+
// When the current block is less than the look-back window, the filter
117+
// start block should remain 0 to avoid underflow.
118+
currentBlock := uint64(100000)
119+
120+
walletPublicKeyHash := hexToByte20(
121+
"7670343fc00ccc2d0cd65360e6ad400697ea0fed",
122+
)
123+
124+
tbtcChain := tbtcpg.NewLocalChain()
125+
btcChain := tbtcpg.NewLocalBitcoinChain()
126+
127+
blockCounter := tbtcpg.NewMockBlockCounter()
128+
blockCounter.SetCurrentBlock(currentBlock)
129+
tbtcChain.SetBlockCounter(blockCounter)
130+
131+
tbtcChain.SetDepositMinAge(3600)
132+
133+
fundingTxHash := hashFromString(
134+
"d91868ca43db4deb96047d727a5e782f282864fde2d9364f8c562c8998ba64bf",
135+
)
136+
137+
tbtcChain.SetDepositRequest(
138+
fundingTxHash,
139+
uint32(1),
140+
&tbtc.DepositChainRequest{
141+
RevealedAt: time.Now().Add(-2 * time.Hour),
142+
SweptAt: time.Unix(0, 0),
143+
},
144+
)
145+
146+
btcChain.SetTransaction(fundingTxHash, &bitcoin.Transaction{})
147+
btcChain.SetTransactionConfirmations(
148+
fundingTxHash,
149+
tbtc.DepositSweepRequiredFundingTxConfirmations,
150+
)
151+
152+
// Register events under the filter with StartBlock = 0,
153+
// since the underflow guard should keep the filter at 0.
154+
err := tbtcChain.AddPastDepositRevealedEvent(
155+
&tbtc.DepositRevealedEventFilter{
156+
StartBlock: 0,
157+
WalletPublicKeyHash: [][20]byte{walletPublicKeyHash},
158+
},
159+
&tbtc.DepositRevealedEvent{
160+
BlockNumber: 90000,
161+
WalletPublicKeyHash: walletPublicKeyHash,
162+
FundingTxHash: fundingTxHash,
163+
FundingOutputIndex: 1,
164+
},
165+
)
166+
if err != nil {
167+
t.Fatal(err)
168+
}
169+
170+
task := tbtcpg.NewDepositSweepTask(tbtcChain, btcChain)
171+
172+
deposits, err := task.FindDepositsToSweep(
173+
&testutils.MockLogger{},
174+
walletPublicKeyHash,
175+
5,
176+
)
177+
if err != nil {
178+
t.Fatal(err)
179+
}
180+
181+
if len(deposits) != 1 {
182+
t.Fatalf("expected 1 deposit, got %d", len(deposits))
183+
}
184+
185+
if deposits[0].FundingTxHash != fundingTxHash {
186+
t.Errorf("unexpected funding tx hash")
187+
}
188+
}
189+
16190
func TestDepositSweepTask_FindDepositsToSweep(t *testing.T) {
17191
err := log.SetLogLevel("*", "DEBUG")
18192
if err != nil {
@@ -31,6 +205,20 @@ func TestDepositSweepTask_FindDepositsToSweep(t *testing.T) {
31205

32206
tbtcChain.SetDepositMinAge(scenario.ChainParameters.DepositMinAge)
33207

208+
// Wire the MockBlockCounter using the scenario's current
209+
// block value. FindDepositsToSweep requires a block
210+
// counter to compute the bounded lookback window.
211+
blockCounter := tbtcpg.NewMockBlockCounter()
212+
blockCounter.SetCurrentBlock(scenario.ChainParameters.CurrentBlock)
213+
tbtcChain.SetBlockCounter(blockCounter)
214+
215+
// Compute the expected filter start block using the same
216+
// logic as the production code.
217+
filterStartBlock := uint64(0)
218+
if scenario.ChainParameters.CurrentBlock > tbtcpg.DepositSweepLookBackBlocks {
219+
filterStartBlock = scenario.ChainParameters.CurrentBlock - tbtcpg.DepositSweepLookBackBlocks
220+
}
221+
34222
// Chain setup.
35223
for _, deposit := range scenario.Deposits {
36224
tbtcChain.SetDepositRequest(
@@ -48,7 +236,10 @@ func TestDepositSweepTask_FindDepositsToSweep(t *testing.T) {
48236
)
49237

50238
err := tbtcChain.AddPastDepositRevealedEvent(
51-
&tbtc.DepositRevealedEventFilter{WalletPublicKeyHash: [][20]byte{deposit.WalletPublicKeyHash}},
239+
&tbtc.DepositRevealedEventFilter{
240+
StartBlock: filterStartBlock,
241+
WalletPublicKeyHash: [][20]byte{deposit.WalletPublicKeyHash},
242+
},
52243
&tbtc.DepositRevealedEvent{
53244
BlockNumber: deposit.RevealBlockNumber,
54245
WalletPublicKeyHash: deposit.WalletPublicKeyHash,

0 commit comments

Comments
 (0)