@dusk-network/app-core 0.3.2 → 0.4.1
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 +36 -0
- package/README.md +15 -11
- package/package.json +1 -1
- package/src/activity.d.ts +5 -1
- package/src/activity.js +7 -1
- package/src/bridge.d.ts +26 -10
- package/src/bridge.js +184 -101
- package/src/duskevm-withdrawals.d.ts +31 -6
- package/src/duskevm-withdrawals.js +234 -18
- package/src/history.d.ts +34 -10
- package/src/history.js +80 -18
- package/src/shielded.d.ts +21 -6
- package/src/shielded.js +155 -42
package/src/bridge.js
CHANGED
|
@@ -76,14 +76,16 @@ const MAX_U64 = (1n << 64n) - 1n;
|
|
|
76
76
|
const TIMEOUT_MS = 8_000;
|
|
77
77
|
// About one Dusk block every 10 seconds: how far back a sender's history is read.
|
|
78
78
|
const DUSK_BLOCK_MS = 10_000;
|
|
79
|
-
// The blocks the chain's pace is read over,
|
|
80
|
-
//
|
|
81
|
-
//
|
|
79
|
+
// The blocks the chain's pace is read over, from a block rounded down to a
|
|
80
|
+
// multiple of `PACE_STEP` so that the app's cache keeps its time, and how
|
|
81
|
+
// long before the transfer a search starts as the pace says: the block is
|
|
82
|
+
// then checked by its time, and stepped back while it's still later.
|
|
82
83
|
const PACE_SAMPLE = 100_000n;
|
|
83
|
-
const
|
|
84
|
-
const
|
|
85
|
-
const START_CHECKS = 4;
|
|
84
|
+
const PACE_STEP = 10_000n;
|
|
85
|
+
const START_MS = 10 * 60_000;
|
|
86
86
|
const MIN_WINDOW = 50n;
|
|
87
|
+
// The widest a search's windows grow, while the RPC answers them whole.
|
|
88
|
+
const MAX_WINDOW = 100_000n;
|
|
87
89
|
// How far back from the head a search's last window reaches: those blocks
|
|
88
90
|
// are read again on each call.
|
|
89
91
|
const OVERLAP = 1_000n;
|
|
@@ -245,9 +247,9 @@ function historyScanOf(value) {
|
|
|
245
247
|
}
|
|
246
248
|
|
|
247
249
|
/**
|
|
248
|
-
* Where a search stopped, what it searched for,
|
|
249
|
-
* before, and how far the transfer's history is
|
|
250
|
-
* them; null for anything else.
|
|
250
|
+
* Where a search stopped, what it searched for, the credits it found
|
|
251
|
+
* before, the width of its windows, and how far the transfer's history is
|
|
252
|
+
* read, as a result gave them; null for anything else.
|
|
251
253
|
*
|
|
252
254
|
* @param {unknown} value
|
|
253
255
|
* @returns {Scan | null}
|
|
@@ -274,10 +276,16 @@ function scanOf(value) {
|
|
|
274
276
|
// as a search kept by an earlier version.
|
|
275
277
|
const start = quantityOf(scan.start);
|
|
276
278
|
const recheck = start === null ? null : recheckOf(scan.recheck);
|
|
279
|
+
// The width of its windows, as an earlier version kept it for its passes
|
|
280
|
+
// where it isn't said; and the narrowest the RPC refused.
|
|
281
|
+
const size = Number.isSafeInteger(scan.size) && scan.size >= Number(MIN_WINDOW) && scan.size <= Number(MAX_WINDOW) ? scan.size : (recheck?.size ?? null);
|
|
282
|
+
const refused = Number.isSafeInteger(scan.refused) && scan.refused > 0 ? scan.refused : null;
|
|
277
283
|
|
|
278
284
|
Object.assign(out, { basis: scan.basis, from: from.toString(), search: scan.search, seen });
|
|
279
285
|
if (start !== null) out.start = start.toString();
|
|
280
286
|
if (recheck) out.recheck = recheckIn(recheck);
|
|
287
|
+
if (size !== null) out.size = size;
|
|
288
|
+
if (refused !== null) out.refused = refused;
|
|
281
289
|
} else if (!history) {
|
|
282
290
|
return null;
|
|
283
291
|
}
|
|
@@ -395,6 +403,10 @@ function reader(fetchFn, timeoutMs) {
|
|
|
395
403
|
|
|
396
404
|
return {
|
|
397
405
|
counter,
|
|
406
|
+
// What a call reads once for all its searches, by what and where: a
|
|
407
|
+
// chain's head and pace.
|
|
408
|
+
/** @type {Map<string, Promise<any>>} */
|
|
409
|
+
once: new Map(),
|
|
398
410
|
|
|
399
411
|
/**
|
|
400
412
|
* @param {string} url
|
|
@@ -693,80 +705,125 @@ async function blockTime(read, url, block, cache) {
|
|
|
693
705
|
|
|
694
706
|
if (typeof kept === "number" && Number.isFinite(kept)) return kept;
|
|
695
707
|
|
|
696
|
-
|
|
708
|
+
return once(read, `time ${url} ${block}`, async () => {
|
|
709
|
+
const seconds = quantityOf((await read.rpc(url, "eth_getBlockByNumber", [hex(block), false]))?.timestamp);
|
|
697
710
|
|
|
698
|
-
|
|
711
|
+
if (seconds === null) throw new Error(`The chain gave no time for block ${block}`);
|
|
699
712
|
|
|
700
|
-
|
|
713
|
+
const at = Number(seconds) * 1000;
|
|
701
714
|
|
|
702
|
-
|
|
715
|
+
cache?.set(Number(block), at);
|
|
703
716
|
|
|
704
|
-
|
|
717
|
+
return at;
|
|
718
|
+
});
|
|
705
719
|
}
|
|
706
720
|
|
|
707
721
|
/**
|
|
722
|
+
* What a call reads once for all its searches on a chain.
|
|
723
|
+
*
|
|
724
|
+
* @template T
|
|
725
|
+
* @param {Reader} read
|
|
726
|
+
* @param {string} key
|
|
727
|
+
* @param {() => Promise<T>} get
|
|
728
|
+
* @returns {Promise<T>}
|
|
729
|
+
*/
|
|
730
|
+
function once(read, key, get) {
|
|
731
|
+
const kept = read.once.get(key);
|
|
732
|
+
|
|
733
|
+
if (kept) return kept;
|
|
734
|
+
|
|
735
|
+
const got = get();
|
|
736
|
+
|
|
737
|
+
read.once.set(key, got);
|
|
738
|
+
// One that failed is read again by the next search.
|
|
739
|
+
got.catch(() => read.once.delete(key));
|
|
740
|
+
|
|
741
|
+
return got;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/**
|
|
745
|
+
* The chain's head, read once a call: the head its pace was read with, where
|
|
746
|
+
* the call read that.
|
|
747
|
+
*
|
|
708
748
|
* @param {Reader} read
|
|
709
749
|
* @param {string} url
|
|
710
750
|
* @returns {Promise<bigint>}
|
|
711
751
|
*/
|
|
712
752
|
async function headOf(read, url) {
|
|
713
|
-
const
|
|
753
|
+
const paced = read.once.get(`pace ${url}`);
|
|
714
754
|
|
|
715
|
-
if (
|
|
755
|
+
if (paced) return (await paced).head;
|
|
716
756
|
|
|
717
|
-
return head
|
|
757
|
+
return once(read, `head ${url}`, async () => {
|
|
758
|
+
const head = quantityOf(await read.rpc(url, "eth_blockNumber", []));
|
|
759
|
+
|
|
760
|
+
if (head === null) throw new Error("The chain gave no head");
|
|
761
|
+
|
|
762
|
+
return head;
|
|
763
|
+
});
|
|
718
764
|
}
|
|
719
765
|
|
|
720
766
|
/**
|
|
721
|
-
* The chain's head, and
|
|
722
|
-
*
|
|
723
|
-
* stepped back while its own time is still later, twice as far each time,
|
|
724
|
-
* as the chain's pace may have changed.
|
|
767
|
+
* The chain's head, its time, and its pace (blocks a second) over its last
|
|
768
|
+
* 100,000 blocks or so, read once a call.
|
|
725
769
|
*
|
|
726
770
|
* @param {Reader} read
|
|
727
771
|
* @param {string} url
|
|
728
|
-
* @param {number} atMs
|
|
729
772
|
* @param {BlockTimes | undefined} cache
|
|
730
|
-
* @returns {Promise<{ head: bigint,
|
|
773
|
+
* @returns {Promise<{ head: bigint, seconds: number, pace: number }>}
|
|
731
774
|
*/
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
775
|
+
function paceOf(read, url, cache) {
|
|
776
|
+
return once(read, `pace ${url}`, async () => {
|
|
777
|
+
const latest = await read.rpc(url, "eth_getBlockByNumber", ["latest", false]);
|
|
778
|
+
const head = quantityOf(latest?.number);
|
|
779
|
+
const headSeconds = quantityOf(latest?.timestamp);
|
|
736
780
|
|
|
737
|
-
|
|
781
|
+
if (head === null || headSeconds === null) throw new Error("The chain gave no head");
|
|
738
782
|
|
|
739
|
-
|
|
783
|
+
cache?.set(Number(head), Number(headSeconds) * 1000);
|
|
784
|
+
read.once.set(`time ${url} ${head}`, Promise.resolve(Number(headSeconds) * 1000));
|
|
740
785
|
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
let pace = 1;
|
|
786
|
+
const back = head < PACE_SAMPLE ? 0n : ((head - PACE_SAMPLE) / PACE_STEP) * PACE_STEP;
|
|
787
|
+
let pace = 1;
|
|
744
788
|
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
const span = Number(headSeconds) - sample / 1000;
|
|
789
|
+
if (back < head) {
|
|
790
|
+
const span = Number(headSeconds) - (await blockTime(read, url, back, cache)) / 1000;
|
|
748
791
|
|
|
749
|
-
|
|
750
|
-
|
|
792
|
+
if (span > 0) pace = Number(head - back) / span;
|
|
793
|
+
}
|
|
751
794
|
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
795
|
+
return { head, pace, seconds: Number(headSeconds) };
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
/**
|
|
800
|
+
* The chain's head, and a block from before `atMs`: the block 10 minutes
|
|
801
|
+
* before it, as the chain's pace says, checked by its time, and stepped back
|
|
802
|
+
* while that's still later, twice as far each time, as the chain's pace may
|
|
803
|
+
* have changed.
|
|
804
|
+
*
|
|
805
|
+
* @param {Reader} read
|
|
806
|
+
* @param {string} url
|
|
807
|
+
* @param {number} atMs
|
|
808
|
+
* @param {BlockTimes | undefined} cache
|
|
809
|
+
* @returns {Promise<{ head: bigint, start: bigint }>}
|
|
810
|
+
*/
|
|
811
|
+
async function startBefore(read, url, atMs, cache) {
|
|
812
|
+
const { head, pace, seconds } = await paceOf(read, url, cache);
|
|
813
|
+
/** @param {bigint} block @param {number} ms @param {number} times */
|
|
814
|
+
const back = (block, ms, times) => {
|
|
815
|
+
const step = BigInt(Math.ceil((Math.max(0, ms) / 1000) * pace * times));
|
|
759
816
|
|
|
760
817
|
return block > step ? block - step : 0n;
|
|
761
818
|
};
|
|
762
|
-
let start =
|
|
819
|
+
let start = back(head, seconds * 1000 - atMs + START_MS, 1);
|
|
763
820
|
|
|
764
|
-
for (let
|
|
821
|
+
for (let times = 2; start > 0n; times *= 2) {
|
|
765
822
|
const at = await blockTime(read, url, start, cache);
|
|
766
823
|
|
|
767
824
|
if (at <= atMs) break;
|
|
768
825
|
|
|
769
|
-
start =
|
|
826
|
+
start = back(start, at - atMs + START_MS, times);
|
|
770
827
|
}
|
|
771
828
|
|
|
772
829
|
return { head, start };
|
|
@@ -775,37 +832,43 @@ async function startBefore(read, url, atMs, cache) {
|
|
|
775
832
|
/**
|
|
776
833
|
* The credits asked for from block `start`, a window of blocks at a time, at
|
|
777
834
|
* most `maxWindows`, until `head`, `enough`, or a window ending after
|
|
778
|
-
* `until` (`covered
|
|
779
|
-
*
|
|
780
|
-
*
|
|
781
|
-
*
|
|
835
|
+
* `until` (`covered`, as the block before `start` may already be, where a
|
|
836
|
+
* search before stopped there): each checked against what was asked, with
|
|
837
|
+
* its block's time. Each window is `size` blocks wide, twice as wide after
|
|
838
|
+
* one the RPC answered whole, up to `MAX_WINDOW`, and narrower than any it
|
|
839
|
+
* refused (`refused`, the narrowest). One it won't search at once, or one
|
|
840
|
+
* wider than `LOG_WINDOW` that isn't answered, is asked again a quarter as
|
|
841
|
+
* wide, or as wide as the widest it answered where that's wider: `size` and
|
|
842
|
+
* `refused` are as the search leaves them. The window ending at `head`
|
|
782
843
|
* reaches back up to `overlap` blocks from it, no wider than `size`, though
|
|
783
844
|
* not under `floor`: those blocks are read again (from `low`). `asked`: the
|
|
784
845
|
* windows asked.
|
|
785
846
|
*
|
|
786
847
|
* @param {Reader} read
|
|
787
|
-
* @param {{ url: string, address: string, topics: ReadonlyArray<string | null>, wei: bigint, amountOf: (data: string) => bigint | null, start: bigint, floor: bigint, head: bigint, size
|
|
788
|
-
* @returns {Promise<{ asked: number, complete: boolean, covered: boolean, credits: Credit[], low: bigint | null, next: bigint, refused: bigint | null, size: bigint
|
|
848
|
+
* @param {{ url: string, address: string, topics: ReadonlyArray<string | null>, wei: bigint, amountOf: (data: string) => bigint | null, start: bigint, floor: bigint, head: bigint, size: bigint, refused: bigint | null, overlap?: bigint, maxWindows: number, blockTimes: BlockTimes | undefined, enough: (credits: Credit[]) => boolean, until: number | null, known: Credit[] }} search
|
|
849
|
+
* @returns {Promise<{ asked: number, complete: boolean, covered: boolean, credits: Credit[], low: bigint | null, next: bigint, refused: bigint | null, size: bigint }>}
|
|
789
850
|
*/
|
|
790
|
-
async function searchLogs(read, { address, amountOf, blockTimes, enough, floor, head, known, maxWindows, overlap = OVERLAP, size: width
|
|
851
|
+
async function searchLogs(read, { address, amountOf, blockTimes, enough, floor, head, known, maxWindows, overlap = OVERLAP, refused: kept, size: width, start, topics, until, url, wei }) {
|
|
791
852
|
// What an earlier search found before `start`.
|
|
792
853
|
/** @type {Credit[]} */
|
|
793
854
|
const credits = [...known];
|
|
794
855
|
let from = start;
|
|
795
856
|
let size = width;
|
|
857
|
+
let refused = kept;
|
|
796
858
|
let covered = false;
|
|
797
859
|
let asked = 0;
|
|
798
860
|
/** @type {bigint | null} */
|
|
799
861
|
let low = null;
|
|
800
862
|
/** @type {bigint | null} */
|
|
801
|
-
let refused = null;
|
|
802
|
-
/** @type {bigint | null} */
|
|
803
863
|
let took = null;
|
|
804
864
|
|
|
805
|
-
|
|
865
|
+
if (until !== null && start > floor && start <= head) covered = (await blockTime(read, url, start - 1n, blockTimes)) > until;
|
|
866
|
+
|
|
867
|
+
while (!covered && asked < maxWindows && from <= head) {
|
|
806
868
|
const to = from + size - 1n < head ? from + size - 1n : head;
|
|
807
869
|
const back = head + 1n - (size < overlap ? size : overlap);
|
|
808
870
|
const lo = to === head && back < from ? (back > floor ? back : floor) : from;
|
|
871
|
+
const blocks = to - lo + 1n;
|
|
809
872
|
/** @type {any} */
|
|
810
873
|
let logs;
|
|
811
874
|
|
|
@@ -814,9 +877,11 @@ async function searchLogs(read, { address, amountOf, blockTimes, enough, floor,
|
|
|
814
877
|
try {
|
|
815
878
|
logs = await read.rpc(url, "eth_getLogs", [{ address, fromBlock: hex(lo), toBlock: hex(to), topics }]);
|
|
816
879
|
} catch (error) {
|
|
817
|
-
if (
|
|
818
|
-
|
|
819
|
-
|
|
880
|
+
if (blocks > MIN_WINDOW && (blocks > BigInt(LOG_WINDOW) || RANGE_REFUSED.test(messageOf(error)))) {
|
|
881
|
+
const fits = took !== null && took < blocks && took > blocks / 4n ? took : blocks / 4n;
|
|
882
|
+
|
|
883
|
+
if (refused === null || blocks < refused) refused = blocks;
|
|
884
|
+
size = fits > MIN_WINDOW ? fits : MIN_WINDOW;
|
|
820
885
|
continue;
|
|
821
886
|
}
|
|
822
887
|
|
|
@@ -824,7 +889,14 @@ async function searchLogs(read, { address, amountOf, blockTimes, enough, floor,
|
|
|
824
889
|
}
|
|
825
890
|
|
|
826
891
|
if (to === head) low = lo;
|
|
827
|
-
if (took === null ||
|
|
892
|
+
if (took === null || blocks > took) took = blocks;
|
|
893
|
+
|
|
894
|
+
// Answered whole: the next one twice as wide, while narrower than any refused.
|
|
895
|
+
if (blocks === size) {
|
|
896
|
+
const wider = size * 2n < MAX_WINDOW ? size * 2n : MAX_WINDOW;
|
|
897
|
+
|
|
898
|
+
if (refused === null || wider < refused) size = wider;
|
|
899
|
+
}
|
|
828
900
|
|
|
829
901
|
for (const log of Array.isArray(logs) ? logs : []) {
|
|
830
902
|
const found = creditLog(log, { address, amountOf, from: lo, to, topics, wei });
|
|
@@ -844,14 +916,15 @@ async function searchLogs(read, { address, amountOf, blockTimes, enough, floor,
|
|
|
844
916
|
}
|
|
845
917
|
}
|
|
846
918
|
|
|
847
|
-
return { asked, complete: from > head, covered, credits, low, next: from, refused, size
|
|
919
|
+
return { asked, complete: from > head, covered, credits, low, next: from, refused, size };
|
|
848
920
|
}
|
|
849
921
|
|
|
850
922
|
/**
|
|
851
923
|
* A search's passes over its blocks again, as a result gave them: where the
|
|
852
924
|
* one under way is (null between two), when the next begins, how many are
|
|
853
|
-
* done, when the search first reached the head, and the
|
|
854
|
-
*
|
|
925
|
+
* done, when the search first reached the head, and the width of the
|
|
926
|
+
* windows it read them in, as an earlier version kept it; null for anything
|
|
927
|
+
* else.
|
|
855
928
|
*
|
|
856
929
|
* @param {unknown} value
|
|
857
930
|
* @returns {{ at: number, from: bigint | null, passes: number, since: number, size: number | null } | null}
|
|
@@ -875,8 +948,8 @@ function recheckOf(value) {
|
|
|
875
948
|
* @param {NonNullable<ReturnType<typeof recheckOf>>} recheck
|
|
876
949
|
* @returns {NonNullable<Scan["recheck"]>}
|
|
877
950
|
*/
|
|
878
|
-
function recheckIn({ at, from, passes, since
|
|
879
|
-
return { at, from: from === null ? null : from.toString(), passes, since
|
|
951
|
+
function recheckIn({ at, from, passes, since }) {
|
|
952
|
+
return { at, from: from === null ? null : from.toString(), passes, since };
|
|
880
953
|
}
|
|
881
954
|
|
|
882
955
|
/**
|
|
@@ -892,9 +965,10 @@ function recheckIn({ at, from, passes, since, size }) {
|
|
|
892
965
|
* time: the first a minute later, each after as long again as it has been
|
|
893
966
|
* since then, and one after the other, as far as its credit, while an
|
|
894
967
|
* arrival is held back. A pass under way takes what the search ahead leaves
|
|
895
|
-
* of a call's windows, one at least
|
|
896
|
-
*
|
|
897
|
-
*
|
|
968
|
+
* of a call's windows, one at least. A pass counts once it read the blocks
|
|
969
|
+
* again, as does a call whose window at the head reached back to where the
|
|
970
|
+
* search began. Both go on in windows as wide as the searches before left
|
|
971
|
+
* them, narrower than the narrowest the RPC refused.
|
|
898
972
|
*
|
|
899
973
|
* @param {Reader} read
|
|
900
974
|
* @param {{ given: Queued[], queue: Queued[], guarded: boolean, unsure: ReadonlySet<string>, rivals?: ReadonlySet<string>, filter: { address: string, topics: ReadonlyArray<string | null>, wei: bigint, amountOf: (data: string) => bigint | null } }} group
|
|
@@ -922,10 +996,10 @@ async function followLogs(read, { filter, given, guarded, queue, rivals, unsure
|
|
|
922
996
|
// The logs searched for: a search for others, as for another sender or
|
|
923
997
|
// amount, isn't carried on.
|
|
924
998
|
const search = [filter.address, ...filter.topics.map((topic) => topic ?? "*"), filter.wei.toString()].join(":");
|
|
925
|
-
/** @type {{ basis: number, from: string, seen?: Scan["seen"], start?: string, recheck?: Scan["recheck"] }[]} */
|
|
999
|
+
/** @type {{ basis: number, from: string, seen?: Scan["seen"], start?: string, recheck?: Scan["recheck"], size?: number, refused?: number }[]} */
|
|
926
1000
|
const resumed = live.flatMap(({ scan }) =>
|
|
927
1001
|
scan?.from !== undefined && scan.search === search && scan.basis !== undefined && scan.basis <= first.at
|
|
928
|
-
? [{ basis: scan.basis, from: scan.from, recheck: scan.recheck, seen: scan.seen, start: scan.start }]
|
|
1002
|
+
? [{ basis: scan.basis, from: scan.from, recheck: scan.recheck, refused: scan.refused, seen: scan.seen, size: scan.size, start: scan.start }]
|
|
929
1003
|
: []
|
|
930
1004
|
);
|
|
931
1005
|
/** @type {Map<string, Credit>} */
|
|
@@ -990,9 +1064,11 @@ async function followLogs(read, { filter, given, guarded, queue, rivals, unsure
|
|
|
990
1064
|
// No later than a credit found before: the blocks are read again from it.
|
|
991
1065
|
for (const credit of carried.values()) if (credit.block !== null && credit.block < floor) floor = credit.block;
|
|
992
1066
|
|
|
993
|
-
// The
|
|
994
|
-
//
|
|
995
|
-
const
|
|
1067
|
+
// The width of the windows, and the narrowest the RPC refused, as the
|
|
1068
|
+
// searches before left them: the narrowest of each.
|
|
1069
|
+
const sizes = resumed.flatMap((scan) => (scan.size === undefined ? [] : [BigInt(scan.size)]));
|
|
1070
|
+
const refusals = resumed.flatMap((scan) => (scan.refused === undefined ? [] : [BigInt(scan.refused)]));
|
|
1071
|
+
const width = sizes.length ? minOf(sizes) : BigInt(LOG_WINDOW);
|
|
996
1072
|
|
|
997
1073
|
/**
|
|
998
1074
|
* The given transfers' credits with `found`: those that arrived, by their
|
|
@@ -1060,7 +1136,8 @@ async function followLogs(read, { filter, given, guarded, queue, rivals, unsure
|
|
|
1060
1136
|
head,
|
|
1061
1137
|
known: [...carried.values()],
|
|
1062
1138
|
maxWindows,
|
|
1063
|
-
|
|
1139
|
+
refused: refusals.length ? minOf(refusals) : null,
|
|
1140
|
+
size: width,
|
|
1064
1141
|
start,
|
|
1065
1142
|
until,
|
|
1066
1143
|
url,
|
|
@@ -1075,7 +1152,7 @@ async function followLogs(read, { filter, given, guarded, queue, rivals, unsure
|
|
|
1075
1152
|
|
|
1076
1153
|
let credits = mark(ahead.credits);
|
|
1077
1154
|
const before = settled(credits, seen);
|
|
1078
|
-
let { refused,
|
|
1155
|
+
let { refused, size } = ahead;
|
|
1079
1156
|
|
|
1080
1157
|
if ((seen || before.held.size) && recheck === null) recheck = { at: before.held.size ? now : now + RECHECK_MS, from: null, passes: 0, since: now, size: null };
|
|
1081
1158
|
|
|
@@ -1096,7 +1173,8 @@ async function followLogs(read, { filter, given, guarded, queue, rivals, unsure
|
|
|
1096
1173
|
head: before.top !== null && before.top < below ? before.top : below - 1n,
|
|
1097
1174
|
known: credits,
|
|
1098
1175
|
maxWindows: maxWindows < 1 ? 0 : Math.max(1, maxWindows - ahead.asked),
|
|
1099
|
-
|
|
1176
|
+
refused,
|
|
1177
|
+
size,
|
|
1100
1178
|
start: from,
|
|
1101
1179
|
until,
|
|
1102
1180
|
url,
|
|
@@ -1108,18 +1186,7 @@ async function followLogs(read, { filter, given, guarded, queue, rivals, unsure
|
|
|
1108
1186
|
|
|
1109
1187
|
recheck = done ? { ...recheck, at: now + Math.max(RECHECK_MS, now - recheck.since), from: null, passes: recheck.passes + (counted ? 1 : 0) } : { ...recheck, from: pass.next };
|
|
1110
1188
|
credits = mark(pass.credits);
|
|
1111
|
-
|
|
1112
|
-
if (pass.took !== null && (took === null || pass.took > took)) took = pass.took;
|
|
1113
|
-
}
|
|
1114
|
-
|
|
1115
|
-
// A window refused: from the next call on, the blocks are read again in
|
|
1116
|
-
// ones as wide as the widest it took narrower than that, else a quarter
|
|
1117
|
-
// as wide.
|
|
1118
|
-
if (recheck !== null && refused !== null) {
|
|
1119
|
-
const fits = took !== null && took < refused ? took : refused / 4n;
|
|
1120
|
-
const narrower = fits > MIN_WINDOW ? fits : MIN_WINDOW;
|
|
1121
|
-
|
|
1122
|
-
if (narrower < width) recheck = { ...recheck, size: Number(narrower) };
|
|
1189
|
+
({ refused, size } = pass);
|
|
1123
1190
|
}
|
|
1124
1191
|
|
|
1125
1192
|
const { arrived, assigned, held, verdicts } = settled(credits, seen);
|
|
@@ -1138,8 +1205,10 @@ async function followLogs(read, { filter, given, guarded, queue, rivals, unsure
|
|
|
1138
1205
|
basis: first.at,
|
|
1139
1206
|
from: next.toString(),
|
|
1140
1207
|
...(recheck ? { recheck: recheckIn(recheck) } : {}),
|
|
1208
|
+
...(refused === null ? {} : { refused: Number(refused) }),
|
|
1141
1209
|
search,
|
|
1142
1210
|
seen: credits.flatMap((credit) => (credit.at >= reach && credit.block !== null ? [{ at: credit.at, block: credit.block.toString(), hash: credit.hash, pass: credit.pass ?? 0, sender: credit.from }] : [])),
|
|
1211
|
+
size: Number(size),
|
|
1143
1212
|
start: floor.toString(),
|
|
1144
1213
|
};
|
|
1145
1214
|
|
|
@@ -1209,19 +1278,24 @@ function exactJson(text) {
|
|
|
1209
1278
|
return JSON.parse(text.replace(TOKEN, (match, string) => string ?? (/^-?\d+$/.test(match) && !Number.isSafeInteger(Number(match)) ? `"${match}"` : match)));
|
|
1210
1279
|
}
|
|
1211
1280
|
|
|
1212
|
-
const utf8 = new TextDecoder();
|
|
1281
|
+
const utf8 = new TextDecoder("utf-8", { fatal: true });
|
|
1213
1282
|
|
|
1214
1283
|
/**
|
|
1215
|
-
* A memo's bytes, given as hex, as text
|
|
1216
|
-
*
|
|
1217
|
-
* character; "" for none.
|
|
1284
|
+
* A memo's bytes, given as hex, as UTF-8 text; "" for none, and for bytes
|
|
1285
|
+
* that aren't UTF-8: an address or a hash is read from text only.
|
|
1218
1286
|
*
|
|
1219
1287
|
* @param {unknown} hex
|
|
1220
1288
|
*/
|
|
1221
1289
|
function memoOf(hex) {
|
|
1222
1290
|
const text = String(hex ?? "").replace(/^0x/i, "");
|
|
1223
1291
|
|
|
1224
|
-
|
|
1292
|
+
if (!/^(?:[0-9a-fA-F]{2})+$/.test(text)) return "";
|
|
1293
|
+
|
|
1294
|
+
try {
|
|
1295
|
+
return utf8.decode(Uint8Array.from(text.match(/../g) ?? [], (byte) => parseInt(byte, 16)));
|
|
1296
|
+
} catch {
|
|
1297
|
+
return "";
|
|
1298
|
+
}
|
|
1225
1299
|
}
|
|
1226
1300
|
|
|
1227
1301
|
/**
|
|
@@ -1486,7 +1560,8 @@ async function depositPeers(archive, net, found, { amount, bounds, calls, suppli
|
|
|
1486
1560
|
}
|
|
1487
1561
|
|
|
1488
1562
|
/**
|
|
1489
|
-
* Where a scan's search stopped, without its
|
|
1563
|
+
* Where a scan's search stopped, with the width of its windows, without its
|
|
1564
|
+
* history.
|
|
1490
1565
|
*
|
|
1491
1566
|
* @param {Scan | null | undefined} scan
|
|
1492
1567
|
* @returns {Scan}
|
|
@@ -1499,6 +1574,8 @@ function searchOf(scan) {
|
|
|
1499
1574
|
|
|
1500
1575
|
if (scan.start !== undefined) out.start = scan.start;
|
|
1501
1576
|
if (scan.recheck !== undefined) out.recheck = scan.recheck;
|
|
1577
|
+
if (scan.size !== undefined) out.size = scan.size;
|
|
1578
|
+
if (scan.refused !== undefined) out.refused = scan.refused;
|
|
1502
1579
|
|
|
1503
1580
|
return out;
|
|
1504
1581
|
}
|
|
@@ -2072,6 +2149,12 @@ export async function serviceArrivals({
|
|
|
2072
2149
|
}
|
|
2073
2150
|
|
|
2074
2151
|
const live = triage(known, results, { now, waitMs });
|
|
2152
|
+
// The block times the app keeps for a route's chain: by its chain ID, or
|
|
2153
|
+
// the one cache given, where the network's service routes are all on one
|
|
2154
|
+
// chain.
|
|
2155
|
+
const oneChain = new Set(services.map((route) => route.chain?.chainId)).size === 1;
|
|
2156
|
+
/** @param {Route} route @returns {BlockTimes | undefined} */
|
|
2157
|
+
const timesOf = (route) => (typeof blockTimes === "function" ? (blockTimes(route.chain.chainId) ?? undefined) : oneChain ? blockTimes : undefined);
|
|
2075
2158
|
// The fee of each transfer the app gave, those no longer looked for too,
|
|
2076
2159
|
// and the block its payout can't be before.
|
|
2077
2160
|
const feeOf = new Map(routed.map((item) => [item.hash, item.fee ?? /** @type {Route & { kind: "service" }} */ (item.route).feeLux]));
|
|
@@ -2144,7 +2227,7 @@ export async function serviceArrivals({
|
|
|
2144
2227
|
queue: history.queue,
|
|
2145
2228
|
unsure,
|
|
2146
2229
|
},
|
|
2147
|
-
{ blockTimes, maxWindows, now, slackMs: CLOCK_SLACK_MS, url: String(rpcUrl ?? route.chain.rpcUrl), used, windowMs: CREDIT_WINDOW_MS }
|
|
2230
|
+
{ blockTimes: timesOf(route), maxWindows, now, slackMs: CLOCK_SLACK_MS, url: String(rpcUrl ?? route.chain.rpcUrl), used, windowMs: CREDIT_WINDOW_MS }
|
|
2148
2231
|
);
|
|
2149
2232
|
|
|
2150
2233
|
for (const [hash, arrival] of found) results.set(hash, withHistory(hash, arrival, given, history.states));
|
|
@@ -2373,8 +2456,9 @@ export function serviceTransferOf(entry, network) {
|
|
|
2373
2456
|
const spent = luxOf(entry.gasSpent);
|
|
2374
2457
|
const price = luxOf(entry.gasPrice);
|
|
2375
2458
|
const amount = limit !== null && spent !== null && price !== null && (limit - spent) * price <= 0n ? value : null;
|
|
2376
|
-
// The address the memo gives, exactly as the bridge reads it.
|
|
2377
|
-
|
|
2459
|
+
// The address the memo gives, exactly as the bridge reads it: from text.
|
|
2460
|
+
// A memo of bytes that aren't text, which /history gives as 0x hex, gives none.
|
|
2461
|
+
const memo = typeof entry.memo === "string" && entry.memoBinary !== true ? entry.memo : "";
|
|
2378
2462
|
const to = EVM_ADDRESS.test(memo) ? memo : null;
|
|
2379
2463
|
const problem = entry.success === false ? "failed" : value <= route.feeLux ? "under-fee" : to === null ? "no-address" : null;
|
|
2380
2464
|
|
|
@@ -2392,9 +2476,8 @@ export function migrationPayoutOf(entry, network) {
|
|
|
2392
2476
|
const { at, hash, height, value } = factsOf(entry);
|
|
2393
2477
|
// What the transfer paid: a gas refund paid to the account with it aside.
|
|
2394
2478
|
const refund = entry?.refund === undefined || entry.refund === null ? 0n : luxOf(entry.refund);
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
.toLowerCase();
|
|
2479
|
+
// The migration's hash, as text: a memo of bytes that aren't text names none.
|
|
2480
|
+
const memo = entry?.memoBinary === true ? "" : String(entry?.memo ?? "").trim().toLowerCase();
|
|
2398
2481
|
|
|
2399
2482
|
if (!paying.length || entry.success === false || !hash || height === null || value === null || refund === null || refund > value || !EVM_HASH.test(memo)) {
|
|
2400
2483
|
return null;
|
|
@@ -359,7 +359,11 @@ export type WithdrawalsOptions = {
|
|
|
359
359
|
maxBatch?: number;
|
|
360
360
|
/** How many of the newest games a proof is looked for among: 64 by default, and for anything but a positive whole number. */
|
|
361
361
|
gameSearchDepth?: number | bigint;
|
|
362
|
-
/**
|
|
362
|
+
/**
|
|
363
|
+
* Where the deployment's fixed facts are kept: each game's hub, each game
|
|
364
|
+
* type's hub, each hub's clock limit, and each withdrawal's receipt once
|
|
365
|
+
* its DuskEVM block is final.
|
|
366
|
+
*/
|
|
363
367
|
cache?: FactCache;
|
|
364
368
|
/** The clock, in milliseconds. */
|
|
365
369
|
now?: () => number;
|
|
@@ -368,7 +372,14 @@ export type WithdrawalsOptions = {
|
|
|
368
372
|
/** A network's withdrawals, read on its Dusk node and DuskEVM. */
|
|
369
373
|
export type Withdrawals = {
|
|
370
374
|
readonly network: Network;
|
|
371
|
-
/**
|
|
375
|
+
/**
|
|
376
|
+
* A DuskEVM transaction's withdrawal, from its receipt; refuses as
|
|
377
|
+
* `parseWithdrawalReceipt` does. The receipt is read after DuskEVM's
|
|
378
|
+
* finalized block, which the receipts read at the same time share, and
|
|
379
|
+
* kept in `cache` once its block is at or under that one: a final
|
|
380
|
+
* receipt is read once. One that isn't final yet, or isn't a
|
|
381
|
+
* withdrawal's, is read again each time.
|
|
382
|
+
*/
|
|
372
383
|
receipt(txHash: string): Promise<ReceiptWithdrawal>;
|
|
373
384
|
/**
|
|
374
385
|
* Where a withdrawal is, for `submitter` (a Dusk account, or its
|
|
@@ -458,6 +469,13 @@ export type WithdrawalScan = {
|
|
|
458
469
|
target: number | null;
|
|
459
470
|
/** The blocks one search asks for. */
|
|
460
471
|
window: number;
|
|
472
|
+
/**
|
|
473
|
+
* Its passes over the final blocks, read again once they're all covered:
|
|
474
|
+
* the highest block the one under way reads next, downwards (`from`, null
|
|
475
|
+
* between two), when the next one begins (`at`), and when the blocks were
|
|
476
|
+
* first all covered (`since`), in milliseconds.
|
|
477
|
+
*/
|
|
478
|
+
recheck?: { from: number | null; at: number; since: number };
|
|
461
479
|
updatedAt?: number;
|
|
462
480
|
};
|
|
463
481
|
|
|
@@ -465,10 +483,17 @@ export type WithdrawalScan = {
|
|
|
465
483
|
* One run over an address's withdrawals, from the bridge's own event
|
|
466
484
|
* (`ETHBridgeInitiated`, by its sender), the newest final blocks first, a
|
|
467
485
|
* window at a time, narrower where the RPC asks for it, within
|
|
468
|
-
* `maxRequests
|
|
469
|
-
* be reorged away.
|
|
470
|
-
*
|
|
471
|
-
*
|
|
486
|
+
* `maxRequests`, the head's requests among them. Only final blocks count as
|
|
487
|
+
* covered: those above can still be reorged away. Each run reads the
|
|
488
|
+
* newest 50,000 final blocks, the new ones first, in as many windows as they
|
|
489
|
+
* take though in half its requests at most (while older blocks are still to
|
|
490
|
+
* be read, only once the final head has moved), and carries on below; under
|
|
491
|
+
* a final head lower than the last run's, what was covered and found above
|
|
492
|
+
* it is dropped, and read again. Once all the final blocks are
|
|
493
|
+
* covered, they're read again from the newest down, a pass at a time with
|
|
494
|
+
* the requests a run has left: the first an hour later, each after as long
|
|
495
|
+
* again as it has been since. A withdrawal is found once: one read again
|
|
496
|
+
* isn't found again.
|
|
472
497
|
*/
|
|
473
498
|
export declare function syncWithdrawals(options: {
|
|
474
499
|
/** The DuskEVM address that sent them. */
|