@waku/rln 0.0.14-88a28a1 → 0.1.0-b7cb3f9
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/README.md +5 -5
- package/bundle/assets/{rln-fb4d7b4b.wasm → rln-6ded2896.wasm} +0 -0
- package/bundle/assets/rln_final-8b299152.zkey +0 -0
- package/bundle/assets/rln_wasm_bg-6f96f821.wasm +0 -0
- package/bundle/index.js +489 -213
- package/dist/.tsbuildinfo +1 -1
- package/dist/codec.d.ts +4 -4
- package/dist/codec.js +4 -4
- package/dist/codec.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/resources/rln.wasm +0 -0
- package/dist/resources/rln_final.zkey +0 -0
- package/dist/resources/verification_key.js +63 -72
- package/dist/resources/verification_key.js.map +1 -1
- package/dist/rln.d.ts +22 -8
- package/dist/rln.js +56 -20
- package/dist/rln.js.map +1 -1
- package/dist/rln_contract.d.ts +8 -3
- package/dist/rln_contract.js +59 -6
- package/dist/rln_contract.js.map +1 -1
- package/dist/root_tracker.d.ts +10 -0
- package/dist/root_tracker.js +72 -0
- package/dist/root_tracker.js.map +1 -0
- package/package.json +7 -7
- package/src/codec.ts +7 -7
- package/src/index.ts +10 -3
- package/src/rln.ts +83 -20
- package/src/rln_contract.ts +104 -12
- package/src/root_tracker.ts +88 -0
- package/bundle/assets/rln_final-a641c06e.zkey +0 -0
- package/bundle/assets/rln_wasm_bg-4d82d6a4.wasm +0 -0
@@ -0,0 +1,72 @@
|
|
1
|
+
class RootPerBlock {
|
2
|
+
constructor(root, blockNumber) {
|
3
|
+
this.root = root;
|
4
|
+
this.blockNumber = blockNumber;
|
5
|
+
}
|
6
|
+
}
|
7
|
+
const maxBufferSize = 20;
|
8
|
+
export class MerkleRootTracker {
|
9
|
+
constructor(acceptableRootWindowSize, initialRoot) {
|
10
|
+
this.acceptableRootWindowSize = acceptableRootWindowSize;
|
11
|
+
this.validMerkleRoots = new Array();
|
12
|
+
this.merkleRootBuffer = new Array();
|
13
|
+
this.pushRoot(0, initialRoot);
|
14
|
+
}
|
15
|
+
backFill(fromBlockNumber) {
|
16
|
+
if (this.validMerkleRoots.length == 0)
|
17
|
+
return;
|
18
|
+
let numBlocks = 0;
|
19
|
+
for (let i = this.validMerkleRoots.length - 1; i >= 0; i--) {
|
20
|
+
if (this.validMerkleRoots[i].blockNumber >= fromBlockNumber) {
|
21
|
+
numBlocks++;
|
22
|
+
}
|
23
|
+
}
|
24
|
+
if (numBlocks == 0)
|
25
|
+
return;
|
26
|
+
const olderBlock = fromBlockNumber < this.validMerkleRoots[0].blockNumber;
|
27
|
+
// Remove last roots
|
28
|
+
let rootsToPop = numBlocks;
|
29
|
+
if (this.validMerkleRoots.length < rootsToPop) {
|
30
|
+
rootsToPop = this.validMerkleRoots.length;
|
31
|
+
}
|
32
|
+
this.validMerkleRoots = this.validMerkleRoots.slice(0, this.validMerkleRoots.length - rootsToPop);
|
33
|
+
if (this.merkleRootBuffer.length == 0)
|
34
|
+
return;
|
35
|
+
if (olderBlock) {
|
36
|
+
const idx = this.merkleRootBuffer.findIndex((x) => x.blockNumber == fromBlockNumber);
|
37
|
+
if (idx > -1) {
|
38
|
+
this.merkleRootBuffer = this.merkleRootBuffer.slice(0, idx);
|
39
|
+
}
|
40
|
+
}
|
41
|
+
// Backfill the tree's acceptable roots
|
42
|
+
let rootsToRestore = this.acceptableRootWindowSize - this.validMerkleRoots.length;
|
43
|
+
if (this.merkleRootBuffer.length < rootsToRestore) {
|
44
|
+
rootsToRestore = this.merkleRootBuffer.length;
|
45
|
+
}
|
46
|
+
for (let i = 0; i < rootsToRestore; i++) {
|
47
|
+
const x = this.merkleRootBuffer.pop();
|
48
|
+
if (x)
|
49
|
+
this.validMerkleRoots.unshift(x);
|
50
|
+
}
|
51
|
+
}
|
52
|
+
pushRoot(blockNumber, root) {
|
53
|
+
this.validMerkleRoots.push(new RootPerBlock(root, blockNumber));
|
54
|
+
// Maintain valid merkle root window
|
55
|
+
if (this.validMerkleRoots.length > this.acceptableRootWindowSize) {
|
56
|
+
const x = this.validMerkleRoots.shift();
|
57
|
+
if (x)
|
58
|
+
this.merkleRootBuffer.push(x);
|
59
|
+
}
|
60
|
+
// Maintain merkle root buffer
|
61
|
+
if (this.merkleRootBuffer.length > maxBufferSize) {
|
62
|
+
this.merkleRootBuffer.shift();
|
63
|
+
}
|
64
|
+
}
|
65
|
+
roots() {
|
66
|
+
return this.validMerkleRoots.map((x) => x.root);
|
67
|
+
}
|
68
|
+
buffer() {
|
69
|
+
return this.merkleRootBuffer.map((x) => x.root);
|
70
|
+
}
|
71
|
+
}
|
72
|
+
//# sourceMappingURL=root_tracker.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"root_tracker.js","sourceRoot":"","sources":["../src/root_tracker.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@waku/rln",
|
3
|
-
"version": "0.0
|
3
|
+
"version": "0.1.0-b7cb3f9",
|
4
4
|
"description": "Rate Limit Nullifier for js-waku",
|
5
5
|
"types": "./dist/index.d.ts",
|
6
6
|
"module": "./dist/index.js",
|
@@ -83,9 +83,9 @@
|
|
83
83
|
"husky": "^7.0.4",
|
84
84
|
"ignore-loader": "^0.1.2",
|
85
85
|
"isomorphic-fetch": "^3.0.0",
|
86
|
-
"@waku/interfaces": "^0.0.
|
87
|
-
"@waku/message-encryption": "^0.0.
|
88
|
-
"@waku/core": "^0.0.
|
86
|
+
"@waku/interfaces": "^0.0.12",
|
87
|
+
"@waku/message-encryption": "^0.0.15",
|
88
|
+
"@waku/core": "^0.0.17",
|
89
89
|
"jsdom": "^19.0.0",
|
90
90
|
"jsdom-global": "^3.0.2",
|
91
91
|
"karma": "^6.3.12",
|
@@ -93,7 +93,7 @@
|
|
93
93
|
"karma-mocha": "^2.0.1",
|
94
94
|
"karma-webpack": "^5.0.0",
|
95
95
|
"lint-staged": "^13.0.3",
|
96
|
-
"mocha": "
|
96
|
+
"mocha": "10.1.0",
|
97
97
|
"npm-run-all": "^4.1.5",
|
98
98
|
"p-timeout": "^4.1.0",
|
99
99
|
"prettier": "^2.1.1",
|
@@ -129,8 +129,8 @@
|
|
129
129
|
]
|
130
130
|
},
|
131
131
|
"dependencies": {
|
132
|
-
"@waku/utils": "^0.0.
|
133
|
-
"@waku/zerokit-rln-wasm": "^0.0.
|
132
|
+
"@waku/utils": "^0.0.5",
|
133
|
+
"@waku/zerokit-rln-wasm": "^0.0.10",
|
134
134
|
"ethers": "^5.7.2"
|
135
135
|
}
|
136
136
|
}
|
package/src/codec.ts
CHANGED
@@ -9,21 +9,21 @@ import type {
|
|
9
9
|
import debug from "debug";
|
10
10
|
|
11
11
|
import { RlnMessage, toRLNSignal } from "./message.js";
|
12
|
-
import {
|
12
|
+
import { IdentityCredential, RLNInstance } from "./rln.js";
|
13
13
|
|
14
14
|
const log = debug("waku:rln:encoder");
|
15
15
|
|
16
16
|
export class RLNEncoder implements IEncoder {
|
17
|
-
private readonly
|
17
|
+
private readonly idSecretHash: Uint8Array;
|
18
18
|
|
19
19
|
constructor(
|
20
20
|
private encoder: IEncoder,
|
21
21
|
private rlnInstance: RLNInstance,
|
22
22
|
private index: number,
|
23
|
-
|
23
|
+
identityCredential: IdentityCredential
|
24
24
|
) {
|
25
25
|
if (index < 0) throw "invalid membership index";
|
26
|
-
this.
|
26
|
+
this.idSecretHash = identityCredential.IDSecretHash;
|
27
27
|
}
|
28
28
|
|
29
29
|
async toWire(message: IMessage): Promise<Uint8Array | undefined> {
|
@@ -50,7 +50,7 @@ export class RLNEncoder implements IEncoder {
|
|
50
50
|
signal,
|
51
51
|
this.index,
|
52
52
|
message.timestamp,
|
53
|
-
this.
|
53
|
+
this.idSecretHash
|
54
54
|
);
|
55
55
|
console.timeEnd("proof_gen_timer");
|
56
56
|
return proof;
|
@@ -69,7 +69,7 @@ type RLNEncoderOptions = {
|
|
69
69
|
encoder: IEncoder;
|
70
70
|
rlnInstance: RLNInstance;
|
71
71
|
index: number;
|
72
|
-
|
72
|
+
credential: IdentityCredential;
|
73
73
|
};
|
74
74
|
|
75
75
|
export const createRLNEncoder = (options: RLNEncoderOptions): RLNEncoder => {
|
@@ -77,7 +77,7 @@ export const createRLNEncoder = (options: RLNEncoderOptions): RLNEncoder => {
|
|
77
77
|
options.encoder,
|
78
78
|
options.rlnInstance,
|
79
79
|
options.index,
|
80
|
-
options.
|
80
|
+
options.credential
|
81
81
|
);
|
82
82
|
};
|
83
83
|
|
package/src/index.ts
CHANGED
@@ -1,8 +1,13 @@
|
|
1
1
|
import { RLNDecoder, RLNEncoder } from "./codec.js";
|
2
2
|
import { GOERLI_CONTRACT, RLN_ABI } from "./constants.js";
|
3
|
-
import {
|
4
|
-
|
3
|
+
import {
|
4
|
+
IdentityCredential,
|
5
|
+
Proof,
|
6
|
+
ProofMetadata,
|
7
|
+
RLNInstance,
|
8
|
+
} from "./rln.js";
|
5
9
|
import { RLNContract } from "./rln_contract.js";
|
10
|
+
import { MerkleRootTracker } from "./root_tracker.js";
|
6
11
|
|
7
12
|
// reexport the create function, dynamically imported from rln.ts
|
8
13
|
export async function create(): Promise<RLNInstance> {
|
@@ -15,10 +20,12 @@ export async function create(): Promise<RLNInstance> {
|
|
15
20
|
|
16
21
|
export {
|
17
22
|
RLNInstance,
|
18
|
-
|
23
|
+
IdentityCredential,
|
19
24
|
Proof,
|
25
|
+
ProofMetadata,
|
20
26
|
RLNEncoder,
|
21
27
|
RLNDecoder,
|
28
|
+
MerkleRootTracker,
|
22
29
|
RLNContract,
|
23
30
|
RLN_ABI,
|
24
31
|
GOERLI_CONTRACT,
|
package/src/rln.ts
CHANGED
@@ -66,18 +66,29 @@ export async function create(): Promise<RLNInstance> {
|
|
66
66
|
return new RLNInstance(zkRLN, witnessCalculator);
|
67
67
|
}
|
68
68
|
|
69
|
-
export class
|
69
|
+
export class IdentityCredential {
|
70
70
|
constructor(
|
71
|
-
public readonly
|
71
|
+
public readonly IDTrapdoor: Uint8Array,
|
72
|
+
public readonly IDNullifier: Uint8Array,
|
73
|
+
public readonly IDSecretHash: Uint8Array,
|
72
74
|
public readonly IDCommitment: Uint8Array,
|
73
75
|
public readonly IDCommitmentBigInt: bigint
|
74
76
|
) {}
|
75
77
|
|
76
|
-
static fromBytes(memKeys: Uint8Array):
|
77
|
-
const
|
78
|
-
const
|
78
|
+
static fromBytes(memKeys: Uint8Array): IdentityCredential {
|
79
|
+
const idTrapdoor = memKeys.subarray(0, 32);
|
80
|
+
const idNullifier = memKeys.subarray(32, 64);
|
81
|
+
const idSecretHash = memKeys.subarray(64, 96);
|
82
|
+
const idCommitment = memKeys.subarray(96);
|
79
83
|
const idCommitmentBigInt = buildBigIntFromUint8Array(idCommitment);
|
80
|
-
|
84
|
+
|
85
|
+
return new IdentityCredential(
|
86
|
+
idTrapdoor,
|
87
|
+
idNullifier,
|
88
|
+
idSecretHash,
|
89
|
+
idCommitment,
|
90
|
+
idCommitmentBigInt
|
91
|
+
);
|
81
92
|
}
|
82
93
|
}
|
83
94
|
|
@@ -89,6 +100,14 @@ const shareYOffset = shareXOffset + 32;
|
|
89
100
|
const nullifierOffset = shareYOffset + 32;
|
90
101
|
const rlnIdentifierOffset = nullifierOffset + 32;
|
91
102
|
|
103
|
+
export class ProofMetadata {
|
104
|
+
constructor(
|
105
|
+
public readonly nullifier: Uint8Array,
|
106
|
+
public readonly shareX: Uint8Array,
|
107
|
+
public readonly shareY: Uint8Array,
|
108
|
+
public readonly externalNullifier: Uint8Array
|
109
|
+
) {}
|
110
|
+
}
|
92
111
|
export class Proof implements IRateLimitProof {
|
93
112
|
readonly proof: Uint8Array;
|
94
113
|
readonly merkleRoot: Uint8Array;
|
@@ -112,6 +131,16 @@ export class Proof implements IRateLimitProof {
|
|
112
131
|
rlnIdentifierOffset
|
113
132
|
);
|
114
133
|
}
|
134
|
+
|
135
|
+
extractMetadata(): ProofMetadata {
|
136
|
+
const externalNullifier = poseidonHash(this.epoch, this.rlnIdentifier);
|
137
|
+
return new ProofMetadata(
|
138
|
+
this.nullifier,
|
139
|
+
this.shareX,
|
140
|
+
this.shareY,
|
141
|
+
externalNullifier
|
142
|
+
);
|
143
|
+
}
|
115
144
|
}
|
116
145
|
|
117
146
|
export function proofToBytes(p: IRateLimitProof): Uint8Array {
|
@@ -126,30 +155,60 @@ export function proofToBytes(p: IRateLimitProof): Uint8Array {
|
|
126
155
|
);
|
127
156
|
}
|
128
157
|
|
158
|
+
export function poseidonHash(...input: Array<Uint8Array>): Uint8Array {
|
159
|
+
const inputLen = writeUIntLE(new Uint8Array(8), input.length, 0, 8);
|
160
|
+
const lenPrefixedData = concatenate(inputLen, ...input);
|
161
|
+
return zerokitRLN.poseidonHash(lenPrefixedData);
|
162
|
+
}
|
163
|
+
|
164
|
+
export function sha256(input: Uint8Array): Uint8Array {
|
165
|
+
const inputLen = writeUIntLE(new Uint8Array(8), input.length, 0, 8);
|
166
|
+
const lenPrefixedData = concatenate(inputLen, input);
|
167
|
+
return zerokitRLN.hash(lenPrefixedData);
|
168
|
+
}
|
169
|
+
|
129
170
|
export class RLNInstance {
|
130
171
|
constructor(
|
131
172
|
private zkRLN: number,
|
132
173
|
private witnessCalculator: WitnessCalculator
|
133
174
|
) {}
|
134
175
|
|
135
|
-
|
136
|
-
const memKeys = zerokitRLN.
|
137
|
-
return
|
176
|
+
generateIdentityCredentials(): IdentityCredential {
|
177
|
+
const memKeys = zerokitRLN.generateExtendedMembershipKey(this.zkRLN); // TODO: rename this function in zerokit rln-wasm
|
178
|
+
return IdentityCredential.fromBytes(memKeys);
|
138
179
|
}
|
139
180
|
|
140
|
-
|
181
|
+
generateSeededIdentityCredential(seed: string): IdentityCredential {
|
141
182
|
const seedBytes = stringEncoder.encode(seed);
|
142
|
-
|
183
|
+
// TODO: rename this function in zerokit rln-wasm
|
184
|
+
const memKeys = zerokitRLN.generateSeededExtendedMembershipKey(
|
143
185
|
this.zkRLN,
|
144
186
|
seedBytes
|
145
187
|
);
|
146
|
-
return
|
188
|
+
return IdentityCredential.fromBytes(memKeys);
|
147
189
|
}
|
148
190
|
|
149
191
|
insertMember(idCommitment: Uint8Array): void {
|
150
192
|
zerokitRLN.insertMember(this.zkRLN, idCommitment);
|
151
193
|
}
|
152
194
|
|
195
|
+
insertMembers(index: number, ...idCommitments: Array<Uint8Array>): void {
|
196
|
+
// serializes a seq of IDCommitments to a byte seq
|
197
|
+
// the order of serialization is |id_commitment_len<8>|id_commitment<var>|
|
198
|
+
const idCommitmentLen = writeUIntLE(
|
199
|
+
new Uint8Array(8),
|
200
|
+
idCommitments.length,
|
201
|
+
0,
|
202
|
+
8
|
203
|
+
);
|
204
|
+
const idCommitmentBytes = concatenate(idCommitmentLen, ...idCommitments);
|
205
|
+
zerokitRLN.setLeavesFrom(this.zkRLN, index, idCommitmentBytes);
|
206
|
+
}
|
207
|
+
|
208
|
+
deleteMember(index: number): void {
|
209
|
+
zerokitRLN.deleteLeaf(this.zkRLN, index);
|
210
|
+
}
|
211
|
+
|
153
212
|
getMerkleRoot(): Uint8Array {
|
154
213
|
return zerokitRLN.getRoot(this.zkRLN);
|
155
214
|
}
|
@@ -174,7 +233,7 @@ export class RLNInstance {
|
|
174
233
|
msg: Uint8Array,
|
175
234
|
index: number,
|
176
235
|
epoch: Uint8Array | Date | undefined,
|
177
|
-
|
236
|
+
idSecretHash: Uint8Array
|
178
237
|
): Promise<IRateLimitProof> {
|
179
238
|
if (epoch == undefined) {
|
180
239
|
epoch = epochIntToBytes(dateToEpoch(new Date()));
|
@@ -183,10 +242,15 @@ export class RLNInstance {
|
|
183
242
|
}
|
184
243
|
|
185
244
|
if (epoch.length != 32) throw "invalid epoch";
|
186
|
-
if (
|
245
|
+
if (idSecretHash.length != 32) throw "invalid id secret hash";
|
187
246
|
if (index < 0) throw "index must be >= 0";
|
188
247
|
|
189
|
-
const serialized_msg = this.serializeMessage(
|
248
|
+
const serialized_msg = this.serializeMessage(
|
249
|
+
msg,
|
250
|
+
index,
|
251
|
+
epoch,
|
252
|
+
idSecretHash
|
253
|
+
);
|
190
254
|
const rlnWitness = zerokitRLN.getSerializedRLNWitness(
|
191
255
|
this.zkRLN,
|
192
256
|
serialized_msg
|
@@ -228,7 +292,8 @@ export class RLNInstance {
|
|
228
292
|
|
229
293
|
verifyWithRoots(
|
230
294
|
proof: IRateLimitProof | Uint8Array,
|
231
|
-
msg: Uint8Array
|
295
|
+
msg: Uint8Array,
|
296
|
+
...roots: Array<Uint8Array>
|
232
297
|
): boolean {
|
233
298
|
let pBytes: Uint8Array;
|
234
299
|
if (proof instanceof Uint8Array) {
|
@@ -236,17 +301,15 @@ export class RLNInstance {
|
|
236
301
|
} else {
|
237
302
|
pBytes = proofToBytes(proof);
|
238
303
|
}
|
239
|
-
|
240
304
|
// calculate message length
|
241
305
|
const msgLen = writeUIntLE(new Uint8Array(8), msg.length, 0, 8);
|
242
306
|
|
243
|
-
|
244
|
-
const root = zerokitRLN.getRoot(this.zkRLN);
|
307
|
+
const rootsBytes = concatenate(...roots);
|
245
308
|
|
246
309
|
return zerokitRLN.verifyWithRoots(
|
247
310
|
this.zkRLN,
|
248
311
|
concatenate(pBytes, msgLen, msg),
|
249
|
-
|
312
|
+
rootsBytes
|
250
313
|
);
|
251
314
|
}
|
252
315
|
|
package/src/rln_contract.ts
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
import { ethers } from "ethers";
|
2
2
|
|
3
3
|
import { RLN_ABI } from "./constants.js";
|
4
|
-
import {
|
4
|
+
import { IdentityCredential, RLNInstance } from "./rln.js";
|
5
5
|
|
6
6
|
type Member = {
|
7
7
|
pubkey: string;
|
@@ -13,6 +13,12 @@ type ContractOptions = {
|
|
13
13
|
provider: ethers.Signer | ethers.providers.Provider;
|
14
14
|
};
|
15
15
|
|
16
|
+
type FetchMembersOptions = {
|
17
|
+
fromBlock?: number;
|
18
|
+
fetchRange?: number;
|
19
|
+
fetchChunks?: number;
|
20
|
+
};
|
21
|
+
|
16
22
|
export class RLNContract {
|
17
23
|
private _contract: ethers.Contract;
|
18
24
|
private membersFilter: ethers.EventFilter;
|
@@ -46,12 +52,12 @@ export class RLNContract {
|
|
46
52
|
|
47
53
|
public async fetchMembers(
|
48
54
|
rlnInstance: RLNInstance,
|
49
|
-
|
55
|
+
options: FetchMembersOptions = {}
|
50
56
|
): Promise<void> {
|
51
|
-
const registeredMemberEvents = await this.contract
|
52
|
-
|
53
|
-
|
54
|
-
);
|
57
|
+
const registeredMemberEvents = await queryFilter(this.contract, {
|
58
|
+
...options,
|
59
|
+
membersFilter: this.membersFilter,
|
60
|
+
});
|
55
61
|
|
56
62
|
for (const event of registeredMemberEvents) {
|
57
63
|
this.addMemberFromEvent(rlnInstance, event);
|
@@ -88,20 +94,19 @@ export class RLNContract {
|
|
88
94
|
rlnInstance: RLNInstance,
|
89
95
|
signature: string
|
90
96
|
): Promise<ethers.Event | undefined> {
|
91
|
-
const
|
92
|
-
signature
|
93
|
-
);
|
97
|
+
const identityCredential =
|
98
|
+
await rlnInstance.generateSeededIdentityCredential(signature);
|
94
99
|
|
95
|
-
return this.registerWithKey(
|
100
|
+
return this.registerWithKey(identityCredential);
|
96
101
|
}
|
97
102
|
|
98
103
|
public async registerWithKey(
|
99
|
-
|
104
|
+
credential: IdentityCredential
|
100
105
|
): Promise<ethers.Event | undefined> {
|
101
106
|
const depositValue = await this.contract.MEMBERSHIP_DEPOSIT();
|
102
107
|
|
103
108
|
const txRegisterResponse: ethers.ContractTransaction =
|
104
|
-
await this.contract.register(
|
109
|
+
await this.contract.register(credential.IDCommitmentBigInt, {
|
105
110
|
value: depositValue,
|
106
111
|
});
|
107
112
|
const txRegisterReceipt = await txRegisterResponse.wait();
|
@@ -109,3 +114,90 @@ export class RLNContract {
|
|
109
114
|
return txRegisterReceipt?.events?.[0];
|
110
115
|
}
|
111
116
|
}
|
117
|
+
|
118
|
+
type CustomQueryOptions = FetchMembersOptions & {
|
119
|
+
membersFilter: ethers.EventFilter;
|
120
|
+
};
|
121
|
+
|
122
|
+
// these value should be tested on other networks
|
123
|
+
const FETCH_CHUNK = 5;
|
124
|
+
const BLOCK_RANGE = 3000;
|
125
|
+
|
126
|
+
async function queryFilter(
|
127
|
+
contract: ethers.Contract,
|
128
|
+
options: CustomQueryOptions
|
129
|
+
): Promise<ethers.Event[]> {
|
130
|
+
const {
|
131
|
+
fromBlock,
|
132
|
+
membersFilter,
|
133
|
+
fetchRange = BLOCK_RANGE,
|
134
|
+
fetchChunks = FETCH_CHUNK,
|
135
|
+
} = options;
|
136
|
+
|
137
|
+
if (!fromBlock) {
|
138
|
+
return contract.queryFilter(membersFilter);
|
139
|
+
}
|
140
|
+
|
141
|
+
if (!contract.signer.provider) {
|
142
|
+
throw Error("No provider found on the contract's signer.");
|
143
|
+
}
|
144
|
+
|
145
|
+
const toBlock = await contract.signer.provider.getBlockNumber();
|
146
|
+
|
147
|
+
if (toBlock - fromBlock < fetchRange) {
|
148
|
+
return contract.queryFilter(membersFilter);
|
149
|
+
}
|
150
|
+
|
151
|
+
const events: ethers.Event[][] = [];
|
152
|
+
const chunks = splitToChunks(fromBlock, toBlock, fetchRange);
|
153
|
+
|
154
|
+
for (const portion of takeN<[number, number]>(chunks, fetchChunks)) {
|
155
|
+
const promises = portion.map(([left, right]) =>
|
156
|
+
ignoreErrors(contract.queryFilter(membersFilter, left, right), [])
|
157
|
+
);
|
158
|
+
const fetchedEvents = await Promise.all(promises);
|
159
|
+
events.push(fetchedEvents.flatMap((v) => v));
|
160
|
+
}
|
161
|
+
|
162
|
+
return events.flatMap((v) => v);
|
163
|
+
}
|
164
|
+
|
165
|
+
function splitToChunks(
|
166
|
+
from: number,
|
167
|
+
to: number,
|
168
|
+
step: number
|
169
|
+
): Array<[number, number]> {
|
170
|
+
const chunks = [];
|
171
|
+
|
172
|
+
let left = from;
|
173
|
+
while (left < to) {
|
174
|
+
const right = left + step < to ? left + step : to;
|
175
|
+
|
176
|
+
chunks.push([left, right] as [number, number]);
|
177
|
+
|
178
|
+
left = right;
|
179
|
+
}
|
180
|
+
|
181
|
+
return chunks;
|
182
|
+
}
|
183
|
+
|
184
|
+
function* takeN<T>(array: T[], size: number): Iterable<T[]> {
|
185
|
+
let start = 0;
|
186
|
+
let skip = size;
|
187
|
+
|
188
|
+
while (skip < array.length) {
|
189
|
+
const portion = array.slice(start, skip);
|
190
|
+
|
191
|
+
yield portion;
|
192
|
+
|
193
|
+
start = skip;
|
194
|
+
skip += size;
|
195
|
+
}
|
196
|
+
}
|
197
|
+
|
198
|
+
function ignoreErrors<T>(promise: Promise<T>, defaultValue: T): Promise<T> {
|
199
|
+
return promise.catch((err) => {
|
200
|
+
console.error(`Ignoring an error during query: ${err?.message}`);
|
201
|
+
return defaultValue;
|
202
|
+
});
|
203
|
+
}
|
@@ -0,0 +1,88 @@
|
|
1
|
+
class RootPerBlock {
|
2
|
+
constructor(public root: Uint8Array, public blockNumber: number) {}
|
3
|
+
}
|
4
|
+
|
5
|
+
const maxBufferSize = 20;
|
6
|
+
|
7
|
+
export class MerkleRootTracker {
|
8
|
+
private validMerkleRoots: Array<RootPerBlock> = new Array<RootPerBlock>();
|
9
|
+
private merkleRootBuffer: Array<RootPerBlock> = new Array<RootPerBlock>();
|
10
|
+
constructor(
|
11
|
+
private acceptableRootWindowSize: number,
|
12
|
+
initialRoot: Uint8Array
|
13
|
+
) {
|
14
|
+
this.pushRoot(0, initialRoot);
|
15
|
+
}
|
16
|
+
|
17
|
+
backFill(fromBlockNumber: number): void {
|
18
|
+
if (this.validMerkleRoots.length == 0) return;
|
19
|
+
|
20
|
+
let numBlocks = 0;
|
21
|
+
for (let i = this.validMerkleRoots.length - 1; i >= 0; i--) {
|
22
|
+
if (this.validMerkleRoots[i].blockNumber >= fromBlockNumber) {
|
23
|
+
numBlocks++;
|
24
|
+
}
|
25
|
+
}
|
26
|
+
|
27
|
+
if (numBlocks == 0) return;
|
28
|
+
|
29
|
+
const olderBlock = fromBlockNumber < this.validMerkleRoots[0].blockNumber;
|
30
|
+
|
31
|
+
// Remove last roots
|
32
|
+
let rootsToPop = numBlocks;
|
33
|
+
if (this.validMerkleRoots.length < rootsToPop) {
|
34
|
+
rootsToPop = this.validMerkleRoots.length;
|
35
|
+
}
|
36
|
+
|
37
|
+
this.validMerkleRoots = this.validMerkleRoots.slice(
|
38
|
+
0,
|
39
|
+
this.validMerkleRoots.length - rootsToPop
|
40
|
+
);
|
41
|
+
|
42
|
+
if (this.merkleRootBuffer.length == 0) return;
|
43
|
+
|
44
|
+
if (olderBlock) {
|
45
|
+
const idx = this.merkleRootBuffer.findIndex(
|
46
|
+
(x) => x.blockNumber == fromBlockNumber
|
47
|
+
);
|
48
|
+
if (idx > -1) {
|
49
|
+
this.merkleRootBuffer = this.merkleRootBuffer.slice(0, idx);
|
50
|
+
}
|
51
|
+
}
|
52
|
+
|
53
|
+
// Backfill the tree's acceptable roots
|
54
|
+
let rootsToRestore =
|
55
|
+
this.acceptableRootWindowSize - this.validMerkleRoots.length;
|
56
|
+
if (this.merkleRootBuffer.length < rootsToRestore) {
|
57
|
+
rootsToRestore = this.merkleRootBuffer.length;
|
58
|
+
}
|
59
|
+
|
60
|
+
for (let i = 0; i < rootsToRestore; i++) {
|
61
|
+
const x = this.merkleRootBuffer.pop();
|
62
|
+
if (x) this.validMerkleRoots.unshift(x);
|
63
|
+
}
|
64
|
+
}
|
65
|
+
|
66
|
+
pushRoot(blockNumber: number, root: Uint8Array): void {
|
67
|
+
this.validMerkleRoots.push(new RootPerBlock(root, blockNumber));
|
68
|
+
|
69
|
+
// Maintain valid merkle root window
|
70
|
+
if (this.validMerkleRoots.length > this.acceptableRootWindowSize) {
|
71
|
+
const x = this.validMerkleRoots.shift();
|
72
|
+
if (x) this.merkleRootBuffer.push(x);
|
73
|
+
}
|
74
|
+
|
75
|
+
// Maintain merkle root buffer
|
76
|
+
if (this.merkleRootBuffer.length > maxBufferSize) {
|
77
|
+
this.merkleRootBuffer.shift();
|
78
|
+
}
|
79
|
+
}
|
80
|
+
|
81
|
+
roots(): Array<Uint8Array> {
|
82
|
+
return this.validMerkleRoots.map((x) => x.root);
|
83
|
+
}
|
84
|
+
|
85
|
+
buffer(): Array<Uint8Array> {
|
86
|
+
return this.merkleRootBuffer.map((x) => x.root);
|
87
|
+
}
|
88
|
+
}
|
Binary file
|
Binary file
|