@dusk-network/app-core 0.4.1 → 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 +14 -0
- package/README.md +8 -4
- 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 +69 -15
- package/src/bridge.js +259 -698
- package/src/duskevm-withdrawals.d.ts +23 -5
- package/src/duskevm-withdrawals.js +79 -11
|
@@ -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. */
|
|
@@ -360,9 +364,9 @@ export type WithdrawalsOptions = {
|
|
|
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
|
/**
|
|
363
|
-
* Where the deployment's fixed facts are kept: each game's hub
|
|
364
|
-
* type's hub, each hub's clock limit, and
|
|
365
|
-
* its DuskEVM block is final.
|
|
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.
|
|
366
370
|
*/
|
|
367
371
|
cache?: FactCache;
|
|
368
372
|
/** The clock, in milliseconds. */
|
|
@@ -463,6 +467,13 @@ export type FoundWithdrawal = {
|
|
|
463
467
|
export type WithdrawalScan = {
|
|
464
468
|
/** The final blocks covered, as ranges, highest first. */
|
|
465
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[][];
|
|
466
477
|
/** What was found, by hash. */
|
|
467
478
|
entries: Record<string, FoundWithdrawal>;
|
|
468
479
|
/** The final head the last run read under. */
|
|
@@ -489,7 +500,14 @@ export type WithdrawalScan = {
|
|
|
489
500
|
* take though in half its requests at most (while older blocks are still to
|
|
490
501
|
* be read, only once the final head has moved), and carries on below; under
|
|
491
502
|
* a final head lower than the last run's, what was covered and found above
|
|
492
|
-
* 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
|
|
493
511
|
* covered, they're read again from the newest down, a pass at a time with
|
|
494
512
|
* the requests a run has left: the first an hour later, each after as long
|
|
495
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;
|
|
@@ -1024,14 +1028,21 @@ function createReads({ cache, contracts, drivers, fetch, nodeUrl }) {
|
|
|
1024
1028
|
};
|
|
1025
1029
|
|
|
1026
1030
|
/**
|
|
1027
|
-
* 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.
|
|
1028
1033
|
*
|
|
1029
1034
|
* @param {number} gameType
|
|
1030
1035
|
* @returns {Promise<string>}
|
|
1031
1036
|
*/
|
|
1032
1037
|
const hubOfType = (gameType) =>
|
|
1033
1038
|
known(cache, inflight, `type:${contracts.disputeGameFactory}:${gameType}`, () =>
|
|
1034
|
-
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
|
+
})
|
|
1035
1046
|
);
|
|
1036
1047
|
|
|
1037
1048
|
/**
|
|
@@ -2230,22 +2241,26 @@ function withdrawalFromLog(log) {
|
|
|
2230
2241
|
const tooWide = (error) => /range|limit|too many|exceed/i.test(asError(error).message);
|
|
2231
2242
|
|
|
2232
2243
|
/**
|
|
2233
|
-
* The highest DuskEVM block that can't be reorged away,
|
|
2234
|
-
* 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.
|
|
2235
2250
|
*
|
|
2236
2251
|
* @param {(method: string, params?: unknown[]) => Promise<any>} rpc
|
|
2237
|
-
* @returns {Promise<{ head: number, requests: number }>}
|
|
2252
|
+
* @returns {Promise<{ head: number, margin: number, requests: number }>}
|
|
2238
2253
|
*/
|
|
2239
2254
|
async function finalHead(rpc) {
|
|
2240
2255
|
try {
|
|
2241
2256
|
const block = await rpc("eth_getBlockByNumber", ["finalized", false]);
|
|
2242
2257
|
|
|
2243
|
-
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 };
|
|
2244
2259
|
} catch {
|
|
2245
2260
|
// An RPC without "finalized": the latest block, less a margin.
|
|
2246
2261
|
}
|
|
2247
2262
|
|
|
2248
|
-
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 };
|
|
2249
2264
|
}
|
|
2250
2265
|
|
|
2251
2266
|
/**
|
|
@@ -2267,6 +2282,39 @@ function recheckOf(value, target) {
|
|
|
2267
2282
|
return { at: kept.at, from: kept.from === null ? null : Math.min(kept.from, target), since: kept.since };
|
|
2268
2283
|
}
|
|
2269
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
|
+
|
|
2270
2318
|
/**
|
|
2271
2319
|
* @param {{ address: string, duskEvm: DuskEvmClient, network?: NetworkRef, bridge?: string, state?: WithdrawalScan | null, maxRequests?: number, now?: () => number }} options
|
|
2272
2320
|
* @returns {Promise<{ found: FoundWithdrawal[], requests: number, state: WithdrawalScan }>}
|
|
@@ -2279,7 +2327,7 @@ export async function syncWithdrawals({ address, bridge, duskEvm, maxRequests =
|
|
|
2279
2327
|
if (!contract) throw new TypeError("No DuskEVM bridge to read");
|
|
2280
2328
|
|
|
2281
2329
|
const rpc = evmRpc(duskEvm, net);
|
|
2282
|
-
const { head: target, requests: asked } = await finalHead(rpc);
|
|
2330
|
+
const { head: target, margin, requests: asked } = await finalHead(rpc);
|
|
2283
2331
|
const time = now();
|
|
2284
2332
|
// Coverage kept above the final head, as an older run read the latest, is
|
|
2285
2333
|
// read again, and what was found there is dropped with it: it may have
|
|
@@ -2293,6 +2341,11 @@ export async function syncWithdrawals({ address, bridge, duskEvm, maxRequests =
|
|
|
2293
2341
|
let requests = asked;
|
|
2294
2342
|
let recheck = recheckOf(state?.recheck, target);
|
|
2295
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);
|
|
2296
2349
|
|
|
2297
2350
|
/**
|
|
2298
2351
|
* The address's withdrawals in blocks `lo` to `hi`, those blocks covered;
|
|
@@ -2331,6 +2384,10 @@ export async function syncWithdrawals({ address, bridge, duskEvm, maxRequests =
|
|
|
2331
2384
|
}
|
|
2332
2385
|
|
|
2333
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);
|
|
2334
2391
|
|
|
2335
2392
|
return true;
|
|
2336
2393
|
};
|
|
@@ -2363,8 +2420,19 @@ export async function syncWithdrawals({ address, bridge, duskEvm, maxRequests =
|
|
|
2363
2420
|
continue;
|
|
2364
2421
|
}
|
|
2365
2422
|
|
|
2366
|
-
// All the final blocks covered:
|
|
2367
|
-
//
|
|
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
|
+
|
|
2368
2436
|
if (recheck === null) recheck = { at: time + L2_RECHECK_MS, from: null, since: time };
|
|
2369
2437
|
if (recheck.from === null && time < recheck.at) break;
|
|
2370
2438
|
|
|
@@ -2378,7 +2446,7 @@ export async function syncWithdrawals({ address, bridge, duskEvm, maxRequests =
|
|
|
2378
2446
|
|
|
2379
2447
|
if (recheck === null && !topGap(covered, target)) recheck = { at: time + L2_RECHECK_MS, from: null, since: time };
|
|
2380
2448
|
|
|
2381
|
-
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 } };
|
|
2382
2450
|
}
|
|
2383
2451
|
|
|
2384
2452
|
/**
|