smail-sdk 0.1.0 → 0.1.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/dist/pool-index.d.ts +10 -1
- package/dist/pool-index.js +45 -12
- package/package.json +1 -1
package/dist/pool-index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Address, type Hex } from "viem";
|
|
2
|
-
import {
|
|
2
|
+
import type { LogWindowOptions } from "./events.js";
|
|
3
3
|
import { type MerklePath, type PoolDeposit } from "./private.js";
|
|
4
4
|
import type { SmailReadClient } from "./read.js";
|
|
5
5
|
/**
|
|
@@ -23,6 +23,13 @@ import type { SmailReadClient } from "./read.js";
|
|
|
23
23
|
* claimant rehashes its own chunk against the stored chunk root, and the
|
|
24
24
|
* root the path reaches must be one the pool itself knows.
|
|
25
25
|
*/
|
|
26
|
+
/**
|
|
27
|
+
* Blocks asked for in one `eth_getLogs` while reading a pool. Wide on purpose:
|
|
28
|
+
* a pool's log is sparse, and a node that refuses a range is asked for each
|
|
29
|
+
* half instead, so a strict node costs a few extra calls and a generous one
|
|
30
|
+
* answers the pool's whole life in a handful.
|
|
31
|
+
*/
|
|
32
|
+
export declare const POOL_LOG_WINDOW = 1000000;
|
|
26
33
|
export declare const CHUNK_LEVELS = 10;
|
|
27
34
|
export declare const CHUNK_SIZE: number;
|
|
28
35
|
export interface SpentEntry {
|
|
@@ -71,6 +78,8 @@ export declare function emptyPoolIndex(pool: Address, firstBlock: bigint): PoolI
|
|
|
71
78
|
*/
|
|
72
79
|
export declare function syncPoolIndex(client: Pick<SmailReadClient, "getLogs" | "getBlockNumber">, base: PoolIndex, options?: LogWindowOptions & {
|
|
73
80
|
onProgress?: (done: bigint, total: bigint) => void;
|
|
81
|
+
/** Windows read at once. One by default; a terminal with a long way to catch up asks for more. */
|
|
82
|
+
concurrency?: number;
|
|
74
83
|
}): Promise<PoolIndex>;
|
|
75
84
|
/**
|
|
76
85
|
* The index as a header and two flat binary tables, the same shape for the
|
package/dist/pool-index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { poseidon2 } from "poseidon-lite";
|
|
2
2
|
import { getAbiItem } from "viem";
|
|
3
3
|
import { privateSnailAbi } from "./abi/index.js";
|
|
4
|
-
import { LOG_WINDOW, walkWindows } from "./events.js";
|
|
5
4
|
import { PRIVATE_LEVELS, ZERO_VALUE } from "./private.js";
|
|
6
5
|
/**
|
|
7
6
|
* A private pool's full public history, kept so a claim never has to re-read
|
|
@@ -24,6 +23,13 @@ import { PRIVATE_LEVELS, ZERO_VALUE } from "./private.js";
|
|
|
24
23
|
* claimant rehashes its own chunk against the stored chunk root, and the
|
|
25
24
|
* root the path reaches must be one the pool itself knows.
|
|
26
25
|
*/
|
|
26
|
+
/**
|
|
27
|
+
* Blocks asked for in one `eth_getLogs` while reading a pool. Wide on purpose:
|
|
28
|
+
* a pool's log is sparse, and a node that refuses a range is asked for each
|
|
29
|
+
* half instead, so a strict node costs a few extra calls and a generous one
|
|
30
|
+
* answers the pool's whole life in a handful.
|
|
31
|
+
*/
|
|
32
|
+
export const POOL_LOG_WINDOW = 1_000_000;
|
|
27
33
|
export const CHUNK_LEVELS = 10;
|
|
28
34
|
export const CHUNK_SIZE = 1 << CHUNK_LEVELS;
|
|
29
35
|
const DEPOSIT = getAbiItem({ abi: privateSnailAbi, name: "Deposit" });
|
|
@@ -123,11 +129,23 @@ export async function syncPoolIndex(client, base, options = {}) {
|
|
|
123
129
|
const from = base.toBlock + 1n;
|
|
124
130
|
const added = [];
|
|
125
131
|
const spent = new Map(base.spent);
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
132
|
+
const read = async (start, end) => {
|
|
133
|
+
let deposits, withdrawals;
|
|
134
|
+
try {
|
|
135
|
+
[deposits, withdrawals] = await Promise.all([
|
|
136
|
+
client.getLogs({ address: base.pool, event: DEPOSIT, fromBlock: start, toBlock: end }),
|
|
137
|
+
client.getLogs({ address: base.pool, event: WITHDRAWAL, fromBlock: start, toBlock: end }),
|
|
138
|
+
]);
|
|
139
|
+
}
|
|
140
|
+
catch (cause) {
|
|
141
|
+
// A node that refuses the range is asked for each half instead.
|
|
142
|
+
if (start === end)
|
|
143
|
+
throw cause;
|
|
144
|
+
const middle = start + (end - start) / 2n;
|
|
145
|
+
await read(start, middle);
|
|
146
|
+
await read(middle + 1n, end);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
131
149
|
for (const log of deposits) {
|
|
132
150
|
const args = log.args;
|
|
133
151
|
added.push({
|
|
@@ -144,9 +162,24 @@ export async function syncPoolIndex(client, base, options = {}) {
|
|
|
144
162
|
const args = log.args;
|
|
145
163
|
spent.set(BigInt(args.nullifierHash), { recipient: args.recipient, transactionHash: log.transactionHash ?? "0x" });
|
|
146
164
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
165
|
+
};
|
|
166
|
+
// The range in windows, read `concurrency` at a time. Order does not matter:
|
|
167
|
+
// deposits are sorted by leaf index below, and a nullifier is spent once.
|
|
168
|
+
const width = BigInt(options.window ?? POOL_LOG_WINDOW);
|
|
169
|
+
const windows = [];
|
|
170
|
+
for (let start = from; start <= to; start += width)
|
|
171
|
+
windows.push([start, start + width - 1n < to ? start + width - 1n : to]);
|
|
172
|
+
let next = 0;
|
|
173
|
+
let done = 0n;
|
|
174
|
+
const worker = async () => {
|
|
175
|
+
while (next < windows.length) {
|
|
176
|
+
const [start, end] = windows[next++];
|
|
177
|
+
await read(start, end);
|
|
178
|
+
done += end - start + 1n;
|
|
179
|
+
options.onProgress?.(done, to - from + 1n);
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
await Promise.all(Array.from({ length: Math.max(1, Math.min(options.concurrency ?? 1, windows.length)) }, worker));
|
|
150
183
|
added.sort((a, b) => a.leafIndex - b.leafIndex);
|
|
151
184
|
const deposits = base.deposits.concat(added);
|
|
152
185
|
for (let i = base.deposits.length; i < deposits.length; i++) {
|
|
@@ -154,14 +187,14 @@ export async function syncPoolIndex(client, base, options = {}) {
|
|
|
154
187
|
throw new PoolIndexMismatch(`Deposit ${i} is missing; the tree cannot be rebuilt.`);
|
|
155
188
|
}
|
|
156
189
|
const chunkRoots = base.chunkRoots.slice();
|
|
157
|
-
const
|
|
190
|
+
const synced = { pool: base.pool, toBlock: to, deposits, spent, chunkRoots };
|
|
158
191
|
if (added.length) {
|
|
159
192
|
const first = Math.floor(base.deposits.length / CHUNK_SIZE);
|
|
160
193
|
const last = Math.floor((deposits.length - 1) / CHUNK_SIZE);
|
|
161
194
|
for (let chunk = first; chunk <= last; chunk++)
|
|
162
|
-
chunkRoots[chunk] = chunkRootOf(chunkLeaves(
|
|
195
|
+
chunkRoots[chunk] = chunkRootOf(chunkLeaves(synced, chunk));
|
|
163
196
|
}
|
|
164
|
-
return
|
|
197
|
+
return synced;
|
|
165
198
|
}
|
|
166
199
|
const DEPOSIT_BYTES = 66;
|
|
167
200
|
const SPENT_BYTES = 84;
|
package/package.json
CHANGED