@dusk-network/app-core 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/README.md +10 -6
- package/package.json +5 -1
- package/src/bridge-common.js +639 -0
- package/src/bridge-explorer.d.ts +34 -0
- package/src/bridge-explorer.js +186 -0
- package/src/bridge.d.ts +78 -20
- package/src/bridge.js +274 -708
- package/src/duskevm-withdrawals.d.ts +33 -4
- package/src/duskevm-withdrawals.js +218 -12
|
@@ -90,7 +90,11 @@ export declare const L2_WINDOW: 100000;
|
|
|
90
90
|
/** Log searches one run of `syncWithdrawals` makes, the head's included, by default. */
|
|
91
91
|
export declare const L2_RUN_REQUESTS: 8;
|
|
92
92
|
|
|
93
|
-
/**
|
|
93
|
+
/**
|
|
94
|
+
* DuskEVM blocks under the head counted as final where the RPC has no
|
|
95
|
+
* "finalized" block: about an hour. The blocks a run reads within that
|
|
96
|
+
* margin under it are read again once they're under a later run's.
|
|
97
|
+
*/
|
|
94
98
|
export declare const UNFINALIZED_MARGIN: 1800;
|
|
95
99
|
|
|
96
100
|
/** Pages of an address's transactions `explorerWithdrawals` reads at most, by default. */
|
|
@@ -359,7 +363,11 @@ export type WithdrawalsOptions = {
|
|
|
359
363
|
maxBatch?: number;
|
|
360
364
|
/** How many of the newest games a proof is looked for among: 64 by default, and for anything but a positive whole number. */
|
|
361
365
|
gameSearchDepth?: number | bigint;
|
|
362
|
-
/**
|
|
366
|
+
/**
|
|
367
|
+
* Where the deployment's fixed facts are kept: each game's hub and each
|
|
368
|
+
* game type's hub, once the factory names one, each hub's clock limit, and
|
|
369
|
+
* each withdrawal's receipt once its DuskEVM block is final.
|
|
370
|
+
*/
|
|
363
371
|
cache?: FactCache;
|
|
364
372
|
/** The clock, in milliseconds. */
|
|
365
373
|
now?: () => number;
|
|
@@ -368,7 +376,14 @@ export type WithdrawalsOptions = {
|
|
|
368
376
|
/** A network's withdrawals, read on its Dusk node and DuskEVM. */
|
|
369
377
|
export type Withdrawals = {
|
|
370
378
|
readonly network: Network;
|
|
371
|
-
/**
|
|
379
|
+
/**
|
|
380
|
+
* A DuskEVM transaction's withdrawal, from its receipt; refuses as
|
|
381
|
+
* `parseWithdrawalReceipt` does. The receipt is read after DuskEVM's
|
|
382
|
+
* finalized block, which the receipts read at the same time share, and
|
|
383
|
+
* kept in `cache` once its block is at or under that one: a final
|
|
384
|
+
* receipt is read once. One that isn't final yet, or isn't a
|
|
385
|
+
* withdrawal's, is read again each time.
|
|
386
|
+
*/
|
|
372
387
|
receipt(txHash: string): Promise<ReceiptWithdrawal>;
|
|
373
388
|
/**
|
|
374
389
|
* Where a withdrawal is, for `submitter` (a Dusk account, or its
|
|
@@ -452,6 +467,13 @@ export type FoundWithdrawal = {
|
|
|
452
467
|
export type WithdrawalScan = {
|
|
453
468
|
/** The final blocks covered, as ranges, highest first. */
|
|
454
469
|
covered: number[][];
|
|
470
|
+
/**
|
|
471
|
+
* Where the RPC has no finalized block: the blocks covered within
|
|
472
|
+
* `UNFINALIZED_MARGIN` under the head a run read them under, as ranges,
|
|
473
|
+
* highest first, the newest 16, read again once they're under a later
|
|
474
|
+
* run's margin. Older ones are left to the passes over all the blocks.
|
|
475
|
+
*/
|
|
476
|
+
open?: number[][];
|
|
455
477
|
/** What was found, by hash. */
|
|
456
478
|
entries: Record<string, FoundWithdrawal>;
|
|
457
479
|
/** The final head the last run read under. */
|
|
@@ -478,7 +500,14 @@ export type WithdrawalScan = {
|
|
|
478
500
|
* take though in half its requests at most (while older blocks are still to
|
|
479
501
|
* be read, only once the final head has moved), and carries on below; under
|
|
480
502
|
* a final head lower than the last run's, what was covered and found above
|
|
481
|
-
* it is dropped, and read again.
|
|
503
|
+
* it is dropped, and read again. Where the RPC has no finalized block, the
|
|
504
|
+
* final head is the latest less `UNFINALIZED_MARGIN`, and the blocks a run
|
|
505
|
+
* reads within that margin under it are read again once they're under a
|
|
506
|
+
* later run's (`open`): among the newest blocks a run reads again, or, once
|
|
507
|
+
* all the final blocks are covered, before the passes over them, with the
|
|
508
|
+
* requests a run has left. A deeper reorg there is found, and older blocks
|
|
509
|
+
* are read as they would be without it.
|
|
510
|
+
* Once all the final blocks are
|
|
482
511
|
* covered, they're read again from the newest down, a pass at a time with
|
|
483
512
|
* the requests a run has left: the first an hour later, each after as long
|
|
484
513
|
* again as it has been since. A withdrawal is found once: one read again
|
|
@@ -133,6 +133,10 @@ const NONCE_COUNT = (1n << 240n) - 1n;
|
|
|
133
133
|
// since.
|
|
134
134
|
const L2_OVERLAP = 50_000;
|
|
135
135
|
const L2_RECHECK_MS = 60 * 60_000;
|
|
136
|
+
// The ranges of blocks read only under a head the RPC didn't call final that
|
|
137
|
+
// a sync keeps to read again, the newest: older ones are left to its passes
|
|
138
|
+
// over all the blocks.
|
|
139
|
+
const L2_OPEN_RANGES = 16;
|
|
136
140
|
// Answers a read is sent again for, a quarter second, then half a second later.
|
|
137
141
|
const RETRY_STATUSES = new Set([429, 502, 503, 504]);
|
|
138
142
|
const ATTEMPTS = 3;
|
|
@@ -773,6 +777,20 @@ function evmRpc(client, net) {
|
|
|
773
777
|
};
|
|
774
778
|
}
|
|
775
779
|
|
|
780
|
+
/**
|
|
781
|
+
* A block number as DuskEVM's RPC gives it, a hex quantity, or as a whole
|
|
782
|
+
* number; null for anything else.
|
|
783
|
+
*
|
|
784
|
+
* @param {unknown} value
|
|
785
|
+
* @returns {bigint | null}
|
|
786
|
+
*/
|
|
787
|
+
function blockNumberOf(value) {
|
|
788
|
+
if (typeof value === "bigint") return value >= 0n ? value : null;
|
|
789
|
+
if (typeof value === "number") return Number.isSafeInteger(value) && value >= 0 ? BigInt(value) : null;
|
|
790
|
+
|
|
791
|
+
return typeof value === "string" && /^0x[0-9a-fA-F]+$/.test(value) ? BigInt(value) : null;
|
|
792
|
+
}
|
|
793
|
+
|
|
776
794
|
// ---------------------------------------------------------------------------
|
|
777
795
|
// The bridge's contracts on Dusk
|
|
778
796
|
// ---------------------------------------------------------------------------
|
|
@@ -1010,14 +1028,21 @@ function createReads({ cache, contracts, drivers, fetch, nodeUrl }) {
|
|
|
1010
1028
|
};
|
|
1011
1029
|
|
|
1012
1030
|
/**
|
|
1013
|
-
* The hub that holds a game type's games.
|
|
1031
|
+
* The hub that holds a game type's games. None, and nothing kept, for a
|
|
1032
|
+
* type the factory names no hub for yet.
|
|
1014
1033
|
*
|
|
1015
1034
|
* @param {number} gameType
|
|
1016
1035
|
* @returns {Promise<string>}
|
|
1017
1036
|
*/
|
|
1018
1037
|
const hubOfType = (gameType) =>
|
|
1019
1038
|
known(cache, inflight, `type:${contracts.disputeGameFactory}:${gameType}`, () =>
|
|
1020
|
-
factory("virtualGameHubId", gameType).then(
|
|
1039
|
+
factory("virtualGameHubId", gameType).then((value) => {
|
|
1040
|
+
const hubId = contractIdHex(value);
|
|
1041
|
+
|
|
1042
|
+
if (/^0+$/.test(hubId)) throw new Error("The factory names no hub for the game type");
|
|
1043
|
+
|
|
1044
|
+
return hubId;
|
|
1045
|
+
})
|
|
1021
1046
|
);
|
|
1022
1047
|
|
|
1023
1048
|
/**
|
|
@@ -1223,13 +1248,137 @@ export function createWithdrawals(options) {
|
|
|
1223
1248
|
]).then(([status, blacklisted, proper, respected]) => ({ blacklisted, proper, respected, status }));
|
|
1224
1249
|
|
|
1225
1250
|
/**
|
|
1251
|
+
* Where a final receipt is kept: by the network, its Portal and the
|
|
1252
|
+
* transaction's hash, so another network's or deployment's stays apart.
|
|
1253
|
+
*
|
|
1254
|
+
* @param {string} hash - 0x and 64 lower-case hex digits
|
|
1255
|
+
*/
|
|
1256
|
+
const receiptKey = (hash) => `receipt:${net.chainId}:${contracts.portal}:${hash}`;
|
|
1257
|
+
|
|
1258
|
+
/** @type {Promise<bigint | null> | null} */
|
|
1259
|
+
let finalizing = null;
|
|
1260
|
+
|
|
1261
|
+
/**
|
|
1262
|
+
* DuskEVM's finalized block's height, or null where it can't be read;
|
|
1263
|
+
* the receipts read at the same time share one read.
|
|
1264
|
+
*
|
|
1265
|
+
* @returns {Promise<bigint | null>}
|
|
1266
|
+
*/
|
|
1267
|
+
function finalizedHeight() {
|
|
1268
|
+
if (!finalizing) {
|
|
1269
|
+
const reading = Promise.resolve()
|
|
1270
|
+
.then(() => rpc("eth_getBlockByNumber", ["finalized", false]))
|
|
1271
|
+
.then(
|
|
1272
|
+
(block) => blockNumberOf(block?.number),
|
|
1273
|
+
() => null
|
|
1274
|
+
);
|
|
1275
|
+
|
|
1276
|
+
finalizing = reading;
|
|
1277
|
+
reading.then(() => {
|
|
1278
|
+
if (finalizing === reading) finalizing = null;
|
|
1279
|
+
});
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
return finalizing;
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
/** @type {Map<string, Promise<any>>} */
|
|
1286
|
+
const receiptReads = new Map();
|
|
1287
|
+
|
|
1288
|
+
/**
|
|
1289
|
+
* The withdrawal a kept receipt records, as when it was kept; null when
|
|
1290
|
+
* none is kept, or what is kept isn't this transaction's withdrawal.
|
|
1291
|
+
*
|
|
1292
|
+
* @param {string} hash
|
|
1293
|
+
* @returns {ReceiptWithdrawal | null}
|
|
1294
|
+
*/
|
|
1295
|
+
function keptReceipt(hash) {
|
|
1296
|
+
try {
|
|
1297
|
+
const text = cache.get(receiptKey(hash));
|
|
1298
|
+
|
|
1299
|
+
if (typeof text !== "string" || !text) return null;
|
|
1300
|
+
|
|
1301
|
+
const withdrawal = parseWithdrawalReceipt(net, JSON.parse(text));
|
|
1302
|
+
|
|
1303
|
+
return withdrawal.transactionHash === hash ? withdrawal : null;
|
|
1304
|
+
} catch {
|
|
1305
|
+
return null;
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
/**
|
|
1310
|
+
* Keeps a receipt that can't change any more: a withdrawal's, of this
|
|
1311
|
+
* transaction, in a block at or under `final`, the finalized height read
|
|
1312
|
+
* before the receipt was. A failed or missing receipt, or one that isn't
|
|
1313
|
+
* a withdrawal's, isn't kept. Kept as the RPC gave it, once its text
|
|
1314
|
+
* reads back as the same withdrawal.
|
|
1315
|
+
*
|
|
1316
|
+
* @param {string} hash
|
|
1317
|
+
* @param {any} raw
|
|
1318
|
+
* @param {bigint} final
|
|
1319
|
+
*/
|
|
1320
|
+
function keepIfFinal(hash, raw, final) {
|
|
1321
|
+
try {
|
|
1322
|
+
const withdrawal = parseWithdrawalReceipt(net, raw);
|
|
1323
|
+
const block = blockNumberOf(raw?.blockNumber);
|
|
1324
|
+
|
|
1325
|
+
if (block === null || block !== withdrawal.blockNumber || block > final || withdrawal.transactionHash !== hash) return;
|
|
1326
|
+
|
|
1327
|
+
const text = JSON.stringify(raw);
|
|
1328
|
+
const again = parseWithdrawalReceipt(net, JSON.parse(text));
|
|
1329
|
+
|
|
1330
|
+
if (again.withdrawalHash !== withdrawal.withdrawalHash || again.blockNumber !== block || again.transactionHash !== hash) return;
|
|
1331
|
+
|
|
1332
|
+
cache.set(receiptKey(hash), text);
|
|
1333
|
+
} catch {
|
|
1334
|
+
// Not kept: it's read again next time.
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
/**
|
|
1339
|
+
* A transaction's receipt as DuskEVM gives it, read after DuskEVM's
|
|
1340
|
+
* finalized block: a receipt read once its block is final is the final
|
|
1341
|
+
* chain's, and is kept. The callers asking for one receipt at the same
|
|
1342
|
+
* time share one read.
|
|
1343
|
+
*
|
|
1344
|
+
* @param {string} hash
|
|
1345
|
+
* @returns {Promise<any>}
|
|
1346
|
+
*/
|
|
1347
|
+
function readReceipt(hash) {
|
|
1348
|
+
let reading = receiptReads.get(hash);
|
|
1349
|
+
|
|
1350
|
+
if (!reading) {
|
|
1351
|
+
reading = (async () => {
|
|
1352
|
+
const final = await finalizedHeight();
|
|
1353
|
+
const raw = await rpc("eth_getTransactionReceipt", [hash]);
|
|
1354
|
+
|
|
1355
|
+
if (final !== null) keepIfFinal(hash, raw, final);
|
|
1356
|
+
|
|
1357
|
+
return raw;
|
|
1358
|
+
})();
|
|
1359
|
+
receiptReads.set(hash, reading);
|
|
1360
|
+
reading.then(
|
|
1361
|
+
() => receiptReads.delete(hash),
|
|
1362
|
+
() => receiptReads.delete(hash)
|
|
1363
|
+
);
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
return reading;
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
/**
|
|
1370
|
+
* A transaction's withdrawal, from its receipt: the one kept once its
|
|
1371
|
+
* block was final, else the one DuskEVM gives now.
|
|
1372
|
+
*
|
|
1226
1373
|
* @param {string} txHash
|
|
1227
1374
|
* @returns {Promise<ReceiptWithdrawal>}
|
|
1228
1375
|
*/
|
|
1229
1376
|
async function receipt(txHash) {
|
|
1230
1377
|
if (typeof txHash !== "string" || !/^0x[0-9a-fA-F]{64}$/.test(txHash)) throw new WithdrawalError("bad-hash");
|
|
1231
1378
|
|
|
1232
|
-
|
|
1379
|
+
const hash = txHash.toLowerCase();
|
|
1380
|
+
|
|
1381
|
+
return keptReceipt(hash) ?? parseWithdrawalReceipt(net, await readReceipt(hash));
|
|
1233
1382
|
}
|
|
1234
1383
|
|
|
1235
1384
|
/**
|
|
@@ -2092,22 +2241,26 @@ function withdrawalFromLog(log) {
|
|
|
2092
2241
|
const tooWide = (error) => /range|limit|too many|exceed/i.test(asError(error).message);
|
|
2093
2242
|
|
|
2094
2243
|
/**
|
|
2095
|
-
* The highest DuskEVM block that can't be reorged away,
|
|
2096
|
-
* took
|
|
2244
|
+
* The highest DuskEVM block that can't be reorged away, the requests it
|
|
2245
|
+
* took, and the margin under it within which the blocks a run reads are
|
|
2246
|
+
* read again on a later one: none under the RPC's finalized block. Where
|
|
2247
|
+
* the RPC has none, the head is the latest less a margin, and the blocks
|
|
2248
|
+
* read within the margin under it are read again, as a deeper reorg could
|
|
2249
|
+
* still change them.
|
|
2097
2250
|
*
|
|
2098
2251
|
* @param {(method: string, params?: unknown[]) => Promise<any>} rpc
|
|
2099
|
-
* @returns {Promise<{ head: number, requests: number }>}
|
|
2252
|
+
* @returns {Promise<{ head: number, margin: number, requests: number }>}
|
|
2100
2253
|
*/
|
|
2101
2254
|
async function finalHead(rpc) {
|
|
2102
2255
|
try {
|
|
2103
2256
|
const block = await rpc("eth_getBlockByNumber", ["finalized", false]);
|
|
2104
2257
|
|
|
2105
|
-
if (block?.number) return { head: Number(BigInt(block.number)), requests: 1 };
|
|
2258
|
+
if (block?.number) return { head: Number(BigInt(block.number)), margin: 0, requests: 1 };
|
|
2106
2259
|
} catch {
|
|
2107
2260
|
// An RPC without "finalized": the latest block, less a margin.
|
|
2108
2261
|
}
|
|
2109
2262
|
|
|
2110
|
-
return { head: Math.max(0, Number(BigInt(await rpc("eth_blockNumber", []))) - UNFINALIZED_MARGIN), requests: 2 };
|
|
2263
|
+
return { head: Math.max(0, Number(BigInt(await rpc("eth_blockNumber", []))) - UNFINALIZED_MARGIN), margin: UNFINALIZED_MARGIN, requests: 2 };
|
|
2111
2264
|
}
|
|
2112
2265
|
|
|
2113
2266
|
/**
|
|
@@ -2129,6 +2282,39 @@ function recheckOf(value, target) {
|
|
|
2129
2282
|
return { at: kept.at, from: kept.from === null ? null : Math.min(kept.from, target), since: kept.since };
|
|
2130
2283
|
}
|
|
2131
2284
|
|
|
2285
|
+
/**
|
|
2286
|
+
* The blocks a state kept as read only under a head the RPC didn't call
|
|
2287
|
+
* final, as far as they're still covered, as ranges, highest first; none
|
|
2288
|
+
* for anything else.
|
|
2289
|
+
*
|
|
2290
|
+
* @param {unknown} value
|
|
2291
|
+
* @param {number[][]} covered
|
|
2292
|
+
* @returns {number[][]}
|
|
2293
|
+
*/
|
|
2294
|
+
function openOf(value, covered) {
|
|
2295
|
+
const kept = Array.isArray(value) ? value.filter((range) => Array.isArray(range) && Number.isSafeInteger(range[0]) && Number.isSafeInteger(range[1]) && range[0] <= range[1]) : [];
|
|
2296
|
+
/** @type {number[][]} */
|
|
2297
|
+
let out = [];
|
|
2298
|
+
|
|
2299
|
+
for (const [a, b] of kept) {
|
|
2300
|
+
for (const [c, d] of covered) {
|
|
2301
|
+
if (Math.max(a, c) <= Math.min(b, d)) out = addCoverage(out, Math.max(a, c), Math.min(b, d));
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2305
|
+
return out;
|
|
2306
|
+
}
|
|
2307
|
+
|
|
2308
|
+
/**
|
|
2309
|
+
* Ranges of blocks, highest first, without blocks `lo` to `hi`.
|
|
2310
|
+
*
|
|
2311
|
+
* @param {number[][]} ranges
|
|
2312
|
+
* @param {number} lo
|
|
2313
|
+
* @param {number} hi
|
|
2314
|
+
* @returns {number[][]}
|
|
2315
|
+
*/
|
|
2316
|
+
const without = (ranges, lo, hi) => ranges.flatMap(([a, b]) => [...(b > hi ? [[Math.max(a, hi + 1), b]] : []), ...(a < lo ? [[a, Math.min(b, lo - 1)]] : [])]);
|
|
2317
|
+
|
|
2132
2318
|
/**
|
|
2133
2319
|
* @param {{ address: string, duskEvm: DuskEvmClient, network?: NetworkRef, bridge?: string, state?: WithdrawalScan | null, maxRequests?: number, now?: () => number }} options
|
|
2134
2320
|
* @returns {Promise<{ found: FoundWithdrawal[], requests: number, state: WithdrawalScan }>}
|
|
@@ -2141,7 +2327,7 @@ export async function syncWithdrawals({ address, bridge, duskEvm, maxRequests =
|
|
|
2141
2327
|
if (!contract) throw new TypeError("No DuskEVM bridge to read");
|
|
2142
2328
|
|
|
2143
2329
|
const rpc = evmRpc(duskEvm, net);
|
|
2144
|
-
const { head: target, requests: asked } = await finalHead(rpc);
|
|
2330
|
+
const { head: target, margin, requests: asked } = await finalHead(rpc);
|
|
2145
2331
|
const time = now();
|
|
2146
2332
|
// Coverage kept above the final head, as an older run read the latest, is
|
|
2147
2333
|
// read again, and what was found there is dropped with it: it may have
|
|
@@ -2155,6 +2341,11 @@ export async function syncWithdrawals({ address, bridge, duskEvm, maxRequests =
|
|
|
2155
2341
|
let requests = asked;
|
|
2156
2342
|
let recheck = recheckOf(state?.recheck, target);
|
|
2157
2343
|
const topics = [ETH_BRIDGE_INITIATED_TOPIC, pad(sender)];
|
|
2344
|
+
// Blocks read only under a head the RPC didn't call final, within its
|
|
2345
|
+
// margin, are read again once they're under a run's margin too: a deeper
|
|
2346
|
+
// reorg could still change them. Those kept above the final head go with
|
|
2347
|
+
// its coverage.
|
|
2348
|
+
let open = openOf(state?.open, covered);
|
|
2158
2349
|
|
|
2159
2350
|
/**
|
|
2160
2351
|
* The address's withdrawals in blocks `lo` to `hi`, those blocks covered;
|
|
@@ -2193,6 +2384,10 @@ export async function syncWithdrawals({ address, bridge, duskEvm, maxRequests =
|
|
|
2193
2384
|
}
|
|
2194
2385
|
|
|
2195
2386
|
covered = addCoverage(covered, lo, hi);
|
|
2387
|
+
// Read under this run's margin, they're as final as its head; within it,
|
|
2388
|
+
// under a head the RPC didn't call final, they're open.
|
|
2389
|
+
open = without(open, lo, hi);
|
|
2390
|
+
if (margin && hi > target - margin) open = addCoverage(open, Math.max(lo, target - margin + 1), hi);
|
|
2196
2391
|
|
|
2197
2392
|
return true;
|
|
2198
2393
|
};
|
|
@@ -2225,8 +2420,19 @@ export async function syncWithdrawals({ address, bridge, duskEvm, maxRequests =
|
|
|
2225
2420
|
continue;
|
|
2226
2421
|
}
|
|
2227
2422
|
|
|
2228
|
-
// All the final blocks covered:
|
|
2229
|
-
//
|
|
2423
|
+
// All the final blocks covered: those read only under a head the RPC
|
|
2424
|
+
// didn't call final, and under this run's margin now, are read again
|
|
2425
|
+
// first, the newest first. Then all of them, a pass at a time, from the
|
|
2426
|
+
// newest down, with what a run has left.
|
|
2427
|
+
const settling = open.find(([a]) => a <= target - margin);
|
|
2428
|
+
|
|
2429
|
+
if (settling) {
|
|
2430
|
+
const b = Math.min(settling[1], target - margin);
|
|
2431
|
+
|
|
2432
|
+
await read(Math.max(settling[0], b - window + 1), b);
|
|
2433
|
+
continue;
|
|
2434
|
+
}
|
|
2435
|
+
|
|
2230
2436
|
if (recheck === null) recheck = { at: time + L2_RECHECK_MS, from: null, since: time };
|
|
2231
2437
|
if (recheck.from === null && time < recheck.at) break;
|
|
2232
2438
|
|
|
@@ -2240,7 +2446,7 @@ export async function syncWithdrawals({ address, bridge, duskEvm, maxRequests =
|
|
|
2240
2446
|
|
|
2241
2447
|
if (recheck === null && !topGap(covered, target)) recheck = { at: time + L2_RECHECK_MS, from: null, since: time };
|
|
2242
2448
|
|
|
2243
|
-
return { found, requests, state: { covered, entries, ...(recheck ? { recheck } : {}), target, updatedAt: now(), window } };
|
|
2449
|
+
return { found, requests, state: { covered, entries, ...(open.length ? { open: open.slice(0, L2_OPEN_RANGES) } : {}), ...(recheck ? { recheck } : {}), target, updatedAt: now(), window } };
|
|
2244
2450
|
}
|
|
2245
2451
|
|
|
2246
2452
|
/**
|