@@ -36,8 +36,20 @@ func encodeRPC(t *testing.T, id int, result string) []byte {
3636 return b
3737}
3838
39+ // withFastAccountRangeReseek shrinks the end-of-walk verification re-seek (one try, no delay) so
40+ // tests don't pay the production retry/backoff. It restores the originals on cleanup. It mutates
41+ // package vars, so callers must not run in parallel.
42+ func withFastAccountRangeReseek (t * testing.T ) {
43+ t .Helper ()
44+ origRetries , origDelay := accountRangeEmptyRetries , accountRangeEmptyRetryDelay
45+ accountRangeEmptyRetries , accountRangeEmptyRetryDelay = 1 , 0
46+ t .Cleanup (func () {
47+ accountRangeEmptyRetries , accountRangeEmptyRetryDelay = origRetries , origDelay
48+ })
49+ }
50+
3951func TestCollectAccountsViaStateDump_Paginates (t * testing.T ) {
40- t . Parallel ( )
52+ withFastAccountRangeReseek ( t )
4153
4254 var calls int
4355 server := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
@@ -48,15 +60,19 @@ func TestCollectAccountsViaStateDump_Paginates(t *testing.T) {
4860 w .Header ().Set ("Content-Type" , "application/json" )
4961
5062 var result string
51- if calls == 1 {
63+ switch calls {
64+ case 1 :
5265 // Non-zero base64 next cursor → tool must request a second page.
5366 next := base64 .StdEncoding .EncodeToString ([]byte {0x01 , 0x02 , 0x03 })
5467 result = `{"root":"0x0","accounts":{` +
5568 `"` + stepAAddr1 + `":{"address":"` + stepAAddr1 + `"},` +
5669 `"` + stepAAddr2 + `":{"address":"` + stepAAddr2 + `"}},"next":"` + next + `"}`
57- } else {
70+ case 2 :
5871 result = `{"root":"0x0","accounts":{` +
5972 `"` + stepAAddr3 + `":{"address":"` + stepAAddr3 + `"}},"next":""}`
73+ default :
74+ // End-of-walk verification re-seek past the highest key: genuinely no new accounts.
75+ result = `{"root":"0x0","accounts":{},"next":""}`
6076 }
6177 _ , _ = w .Write (encodeRPC (t , req .ID , result ))
6278 }))
@@ -65,14 +81,57 @@ func TestCollectAccountsViaStateDump_Paginates(t *testing.T) {
6581 cfg := & Config {L2RPCURL : server .URL }
6682 addrs , err := collectAccountsViaStateDump (context .Background (), cfg , 100 )
6783 require .NoError (t , err )
68- require .Equal (t , 2 , calls , "must paginate until next is empty" )
6984 require .ElementsMatch (t , []common.Address {
7085 common .HexToAddress (stepAAddr1 ),
7186 common .HexToAddress (stepAAddr2 ),
7287 common .HexToAddress (stepAAddr3 ),
7388 }, addrs )
7489}
7590
91+ // TestCollectAccountsViaStateDump_ReseeksPastPrematureEmptyCursor guards the fix: the cdk-erigon
92+ // endpoint can return a valid page with an empty "next" cursor before the trie ends. The dump must
93+ // not treat that as done — it re-seeks past the highest key seen and recovers the remaining
94+ // accounts instead of silently truncating.
95+ func TestCollectAccountsViaStateDump_ReseeksPastPrematureEmptyCursor (t * testing.T ) {
96+ withFastAccountRangeReseek (t )
97+
98+ var calls int
99+ server := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
100+ var req jsonRPCRequest
101+ require .NoError (t , json .NewDecoder (r .Body ).Decode (& req ))
102+ require .Equal (t , rpcMethodDebugAccountRange , req .Method )
103+ calls ++
104+ w .Header ().Set ("Content-Type" , "application/json" )
105+
106+ var result string
107+ switch calls {
108+ case 1 :
109+ // PREMATURE empty cursor: only addr1 returned, but addr2/addr3 still remain.
110+ result = `{"root":"0x0","accounts":{` +
111+ `"` + stepAAddr1 + `":{"address":"` + stepAAddr1 + `"}},"next":""}`
112+ case 2 :
113+ // Re-seek past addr1 recovers the accounts the premature cursor would have dropped.
114+ result = `{"root":"0x0","accounts":{` +
115+ `"` + stepAAddr2 + `":{"address":"` + stepAAddr2 + `"},` +
116+ `"` + stepAAddr3 + `":{"address":"` + stepAAddr3 + `"}},"next":""}`
117+ default :
118+ // Re-seek past addr3: genuinely empty → the walk is complete.
119+ result = `{"root":"0x0","accounts":{},"next":""}`
120+ }
121+ _ , _ = w .Write (encodeRPC (t , req .ID , result ))
122+ }))
123+ defer server .Close ()
124+
125+ cfg := & Config {L2RPCURL : server .URL }
126+ addrs , err := collectAccountsViaStateDump (context .Background (), cfg , 100 )
127+ require .NoError (t , err )
128+ require .ElementsMatch (t , []common.Address {
129+ common .HexToAddress (stepAAddr1 ),
130+ common .HexToAddress (stepAAddr2 ),
131+ common .HexToAddress (stepAAddr3 ),
132+ }, addrs , "re-seek must recover accounts dropped by a premature empty cursor" )
133+ }
134+
76135func TestFetchTransferHoldersInRange_ExtractsFromAndTo (t * testing.T ) {
77136 t .Parallel ()
78137
0 commit comments