@ocash/sdk 0.1.4-rc.1 → 0.1.4-rc.3
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/browser.cjs +403 -350
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.d.cts +18 -16
- package/dist/browser.d.ts +18 -16
- package/dist/browser.js +403 -350
- package/dist/browser.js.map +1 -1
- package/dist/{index-CI7UllxU.d.cts → index-Dvl0HZkw.d.cts} +66 -64
- package/dist/{index-CI7UllxU.d.ts → index-Dvl0HZkw.d.ts} +66 -64
- package/dist/index.cjs +343 -313
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +343 -313
- package/dist/index.js.map +1 -1
- package/dist/node.cjs +449 -401
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.cts +24 -23
- package/dist/node.d.ts +24 -23
- package/dist/node.js +449 -401
- package/dist/node.js.map +1 -1
- package/package.json +1 -1
package/dist/node.cjs
CHANGED
|
@@ -3291,8 +3291,8 @@ var MemoryStore = class {
|
|
|
3291
3291
|
this.utxos = /* @__PURE__ */ new Map();
|
|
3292
3292
|
this.operations = [];
|
|
3293
3293
|
this.merkleLeavesByChain = /* @__PURE__ */ new Map();
|
|
3294
|
-
this.
|
|
3295
|
-
this.
|
|
3294
|
+
this.chairmanMerkleVersionsByChain = /* @__PURE__ */ new Map();
|
|
3295
|
+
this.chairmanMerkleNodesByChain = /* @__PURE__ */ new Map();
|
|
3296
3296
|
this.entryMemosByChain = /* @__PURE__ */ new Map();
|
|
3297
3297
|
this.entryNullifiersByChain = /* @__PURE__ */ new Map();
|
|
3298
3298
|
const max = options?.maxOperations;
|
|
@@ -3308,8 +3308,8 @@ var MemoryStore = class {
|
|
|
3308
3308
|
this.utxos.clear();
|
|
3309
3309
|
this.operations = [];
|
|
3310
3310
|
this.merkleLeavesByChain.clear();
|
|
3311
|
-
this.
|
|
3312
|
-
this.
|
|
3311
|
+
this.chairmanMerkleVersionsByChain.clear();
|
|
3312
|
+
this.chairmanMerkleNodesByChain.clear();
|
|
3313
3313
|
this.entryMemosByChain.clear();
|
|
3314
3314
|
this.entryNullifiersByChain.clear();
|
|
3315
3315
|
}
|
|
@@ -3425,49 +3425,64 @@ var MemoryStore = class {
|
|
|
3425
3425
|
return { chainId, cid: row.cid, commitment: row.commitment };
|
|
3426
3426
|
}
|
|
3427
3427
|
/**
|
|
3428
|
-
* Get a
|
|
3428
|
+
* Get a chairmanMerkle tree node by id.
|
|
3429
3429
|
*/
|
|
3430
|
-
async
|
|
3431
|
-
return this.
|
|
3430
|
+
async getChairmanMerkleNode(chainId, id) {
|
|
3431
|
+
return this.chairmanMerkleNodesByChain.get(chainId)?.get(id);
|
|
3432
3432
|
}
|
|
3433
3433
|
/**
|
|
3434
|
-
*
|
|
3434
|
+
* Put chairmanMerkle tree nodes for a chain.
|
|
3435
3435
|
*/
|
|
3436
|
-
async
|
|
3436
|
+
async putChairmanMerkleNodes(chainId, nodes) {
|
|
3437
3437
|
if (!nodes.length) return;
|
|
3438
|
-
let map = this.
|
|
3438
|
+
let map = this.chairmanMerkleNodesByChain.get(chainId);
|
|
3439
3439
|
if (!map) {
|
|
3440
3440
|
map = /* @__PURE__ */ new Map();
|
|
3441
|
-
this.
|
|
3441
|
+
this.chairmanMerkleNodesByChain.set(chainId, map);
|
|
3442
3442
|
}
|
|
3443
3443
|
for (const node of nodes) {
|
|
3444
3444
|
map.set(node.id, { ...node, chainId });
|
|
3445
3445
|
}
|
|
3446
3446
|
}
|
|
3447
3447
|
/**
|
|
3448
|
-
*
|
|
3448
|
+
* Get a chairmanMerkle version record by chain and version number.
|
|
3449
3449
|
*/
|
|
3450
|
-
async
|
|
3451
|
-
this.
|
|
3450
|
+
async getChairmanMerkleVersion(chainId, version) {
|
|
3451
|
+
const byVersion = this.chairmanMerkleVersionsByChain.get(chainId);
|
|
3452
|
+
const record = byVersion?.get(version);
|
|
3453
|
+
return record ? { ...record } : void 0;
|
|
3452
3454
|
}
|
|
3453
3455
|
/**
|
|
3454
|
-
* Get
|
|
3456
|
+
* Get the latest chairmanMerkle version record (highest version number) for a chain.
|
|
3455
3457
|
*/
|
|
3456
|
-
async
|
|
3457
|
-
const
|
|
3458
|
-
|
|
3458
|
+
async getLatestChairmanMerkleVersion(chainId) {
|
|
3459
|
+
const byVersion = this.chairmanMerkleVersionsByChain.get(chainId);
|
|
3460
|
+
if (!byVersion || byVersion.size === 0) return void 0;
|
|
3461
|
+
let latest;
|
|
3462
|
+
for (const record of byVersion.values()) {
|
|
3463
|
+
if (!latest || record.version > latest.version) {
|
|
3464
|
+
latest = record;
|
|
3465
|
+
}
|
|
3466
|
+
}
|
|
3467
|
+
return latest ? { ...latest } : void 0;
|
|
3459
3468
|
}
|
|
3460
3469
|
/**
|
|
3461
|
-
* Persist
|
|
3470
|
+
* Persist a chairmanMerkle version record.
|
|
3462
3471
|
*/
|
|
3463
|
-
async
|
|
3464
|
-
this.
|
|
3472
|
+
async putChairmanMerkleVersion(chainId, record) {
|
|
3473
|
+
let byVersion = this.chairmanMerkleVersionsByChain.get(chainId);
|
|
3474
|
+
if (!byVersion) {
|
|
3475
|
+
byVersion = /* @__PURE__ */ new Map();
|
|
3476
|
+
this.chairmanMerkleVersionsByChain.set(chainId, byVersion);
|
|
3477
|
+
}
|
|
3478
|
+
byVersion.set(record.version, { ...record, chainId });
|
|
3465
3479
|
}
|
|
3466
3480
|
/**
|
|
3467
|
-
* Clear
|
|
3481
|
+
* Clear all chairmanMerkle tree state (both nodes and versions) for a chain.
|
|
3468
3482
|
*/
|
|
3469
|
-
async
|
|
3470
|
-
this.
|
|
3483
|
+
async clearChairmanMerkleTree(chainId) {
|
|
3484
|
+
this.chairmanMerkleNodesByChain.delete(chainId);
|
|
3485
|
+
this.chairmanMerkleVersionsByChain.delete(chainId);
|
|
3471
3486
|
}
|
|
3472
3487
|
/**
|
|
3473
3488
|
* Upsert entry memos (raw EntryService cache).
|
|
@@ -3991,13 +4006,14 @@ var KeyValueStore = class {
|
|
|
3991
4006
|
this.utxoCache = /* @__PURE__ */ new Map();
|
|
3992
4007
|
this.operationCache = /* @__PURE__ */ new Map();
|
|
3993
4008
|
this.merkleLeafCids = {};
|
|
3994
|
-
this.
|
|
3995
|
-
this.
|
|
4009
|
+
this.chairmanMerkleLatestVersions = {};
|
|
4010
|
+
this.chairmanMerkleNodeIds = {};
|
|
4011
|
+
this.chairmanMerkleVersionNums = {};
|
|
3996
4012
|
this.entryMemoCids = {};
|
|
3997
4013
|
this.entryNullifierNids = {};
|
|
3998
4014
|
this.loadedMerkleLeaves = /* @__PURE__ */ new Set();
|
|
3999
|
-
this.
|
|
4000
|
-
this.
|
|
4015
|
+
this.loadedChairmanMerkleVersions = /* @__PURE__ */ new Set();
|
|
4016
|
+
this.loadedChairmanMerkleNodes = /* @__PURE__ */ new Set();
|
|
4001
4017
|
this.loadedEntryMemos = /* @__PURE__ */ new Set();
|
|
4002
4018
|
this.loadedEntryNullifiers = /* @__PURE__ */ new Set();
|
|
4003
4019
|
this.saveChain = Promise.resolve();
|
|
@@ -4031,9 +4047,6 @@ var KeyValueStore = class {
|
|
|
4031
4047
|
walletOperationKey(id) {
|
|
4032
4048
|
return `${this.walletBaseKey()}:operation:${id}`;
|
|
4033
4049
|
}
|
|
4034
|
-
sharedChainKey(part, chainId) {
|
|
4035
|
-
return `${this.keyPrefix()}:shared:${part}:${chainId}`;
|
|
4036
|
-
}
|
|
4037
4050
|
sharedChainMetaKey(part, chainId) {
|
|
4038
4051
|
return `${this.keyPrefix()}:shared:${part}:${chainId}:meta`;
|
|
4039
4052
|
}
|
|
@@ -4086,13 +4099,14 @@ var KeyValueStore = class {
|
|
|
4086
4099
|
this.operationCache.clear();
|
|
4087
4100
|
this.walletMetaLoaded = false;
|
|
4088
4101
|
this.merkleLeafCids = {};
|
|
4089
|
-
this.
|
|
4090
|
-
this.
|
|
4102
|
+
this.chairmanMerkleLatestVersions = {};
|
|
4103
|
+
this.chairmanMerkleNodeIds = {};
|
|
4104
|
+
this.chairmanMerkleVersionNums = {};
|
|
4091
4105
|
this.entryMemoCids = {};
|
|
4092
4106
|
this.entryNullifierNids = {};
|
|
4093
4107
|
this.loadedMerkleLeaves.clear();
|
|
4094
|
-
this.
|
|
4095
|
-
this.
|
|
4108
|
+
this.loadedChairmanMerkleVersions.clear();
|
|
4109
|
+
this.loadedChairmanMerkleNodes.clear();
|
|
4096
4110
|
this.loadedEntryMemos.clear();
|
|
4097
4111
|
this.loadedEntryNullifiers.clear();
|
|
4098
4112
|
}
|
|
@@ -4187,20 +4201,19 @@ var KeyValueStore = class {
|
|
|
4187
4201
|
this.merkleLeafCids[key] = new Set(this.parseNumberIndex(cidsRaw));
|
|
4188
4202
|
this.loadedMerkleLeaves.add(chainId);
|
|
4189
4203
|
}
|
|
4190
|
-
async
|
|
4191
|
-
if (this.
|
|
4204
|
+
async ensureChairmanMerkleVersionsLoaded(chainId) {
|
|
4205
|
+
if (this.loadedChairmanMerkleVersions.has(chainId)) return;
|
|
4192
4206
|
const key = String(chainId);
|
|
4193
|
-
const
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
this.loadedMerkleTrees.add(chainId);
|
|
4207
|
+
const numsRaw = await this.options.client.get(this.sharedChainMetaKey("chairmanMerkleVersions", chainId));
|
|
4208
|
+
this.chairmanMerkleVersionNums[key] = new Set(this.parseNumberIndex(numsRaw));
|
|
4209
|
+
this.loadedChairmanMerkleVersions.add(chainId);
|
|
4197
4210
|
}
|
|
4198
|
-
async
|
|
4199
|
-
if (this.
|
|
4211
|
+
async ensureChairmanMerkleNodesLoaded(chainId) {
|
|
4212
|
+
if (this.loadedChairmanMerkleNodes.has(chainId)) return;
|
|
4200
4213
|
const key = String(chainId);
|
|
4201
|
-
const idsRaw = await this.options.client.get(this.sharedChainMetaKey("
|
|
4202
|
-
this.
|
|
4203
|
-
this.
|
|
4214
|
+
const idsRaw = await this.options.client.get(this.sharedChainMetaKey("chairmanMerkleNodes", chainId));
|
|
4215
|
+
this.chairmanMerkleNodeIds[key] = new Set(this.parseStringIndex(idsRaw));
|
|
4216
|
+
this.loadedChairmanMerkleNodes.add(chainId);
|
|
4204
4217
|
}
|
|
4205
4218
|
async ensureEntryMemosLoaded(chainId) {
|
|
4206
4219
|
if (this.loadedEntryMemos.has(chainId)) return;
|
|
@@ -4216,64 +4229,88 @@ var KeyValueStore = class {
|
|
|
4216
4229
|
this.entryNullifierNids[key] = new Set(this.parseNumberIndex(nidsRaw));
|
|
4217
4230
|
this.loadedEntryNullifiers.add(chainId);
|
|
4218
4231
|
}
|
|
4219
|
-
async
|
|
4220
|
-
await this.
|
|
4221
|
-
if (!this.
|
|
4222
|
-
const raw = await this.options.client.get(this.sharedRecordKey("
|
|
4232
|
+
async getChairmanMerkleNode(chainId, id) {
|
|
4233
|
+
await this.ensureChairmanMerkleNodesLoaded(chainId);
|
|
4234
|
+
if (!this.chairmanMerkleNodeIds[String(chainId)]?.has(id)) return void 0;
|
|
4235
|
+
const raw = await this.options.client.get(this.sharedRecordKey("chairmanMerkleNodes", chainId, id));
|
|
4223
4236
|
const node = this.parseJson(raw, null);
|
|
4224
4237
|
if (!node) return void 0;
|
|
4225
4238
|
const hash = node.hash;
|
|
4226
4239
|
if (typeof hash !== "string" || !hash.startsWith("0x")) return void 0;
|
|
4227
4240
|
return { ...node, chainId };
|
|
4228
4241
|
}
|
|
4229
|
-
async
|
|
4242
|
+
async putChairmanMerkleNodes(chainId, nodes) {
|
|
4230
4243
|
if (!nodes.length) return;
|
|
4231
|
-
await this.
|
|
4244
|
+
await this.ensureChairmanMerkleNodesLoaded(chainId);
|
|
4232
4245
|
const key = String(chainId);
|
|
4233
|
-
const ids = this.
|
|
4246
|
+
const ids = this.chairmanMerkleNodeIds[key] ?? /* @__PURE__ */ new Set();
|
|
4234
4247
|
const beforeSize = ids.size;
|
|
4235
4248
|
for (const node of nodes) {
|
|
4236
4249
|
ids.add(node.id);
|
|
4237
4250
|
}
|
|
4238
|
-
this.
|
|
4251
|
+
this.chairmanMerkleNodeIds[key] = ids;
|
|
4239
4252
|
await this.enqueueWrite(async () => {
|
|
4240
|
-
await Promise.all(nodes.map((node) => this.writeJson(this.sharedRecordKey("
|
|
4253
|
+
await Promise.all(nodes.map((node) => this.writeJson(this.sharedRecordKey("chairmanMerkleNodes", chainId, node.id), { ...node, chainId })));
|
|
4241
4254
|
if (ids.size !== beforeSize) {
|
|
4242
|
-
await this.writeJson(this.sharedChainMetaKey("
|
|
4255
|
+
await this.writeJson(this.sharedChainMetaKey("chairmanMerkleNodes", chainId), Array.from(ids));
|
|
4243
4256
|
}
|
|
4244
4257
|
});
|
|
4245
4258
|
}
|
|
4246
|
-
async
|
|
4247
|
-
await this.
|
|
4248
|
-
|
|
4249
|
-
|
|
4259
|
+
async getChairmanMerkleVersion(chainId, version) {
|
|
4260
|
+
await this.ensureChairmanMerkleVersionsLoaded(chainId);
|
|
4261
|
+
if (!this.chairmanMerkleVersionNums[String(chainId)]?.has(version)) return void 0;
|
|
4262
|
+
const raw = await this.options.client.get(this.sharedRecordKey("chairmanMerkleVersions", chainId, version));
|
|
4263
|
+
const record = this.parseJson(raw, null);
|
|
4264
|
+
if (!record) return void 0;
|
|
4265
|
+
if (typeof record.rootHash !== "string" || !record.rootHash.startsWith("0x")) return void 0;
|
|
4266
|
+
if (typeof record.rootId !== "string") return void 0;
|
|
4267
|
+
const v = Number(record.version);
|
|
4268
|
+
if (!Number.isFinite(v) || v < 0) return void 0;
|
|
4269
|
+
return { chainId, version: Math.floor(v), rootId: record.rootId, rootHash: record.rootHash };
|
|
4270
|
+
}
|
|
4271
|
+
async getLatestChairmanMerkleVersion(chainId) {
|
|
4272
|
+
await this.ensureChairmanMerkleVersionsLoaded(chainId);
|
|
4273
|
+
const nums = this.chairmanMerkleVersionNums[String(chainId)];
|
|
4274
|
+
if (!nums || nums.size === 0) return void 0;
|
|
4275
|
+
const maxVersion = Math.max(...nums);
|
|
4276
|
+
return this.getChairmanMerkleVersion(chainId, maxVersion);
|
|
4277
|
+
}
|
|
4278
|
+
async putChairmanMerkleVersion(chainId, record) {
|
|
4279
|
+
await this.ensureChairmanMerkleVersionsLoaded(chainId);
|
|
4280
|
+
const key = String(chainId);
|
|
4281
|
+
const nums = this.chairmanMerkleVersionNums[key] ?? /* @__PURE__ */ new Set();
|
|
4282
|
+
const beforeSize = nums.size;
|
|
4283
|
+
nums.add(record.version);
|
|
4284
|
+
this.chairmanMerkleVersionNums[key] = nums;
|
|
4285
|
+
const current = this.chairmanMerkleLatestVersions[key];
|
|
4286
|
+
if (!current || record.version >= current.version) {
|
|
4287
|
+
this.chairmanMerkleLatestVersions[key] = { ...record, chainId };
|
|
4288
|
+
}
|
|
4289
|
+
const row = { ...record, chainId };
|
|
4250
4290
|
await this.enqueueWrite(async () => {
|
|
4251
|
-
await
|
|
4252
|
-
|
|
4291
|
+
await this.writeJson(this.sharedRecordKey("chairmanMerkleVersions", chainId, record.version), row);
|
|
4292
|
+
if (nums.size !== beforeSize) {
|
|
4293
|
+
await this.writeJson(this.sharedChainMetaKey("chairmanMerkleVersions", chainId), Array.from(nums).sort((a, b) => a - b));
|
|
4294
|
+
}
|
|
4253
4295
|
});
|
|
4254
4296
|
}
|
|
4255
|
-
async
|
|
4256
|
-
await this.
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
const
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
if (!Number.isFinite(totalElements) || totalElements < 0) return void 0;
|
|
4264
|
-
return { chainId, root, totalElements: Math.floor(totalElements), lastUpdated: Number.isFinite(lastUpdated) ? Math.floor(lastUpdated) : 0 };
|
|
4265
|
-
}
|
|
4266
|
-
async setMerkleTree(chainId, tree) {
|
|
4267
|
-
await this.ensureMerkleTreeLoaded(chainId);
|
|
4268
|
-
const row = { ...tree, chainId };
|
|
4269
|
-
this.merkleTrees[String(chainId)] = row;
|
|
4270
|
-
await this.enqueueWrite(() => this.writeJson(this.sharedChainKey("merkleTrees", chainId), row));
|
|
4271
|
-
}
|
|
4272
|
-
async clearMerkleTree(chainId) {
|
|
4273
|
-
await this.ensureMerkleTreeLoaded(chainId);
|
|
4274
|
-
delete this.merkleTrees[String(chainId)];
|
|
4297
|
+
async clearChairmanMerkleTree(chainId) {
|
|
4298
|
+
await this.ensureChairmanMerkleNodesLoaded(chainId);
|
|
4299
|
+
await this.ensureChairmanMerkleVersionsLoaded(chainId);
|
|
4300
|
+
const nodeIds = Array.from(this.chairmanMerkleNodeIds[String(chainId)] ?? []);
|
|
4301
|
+
const versionNums = Array.from(this.chairmanMerkleVersionNums[String(chainId)] ?? []);
|
|
4302
|
+
delete this.chairmanMerkleNodeIds[String(chainId)];
|
|
4303
|
+
delete this.chairmanMerkleVersionNums[String(chainId)];
|
|
4304
|
+
delete this.chairmanMerkleLatestVersions[String(chainId)];
|
|
4275
4305
|
await this.enqueueWrite(async () => {
|
|
4276
|
-
await
|
|
4306
|
+
await Promise.all([
|
|
4307
|
+
...nodeIds.map((id) => this.deleteOrReset(this.sharedRecordKey("chairmanMerkleNodes", chainId, id), null)),
|
|
4308
|
+
...versionNums.map((v) => this.deleteOrReset(this.sharedRecordKey("chairmanMerkleVersions", chainId, v), null))
|
|
4309
|
+
]);
|
|
4310
|
+
await Promise.all([
|
|
4311
|
+
this.deleteOrReset(this.sharedChainMetaKey("chairmanMerkleNodes", chainId), []),
|
|
4312
|
+
this.deleteOrReset(this.sharedChainMetaKey("chairmanMerkleVersions", chainId), [])
|
|
4313
|
+
]);
|
|
4277
4314
|
});
|
|
4278
4315
|
}
|
|
4279
4316
|
async upsertEntryMemos(memos) {
|
|
@@ -7117,6 +7154,7 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7117
7154
|
this.hydrateInFlight = /* @__PURE__ */ new Map();
|
|
7118
7155
|
this.mode = options?.mode ?? "hybrid";
|
|
7119
7156
|
this.treeDepth = Math.max(1, Math.floor(options?.treeDepth ?? TREE_DEPTH_DEFAULT));
|
|
7157
|
+
this.readContractRoot = options?.readContractRoot;
|
|
7120
7158
|
}
|
|
7121
7159
|
/**
|
|
7122
7160
|
* Compute the current merkle root index from total elements.
|
|
@@ -7125,9 +7163,6 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7125
7163
|
if (totalElements <= tempArraySize) return 0;
|
|
7126
7164
|
return Math.floor((totalElements - 1) / tempArraySize);
|
|
7127
7165
|
}
|
|
7128
|
-
/**
|
|
7129
|
-
* Get or initialize the pending leaf buffer for a chain.
|
|
7130
|
-
*/
|
|
7131
7166
|
ensurePendingLeaves(chainId) {
|
|
7132
7167
|
let pending = this.pendingLeavesByChain.get(chainId);
|
|
7133
7168
|
if (!pending) {
|
|
@@ -7136,9 +7171,6 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7136
7171
|
}
|
|
7137
7172
|
return pending;
|
|
7138
7173
|
}
|
|
7139
|
-
/**
|
|
7140
|
-
* Get or initialize chain-level merkle state.
|
|
7141
|
-
*/
|
|
7142
7174
|
ensureChainState(chainId) {
|
|
7143
7175
|
let state = this.chainStateByChain.get(chainId);
|
|
7144
7176
|
if (!state) {
|
|
@@ -7147,15 +7179,32 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7147
7179
|
}
|
|
7148
7180
|
return state;
|
|
7149
7181
|
}
|
|
7150
|
-
|
|
7151
|
-
* Poseidon2 merkle hash for a left/right pair.
|
|
7152
|
-
*/
|
|
7182
|
+
// ── Hashing ──
|
|
7153
7183
|
static hashPair(left, right) {
|
|
7154
7184
|
return Poseidon2.hashToHex(BigInt(left), BigInt(right), Poseidon2Domain.Merkle);
|
|
7155
7185
|
}
|
|
7186
|
+
static normalizeHex32(value, name) {
|
|
7187
|
+
try {
|
|
7188
|
+
const bi = BigInt(value);
|
|
7189
|
+
if (bi < 0n) throw new Error("negative");
|
|
7190
|
+
const hex = bi.toString(16).padStart(64, "0");
|
|
7191
|
+
if (hex.length > 64) throw new Error("too_large");
|
|
7192
|
+
return `0x${hex}`;
|
|
7193
|
+
} catch (error) {
|
|
7194
|
+
throw new SdkError("MERKLE", `Invalid ${name}`, { value }, error);
|
|
7195
|
+
}
|
|
7196
|
+
}
|
|
7197
|
+
// ── Static helpers ──
|
|
7198
|
+
static totalElementsInTree(totalElements, tempArraySize = TEMP_ARRAY_SIZE_DEFAULT) {
|
|
7199
|
+
if (tempArraySize <= 0) throw new SdkError("MERKLE", "tempArraySize must be greater than zero", { tempArraySize });
|
|
7200
|
+
if (totalElements <= 0n) return 0;
|
|
7201
|
+
const size = BigInt(tempArraySize);
|
|
7202
|
+
return Number((totalElements - 1n) / size * size);
|
|
7203
|
+
}
|
|
7204
|
+
// ── Subtree (levels 0-5, 32 leaves → 1 root) ──
|
|
7156
7205
|
/**
|
|
7157
7206
|
* Build a fixed-depth subtree from 32 contiguous leaves.
|
|
7158
|
-
* Returns the subtree root and all intermediate nodes for storage.
|
|
7207
|
+
* Returns the subtree root hash and all intermediate nodes for storage.
|
|
7159
7208
|
*/
|
|
7160
7209
|
static buildSubtree(leafCommitments, baseIndex) {
|
|
7161
7210
|
if (leafCommitments.length !== SUBTREE_SIZE) {
|
|
@@ -7176,71 +7225,71 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7176
7225
|
const position = basePos + i;
|
|
7177
7226
|
nodesToStore.push({
|
|
7178
7227
|
chainId: 0,
|
|
7179
|
-
id:
|
|
7180
|
-
|
|
7181
|
-
|
|
7182
|
-
|
|
7228
|
+
id: `st-${level}-${position}`,
|
|
7229
|
+
hash: next[i],
|
|
7230
|
+
leftId: null,
|
|
7231
|
+
rightId: null
|
|
7183
7232
|
});
|
|
7184
7233
|
}
|
|
7185
7234
|
currentLevel = next;
|
|
7186
7235
|
}
|
|
7187
7236
|
return { subtreeRoot: currentLevel[0], nodesToStore };
|
|
7188
7237
|
}
|
|
7238
|
+
// ── ChairmanMerkle tree (persistent segment tree, levels 5-32) ──
|
|
7189
7239
|
/**
|
|
7190
|
-
*
|
|
7191
|
-
|
|
7192
|
-
|
|
7193
|
-
|
|
7194
|
-
|
|
7195
|
-
|
|
7196
|
-
|
|
7197
|
-
*
|
|
7198
|
-
*/
|
|
7199
|
-
async
|
|
7200
|
-
|
|
7201
|
-
|
|
7202
|
-
const
|
|
7203
|
-
|
|
7204
|
-
|
|
7205
|
-
|
|
7206
|
-
|
|
7207
|
-
|
|
7208
|
-
|
|
7209
|
-
|
|
7210
|
-
|
|
7211
|
-
|
|
7212
|
-
|
|
7213
|
-
|
|
7214
|
-
|
|
7240
|
+
* Insert a subtree root into the persistent main tree.
|
|
7241
|
+
*
|
|
7242
|
+
* Top-down recursive: descends from root (level treeDepth) to the target
|
|
7243
|
+
* leaf position (level SUBTREE_DEPTH). At each level only the node on the
|
|
7244
|
+
* update path is newly created; the sibling is shared from the previous
|
|
7245
|
+
* version's tree.
|
|
7246
|
+
*
|
|
7247
|
+
* @returns new root node ID/hash and all newly created nodes.
|
|
7248
|
+
*/
|
|
7249
|
+
async insertSubtreeRoot(chainId, prevRootId, subtreeRootHash, batchIndex, version) {
|
|
7250
|
+
const MAIN_DEPTH = this.treeDepth - SUBTREE_DEPTH;
|
|
7251
|
+
const nodes = [];
|
|
7252
|
+
const descend = async (nodeId, depth) => {
|
|
7253
|
+
const originalLevel = this.treeDepth - depth;
|
|
7254
|
+
if (depth === MAIN_DEPTH) {
|
|
7255
|
+
const newId2 = `cm-${version}-${originalLevel}`;
|
|
7256
|
+
nodes.push({ chainId, id: newId2, hash: subtreeRootHash, leftId: null, rightId: null });
|
|
7257
|
+
return { id: newId2, hash: subtreeRootHash };
|
|
7258
|
+
}
|
|
7259
|
+
let prevLeftId = null;
|
|
7260
|
+
let prevRightId = null;
|
|
7261
|
+
if (nodeId) {
|
|
7262
|
+
const prevNode = await this.storage?.getChairmanMerkleNode?.(chainId, nodeId);
|
|
7263
|
+
if (prevNode) {
|
|
7264
|
+
prevLeftId = prevNode.leftId;
|
|
7265
|
+
prevRightId = prevNode.rightId;
|
|
7215
7266
|
}
|
|
7216
|
-
currentValue = _MerkleEngine.hashPair(currentValue, getZeroHash(level));
|
|
7217
|
-
} else {
|
|
7218
|
-
const leftHash = await this.getNodeHash(input.chainId, `frontier-${level}`) ?? getZeroHash(level);
|
|
7219
|
-
currentValue = _MerkleEngine.hashPair(leftHash, currentValue);
|
|
7220
7267
|
}
|
|
7221
|
-
const
|
|
7222
|
-
|
|
7223
|
-
|
|
7224
|
-
|
|
7225
|
-
|
|
7226
|
-
|
|
7227
|
-
hash:
|
|
7228
|
-
|
|
7229
|
-
|
|
7230
|
-
|
|
7231
|
-
|
|
7232
|
-
|
|
7233
|
-
|
|
7234
|
-
|
|
7235
|
-
|
|
7236
|
-
|
|
7237
|
-
|
|
7238
|
-
|
|
7239
|
-
|
|
7268
|
+
const childLevel = originalLevel - 1;
|
|
7269
|
+
const remainingDepth = MAIN_DEPTH - depth - 1;
|
|
7270
|
+
const goRight = (batchIndex >> remainingDepth & 1) === 1;
|
|
7271
|
+
let leftResult;
|
|
7272
|
+
let rightResult;
|
|
7273
|
+
if (goRight) {
|
|
7274
|
+
const leftHash = prevLeftId ? (await this.storage?.getChairmanMerkleNode?.(chainId, prevLeftId))?.hash ?? getZeroHash(childLevel) : getZeroHash(childLevel);
|
|
7275
|
+
leftResult = { id: prevLeftId, hash: leftHash };
|
|
7276
|
+
const right = await descend(prevRightId, depth + 1);
|
|
7277
|
+
rightResult = { id: right.id, hash: right.hash };
|
|
7278
|
+
} else {
|
|
7279
|
+
const left = await descend(prevLeftId, depth + 1);
|
|
7280
|
+
leftResult = { id: left.id, hash: left.hash };
|
|
7281
|
+
const rightHash = prevRightId ? (await this.storage?.getChairmanMerkleNode?.(chainId, prevRightId))?.hash ?? getZeroHash(childLevel) : getZeroHash(childLevel);
|
|
7282
|
+
rightResult = { id: prevRightId, hash: rightHash };
|
|
7283
|
+
}
|
|
7284
|
+
const hash = _MerkleEngine.hashPair(leftResult.hash, rightResult.hash);
|
|
7285
|
+
const newId = `cm-${version}-${originalLevel}`;
|
|
7286
|
+
nodes.push({ chainId, id: newId, hash, leftId: leftResult.id, rightId: rightResult.id });
|
|
7287
|
+
return { id: newId, hash };
|
|
7288
|
+
};
|
|
7289
|
+
const root = await descend(prevRootId, 0);
|
|
7290
|
+
return { rootId: root.id, rootHash: root.hash, nodes };
|
|
7240
7291
|
}
|
|
7241
|
-
|
|
7242
|
-
* Hydrate local merkle state from storage on first use.
|
|
7243
|
-
*/
|
|
7292
|
+
// ── Hydration ──
|
|
7244
7293
|
async hydrateFromStorage(chainId) {
|
|
7245
7294
|
if (this.mode === "remote") return;
|
|
7246
7295
|
if (this.hydratedChains.has(chainId)) return;
|
|
@@ -7249,31 +7298,17 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7249
7298
|
const task = (async () => {
|
|
7250
7299
|
try {
|
|
7251
7300
|
const state = this.ensureChainState(chainId);
|
|
7252
|
-
const leaves = await this.storage?.getMerkleLeaves?.(chainId);
|
|
7253
|
-
if (!leaves || leaves.length === 0) return;
|
|
7254
|
-
const sorted = [...leaves].map((l) => ({
|
|
7255
|
-
cid: l.cid,
|
|
7256
|
-
commitment: _MerkleEngine.normalizeHex32(l.commitment, "memo.commitment")
|
|
7257
|
-
})).sort((a, b) => a.cid - b.cid);
|
|
7258
|
-
for (let i = 0; i < sorted.length; i++) {
|
|
7259
|
-
if (sorted[i].cid !== i) throw new Error(`Non-contiguous persisted merkle leaves: expected cid=${i}, got cid=${sorted[i].cid}`);
|
|
7260
|
-
}
|
|
7261
|
-
const storedTree = await this.storage?.getMerkleTree?.(chainId);
|
|
7262
|
-
const leafCount = sorted.length;
|
|
7263
|
-
const mergedFromLeaves = _MerkleEngine.totalElementsInTree(BigInt(leafCount));
|
|
7264
|
-
const mergedFromTree = typeof storedTree?.totalElements === "number" && storedTree.totalElements > 0 ? storedTree.totalElements : 0;
|
|
7265
|
-
const mergedElements = Math.max(mergedFromLeaves, mergedFromTree);
|
|
7266
7301
|
const pending = this.ensurePendingLeaves(chainId);
|
|
7267
|
-
|
|
7268
|
-
|
|
7269
|
-
|
|
7270
|
-
|
|
7302
|
+
const latest = await this.storage?.getLatestChairmanMerkleVersion?.(chainId);
|
|
7303
|
+
if (latest) {
|
|
7304
|
+
state.mergedElements = latest.version;
|
|
7305
|
+
state.root = _MerkleEngine.normalizeHex32(latest.rootHash, "chairmanMerkleVersion.rootHash");
|
|
7271
7306
|
}
|
|
7272
|
-
|
|
7273
|
-
|
|
7274
|
-
|
|
7275
|
-
|
|
7276
|
-
|
|
7307
|
+
const leaves = await this.storage?.getMerkleLeaves?.(chainId);
|
|
7308
|
+
if (leaves && leaves.length > state.mergedElements) {
|
|
7309
|
+
const sorted = [...leaves].sort((a, b) => a.cid - b.cid).slice(state.mergedElements);
|
|
7310
|
+
pending.length = 0;
|
|
7311
|
+
pending.push(...sorted.map((l) => _MerkleEngine.normalizeHex32(l.commitment, "leaf.commitment")));
|
|
7277
7312
|
}
|
|
7278
7313
|
} catch (error) {
|
|
7279
7314
|
if (this.mode === "hybrid") return;
|
|
@@ -7286,26 +7321,7 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7286
7321
|
this.hydrateInFlight.set(chainId, task);
|
|
7287
7322
|
return task;
|
|
7288
7323
|
}
|
|
7289
|
-
|
|
7290
|
-
* Normalize unknown values to a 32-byte hex string.
|
|
7291
|
-
*/
|
|
7292
|
-
static normalizeHex32(value, name) {
|
|
7293
|
-
try {
|
|
7294
|
-
const bi = BigInt(value);
|
|
7295
|
-
if (bi < 0n) throw new Error("negative");
|
|
7296
|
-
const hex = bi.toString(16).padStart(64, "0");
|
|
7297
|
-
if (hex.length > 64) throw new Error("too_large");
|
|
7298
|
-
return `0x${hex}`;
|
|
7299
|
-
} catch (error) {
|
|
7300
|
-
throw new SdkError("MERKLE", `Invalid ${name}`, { value }, error);
|
|
7301
|
-
}
|
|
7302
|
-
}
|
|
7303
|
-
/**
|
|
7304
|
-
* Feed contiguous (cid-ordered) memo leaves into the local merkle tree.
|
|
7305
|
-
*
|
|
7306
|
-
* This mirrors the client/app behavior: only after we have a full consecutive batch of 32 leaves
|
|
7307
|
-
* do we merge them into the main tree. Leaves that are still in the buffer do not get local proofs.
|
|
7308
|
-
*/
|
|
7324
|
+
// ── Ingestion ──
|
|
7309
7325
|
async ingestEntryMemos(chainId, memos) {
|
|
7310
7326
|
if (this.mode === "remote") return;
|
|
7311
7327
|
await this.hydrateFromStorage(chainId);
|
|
@@ -7333,26 +7349,42 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7333
7349
|
expected++;
|
|
7334
7350
|
while (pending.length >= SUBTREE_SIZE) {
|
|
7335
7351
|
const batch = pending.splice(0, SUBTREE_SIZE);
|
|
7336
|
-
const
|
|
7337
|
-
const subtree = _MerkleEngine.buildSubtree(batch,
|
|
7338
|
-
const
|
|
7339
|
-
|
|
7340
|
-
|
|
7341
|
-
const
|
|
7342
|
-
|
|
7343
|
-
|
|
7344
|
-
|
|
7345
|
-
|
|
7346
|
-
|
|
7347
|
-
|
|
7348
|
-
|
|
7349
|
-
|
|
7350
|
-
|
|
7352
|
+
const batchIndex = state.mergedElements / SUBTREE_SIZE;
|
|
7353
|
+
const subtree = _MerkleEngine.buildSubtree(batch, state.mergedElements);
|
|
7354
|
+
const subtreeNodes = subtree.nodesToStore.map((n) => ({ ...n, chainId }));
|
|
7355
|
+
const prevVersion = await this.storage?.getLatestChairmanMerkleVersion?.(chainId);
|
|
7356
|
+
const prevRootId = prevVersion?.rootId ?? null;
|
|
7357
|
+
const newVersion = state.mergedElements + SUBTREE_SIZE;
|
|
7358
|
+
const result = await this.insertSubtreeRoot(chainId, prevRootId, subtree.subtreeRoot, batchIndex, newVersion);
|
|
7359
|
+
if (this.readContractRoot) {
|
|
7360
|
+
const rootIndex = newVersion / SUBTREE_SIZE;
|
|
7361
|
+
const onChainRoot = await this.readContractRoot(chainId, rootIndex).catch(() => null);
|
|
7362
|
+
if (onChainRoot !== null) {
|
|
7363
|
+
const onChainNorm = _MerkleEngine.normalizeHex32(onChainRoot, "onChainRoot");
|
|
7364
|
+
const isZero = BigInt(onChainNorm) === 0n;
|
|
7365
|
+
if (!isZero && onChainNorm !== result.rootHash) {
|
|
7366
|
+
const target = state.mergedElements;
|
|
7367
|
+
await this._rollback(chainId, target);
|
|
7368
|
+
throw new SdkError("MERKLE", "Local merkle root mismatch with on-chain root \u2014 rolled back", {
|
|
7369
|
+
chainId,
|
|
7370
|
+
rootIndex,
|
|
7371
|
+
localRoot: result.rootHash,
|
|
7372
|
+
onChainRoot: onChainNorm,
|
|
7373
|
+
version: newVersion,
|
|
7374
|
+
rollbackTarget: target
|
|
7375
|
+
});
|
|
7376
|
+
}
|
|
7377
|
+
}
|
|
7378
|
+
}
|
|
7379
|
+
await this.storage?.putChairmanMerkleNodes?.(chainId, [...subtreeNodes, ...result.nodes]);
|
|
7380
|
+
await this.storage?.putChairmanMerkleVersion?.(chainId, {
|
|
7351
7381
|
chainId,
|
|
7352
|
-
|
|
7353
|
-
|
|
7354
|
-
|
|
7382
|
+
version: newVersion,
|
|
7383
|
+
rootId: result.rootId,
|
|
7384
|
+
rootHash: result.rootHash
|
|
7355
7385
|
});
|
|
7386
|
+
state.mergedElements = newVersion;
|
|
7387
|
+
state.root = result.rootHash;
|
|
7356
7388
|
}
|
|
7357
7389
|
}
|
|
7358
7390
|
this.hydratedChains.add(chainId);
|
|
@@ -7363,20 +7395,33 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7363
7395
|
throw new SdkError("MERKLE", "Failed to ingest local merkle leaves", { chainId, leafCount: leaves.length }, error);
|
|
7364
7396
|
}
|
|
7365
7397
|
}
|
|
7398
|
+
// ── Rollback (tree O(1) + sync cursor reset) ──
|
|
7366
7399
|
/**
|
|
7367
|
-
*
|
|
7400
|
+
* Public rollback: step back one batch (32 elements) from the current position.
|
|
7401
|
+
* Upper-layer code calls this on any error to reset and retry.
|
|
7402
|
+
*
|
|
7403
|
+
* What gets rolled back:
|
|
7404
|
+
* - ChairmanMerkle tree version pointer (O(1) — old nodes still in storage)
|
|
7405
|
+
* - Pending leaves buffer (cleared)
|
|
7406
|
+
* - Sync cursor: memo + merkle fields (nullifier left unchanged — independent)
|
|
7368
7407
|
*
|
|
7369
|
-
*
|
|
7370
|
-
|
|
7408
|
+
* @returns true if rollback succeeded, false if already at 0 or target version doesn't exist.
|
|
7409
|
+
*/
|
|
7410
|
+
async rollback(chainId) {
|
|
7411
|
+
const state = this.ensureChainState(chainId);
|
|
7412
|
+
const target = Math.max(0, state.mergedElements - SUBTREE_SIZE);
|
|
7413
|
+
return this._rollback(chainId, target);
|
|
7414
|
+
}
|
|
7415
|
+
/**
|
|
7416
|
+
* Internal rollback to an exact batch boundary.
|
|
7371
7417
|
*
|
|
7372
|
-
* @param targetMergedElements Must be a non-negative multiple of 32
|
|
7418
|
+
* @param targetMergedElements Must be a non-negative multiple of 32.
|
|
7373
7419
|
* Pass 0 to reset to the empty tree.
|
|
7374
|
-
* @returns true if rollback succeeded, false if
|
|
7375
|
-
* subtree roots) is missing in storage.
|
|
7420
|
+
* @returns true if rollback succeeded, false if the target version doesn't exist.
|
|
7376
7421
|
*/
|
|
7377
|
-
async
|
|
7422
|
+
async _rollback(chainId, targetMergedElements) {
|
|
7378
7423
|
if (targetMergedElements < 0 || targetMergedElements % SUBTREE_SIZE !== 0) {
|
|
7379
|
-
throw new SdkError("MERKLE", "
|
|
7424
|
+
throw new SdkError("MERKLE", "_rollback target must be a non-negative multiple of 32", { targetMergedElements });
|
|
7380
7425
|
}
|
|
7381
7426
|
const state = this.ensureChainState(chainId);
|
|
7382
7427
|
const pending = this.ensurePendingLeaves(chainId);
|
|
@@ -7384,78 +7429,35 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7384
7429
|
state.mergedElements = 0;
|
|
7385
7430
|
state.root = getZeroHash(this.treeDepth);
|
|
7386
7431
|
pending.length = 0;
|
|
7387
|
-
await this.
|
|
7388
|
-
chainId,
|
|
7389
|
-
root: state.root,
|
|
7390
|
-
totalElements: 0,
|
|
7391
|
-
lastUpdated: Date.now()
|
|
7392
|
-
});
|
|
7432
|
+
await this.resetSyncCursor(chainId, 0);
|
|
7393
7433
|
return true;
|
|
7394
7434
|
}
|
|
7395
|
-
const
|
|
7396
|
-
if (!
|
|
7397
|
-
const numBatches = targetMergedElements / SUBTREE_SIZE;
|
|
7398
|
-
const subtreeRoots = [];
|
|
7399
|
-
for (let batch = 0; batch < numBatches; batch++) {
|
|
7400
|
-
const node = await this.storage?.getMerkleNode?.(chainId, `${SUBTREE_DEPTH}-${batch}`);
|
|
7401
|
-
if (!node) return false;
|
|
7402
|
-
subtreeRoots.push(node.hash);
|
|
7403
|
-
}
|
|
7404
|
-
const resetNodes = [];
|
|
7405
|
-
for (let level = SUBTREE_DEPTH; level < this.treeDepth; level++) {
|
|
7406
|
-
resetNodes.push({
|
|
7407
|
-
chainId,
|
|
7408
|
-
id: `frontier-${level}`,
|
|
7409
|
-
level,
|
|
7410
|
-
position: 0,
|
|
7411
|
-
hash: getZeroHash(level)
|
|
7412
|
-
});
|
|
7413
|
-
}
|
|
7414
|
-
await this.storage?.upsertMerkleNodes?.(chainId, resetNodes);
|
|
7415
|
-
let replayRoot = getZeroHash(this.treeDepth);
|
|
7416
|
-
for (let batch = 0; batch < numBatches; batch++) {
|
|
7417
|
-
const merged = await this.mergeSubtreeToMainTree({
|
|
7418
|
-
chainId,
|
|
7419
|
-
subtreeRoot: subtreeRoots[batch],
|
|
7420
|
-
newTotalElements: (batch + 1) * SUBTREE_SIZE
|
|
7421
|
-
});
|
|
7422
|
-
replayRoot = merged.finalRoot;
|
|
7423
|
-
await this.storage?.upsertMerkleNodes?.(chainId, merged.nodesToStore.map((n) => ({ ...n, chainId })));
|
|
7424
|
-
}
|
|
7425
|
-
const replayNorm = _MerkleEngine.normalizeHex32(replayRoot, "replay.root");
|
|
7426
|
-
const checkpointNorm = _MerkleEngine.normalizeHex32(checkpoint.hash, "checkpoint.root");
|
|
7427
|
-
if (replayNorm !== checkpointNorm) {
|
|
7428
|
-
return false;
|
|
7429
|
-
}
|
|
7435
|
+
const version = await this.storage?.getChairmanMerkleVersion?.(chainId, targetMergedElements);
|
|
7436
|
+
if (!version) return false;
|
|
7430
7437
|
state.mergedElements = targetMergedElements;
|
|
7431
|
-
state.root =
|
|
7438
|
+
state.root = _MerkleEngine.normalizeHex32(version.rootHash, "version.rootHash");
|
|
7432
7439
|
pending.length = 0;
|
|
7433
7440
|
this.hydratedChains.add(chainId);
|
|
7434
|
-
await this.
|
|
7435
|
-
chainId,
|
|
7436
|
-
root: state.root,
|
|
7437
|
-
totalElements: targetMergedElements,
|
|
7438
|
-
lastUpdated: Date.now()
|
|
7439
|
-
});
|
|
7440
|
-
const updatedCheckpoint = {
|
|
7441
|
-
chainId,
|
|
7442
|
-
id: `checkpoint-${targetMergedElements}`,
|
|
7443
|
-
level: -1,
|
|
7444
|
-
position: 0,
|
|
7445
|
-
hash: checkpointNorm
|
|
7446
|
-
};
|
|
7447
|
-
await this.storage?.upsertMerkleNodes?.(chainId, [updatedCheckpoint]);
|
|
7441
|
+
await this.resetSyncCursor(chainId, targetMergedElements);
|
|
7448
7442
|
return true;
|
|
7449
7443
|
}
|
|
7450
7444
|
/**
|
|
7451
|
-
*
|
|
7445
|
+
* Reset the sync cursor's memo field to `targetMemo` (and derive merkle cursor),
|
|
7446
|
+
* but only if the current cursor is ahead of the target.
|
|
7447
|
+
* Nullifier cursor is left unchanged — nullifiers are independent of tree state.
|
|
7452
7448
|
*/
|
|
7449
|
+
async resetSyncCursor(chainId, targetMemo) {
|
|
7450
|
+
if (!this.storage?.getSyncCursor || !this.storage?.setSyncCursor) return;
|
|
7451
|
+
const cursor = await this.storage.getSyncCursor(chainId);
|
|
7452
|
+
if (!cursor || cursor.memo <= targetMemo) return;
|
|
7453
|
+
cursor.memo = targetMemo;
|
|
7454
|
+
cursor.merkle = this.currentMerkleRootIndex(targetMemo);
|
|
7455
|
+
await this.storage.setSyncCursor(chainId, cursor);
|
|
7456
|
+
}
|
|
7457
|
+
// ── Proof generation ──
|
|
7453
7458
|
async getProofByCid(input) {
|
|
7454
7459
|
return this.getProofByCids({ chainId: input.chainId, cids: [input.cid], totalElements: input.totalElements });
|
|
7455
7460
|
}
|
|
7456
|
-
/**
|
|
7457
|
-
* Get merkle proofs for a set of cids using local/hybrid/remote logic.
|
|
7458
|
-
*/
|
|
7459
7461
|
async getProofByCids(input) {
|
|
7460
7462
|
const cids = [...input.cids];
|
|
7461
7463
|
if (cids.length === 0) throw new SdkError("MERKLE", "No cids provided", { chainId: input.chainId });
|
|
@@ -7470,15 +7472,16 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7470
7472
|
await this.hydrateFromStorage(input.chainId);
|
|
7471
7473
|
const canUseLocal = this.mode !== "remote";
|
|
7472
7474
|
if (canUseLocal) {
|
|
7473
|
-
const
|
|
7474
|
-
const hasDb = typeof this.storage?.getMerkleLeaf === "function" && typeof this.storage?.
|
|
7475
|
-
if (hasDb
|
|
7476
|
-
|
|
7475
|
+
const version = contractTreeElements > 0 ? await this.storage?.getChairmanMerkleVersion?.(input.chainId, contractTreeElements) : void 0;
|
|
7476
|
+
const hasDb = typeof this.storage?.getMerkleLeaf === "function" && typeof this.storage?.getChairmanMerkleNode === "function" && (contractTreeElements === 0 || !!version);
|
|
7477
|
+
if (hasDb) {
|
|
7478
|
+
const state = this.ensureChainState(input.chainId);
|
|
7479
|
+
if (contractTreeElements > 0 && state.mergedElements < contractTreeElements) {
|
|
7477
7480
|
if (this.mode === "local") {
|
|
7478
7481
|
throw new SdkError("MERKLE", "Local merkle db is behind contract", {
|
|
7479
7482
|
chainId: input.chainId,
|
|
7480
7483
|
cids,
|
|
7481
|
-
|
|
7484
|
+
localMergedElements: state.mergedElements,
|
|
7482
7485
|
contractTreeElements
|
|
7483
7486
|
});
|
|
7484
7487
|
}
|
|
@@ -7490,30 +7493,13 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7490
7493
|
proof.push({ leaf_index: cid, path: new Array(this.treeDepth + 1).fill("0") });
|
|
7491
7494
|
continue;
|
|
7492
7495
|
}
|
|
7493
|
-
const
|
|
7494
|
-
if (!leaf) throw new Error(`missing_leaf:${cid}`);
|
|
7495
|
-
const path2 = [leaf.commitment];
|
|
7496
|
-
for (let level = 1; level <= this.treeDepth; level++) {
|
|
7497
|
-
const siblingIndex = cid >> level - 1 ^ 1;
|
|
7498
|
-
if (level === 1) {
|
|
7499
|
-
const siblingLeaf = await this.storage.getMerkleLeaf(input.chainId, siblingIndex);
|
|
7500
|
-
path2.push(siblingLeaf?.commitment ?? getZeroHash(0));
|
|
7501
|
-
continue;
|
|
7502
|
-
}
|
|
7503
|
-
const targetLevel = level - 1;
|
|
7504
|
-
const siblingNode = await this.storage.getMerkleNode(input.chainId, `${targetLevel}-${siblingIndex}`);
|
|
7505
|
-
path2.push(siblingNode?.hash ?? getZeroHash(targetLevel));
|
|
7506
|
-
}
|
|
7496
|
+
const path2 = await this.buildLocalProofPath(input.chainId, cid, version);
|
|
7507
7497
|
proof.push({ leaf_index: cid, path: path2 });
|
|
7508
7498
|
}
|
|
7509
|
-
|
|
7510
|
-
if (tree.totalElements > contractTreeElements && contractTreeElements > 0) {
|
|
7511
|
-
const checkpoint = await this.storage.getMerkleNode(input.chainId, `checkpoint-${contractTreeElements}`);
|
|
7512
|
-
if (checkpoint) effectiveRoot = checkpoint.hash;
|
|
7513
|
-
}
|
|
7499
|
+
const effectiveRoot = contractTreeElements > 0 ? _MerkleEngine.normalizeHex32(version.rootHash, "version.rootHash") : getZeroHash(this.treeDepth);
|
|
7514
7500
|
return {
|
|
7515
7501
|
proof,
|
|
7516
|
-
merkle_root:
|
|
7502
|
+
merkle_root: effectiveRoot,
|
|
7517
7503
|
latest_cid: totalElements > 0n ? Number(totalElements - 1n) : -1
|
|
7518
7504
|
};
|
|
7519
7505
|
} catch (error) {
|
|
@@ -7523,7 +7509,7 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7523
7509
|
}
|
|
7524
7510
|
}
|
|
7525
7511
|
} else if (this.mode === "local" && needsTreeProof.length) {
|
|
7526
|
-
throw new SdkError("MERKLE", "Local merkle db unavailable", { chainId: input.chainId, cids, reason: "
|
|
7512
|
+
throw new SdkError("MERKLE", "Local merkle db unavailable", { chainId: input.chainId, cids, reason: "missing_adapter_or_version" });
|
|
7527
7513
|
}
|
|
7528
7514
|
}
|
|
7529
7515
|
if (needsTreeProof.length === 0) {
|
|
@@ -7548,15 +7534,64 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7548
7534
|
};
|
|
7549
7535
|
}
|
|
7550
7536
|
/**
|
|
7551
|
-
*
|
|
7537
|
+
* Build a local proof path by traversing the chairmanMerkle tree.
|
|
7538
|
+
*
|
|
7539
|
+
* Levels 0-4: sibling hashes from subtree internal nodes (st-{level}-{pos}).
|
|
7540
|
+
* Levels 5-31: sibling hashes from chairmanMerkle tree traversal (top-down from version root).
|
|
7552
7541
|
*/
|
|
7542
|
+
async buildLocalProofPath(chainId, cid, version) {
|
|
7543
|
+
const leaf = await this.storage.getMerkleLeaf(chainId, cid);
|
|
7544
|
+
if (!leaf) throw new Error(`missing_leaf:${cid}`);
|
|
7545
|
+
const path2 = [leaf.commitment];
|
|
7546
|
+
for (let level = 1; level <= SUBTREE_DEPTH; level++) {
|
|
7547
|
+
const siblingPos = cid >> level - 1 ^ 1;
|
|
7548
|
+
if (level === 1) {
|
|
7549
|
+
const siblingLeaf = await this.storage.getMerkleLeaf(chainId, siblingPos);
|
|
7550
|
+
path2.push(siblingLeaf?.commitment ?? getZeroHash(0));
|
|
7551
|
+
} else {
|
|
7552
|
+
const targetLevel = level - 1;
|
|
7553
|
+
const node = await this.storage.getChairmanMerkleNode(chainId, `st-${targetLevel}-${siblingPos}`);
|
|
7554
|
+
path2.push(node?.hash ?? getZeroHash(targetLevel));
|
|
7555
|
+
}
|
|
7556
|
+
}
|
|
7557
|
+
const batchIndex = cid >> SUBTREE_DEPTH;
|
|
7558
|
+
const MAIN_DEPTH = this.treeDepth - SUBTREE_DEPTH;
|
|
7559
|
+
const mainSiblings = [];
|
|
7560
|
+
let nodeId = version.rootId;
|
|
7561
|
+
for (let depth = 0; depth < MAIN_DEPTH; depth++) {
|
|
7562
|
+
const childLevel = this.treeDepth - depth - 1;
|
|
7563
|
+
if (!nodeId) {
|
|
7564
|
+
mainSiblings.push(getZeroHash(childLevel));
|
|
7565
|
+
continue;
|
|
7566
|
+
}
|
|
7567
|
+
const node = await this.storage.getChairmanMerkleNode(chainId, nodeId);
|
|
7568
|
+
if (!node) {
|
|
7569
|
+
mainSiblings.push(getZeroHash(childLevel));
|
|
7570
|
+
nodeId = null;
|
|
7571
|
+
continue;
|
|
7572
|
+
}
|
|
7573
|
+
const remainingDepth = MAIN_DEPTH - depth - 1;
|
|
7574
|
+
const goRight = (batchIndex >> remainingDepth & 1) === 1;
|
|
7575
|
+
if (goRight) {
|
|
7576
|
+
const leftNode = node.leftId ? await this.storage.getChairmanMerkleNode(chainId, node.leftId) : null;
|
|
7577
|
+
mainSiblings.push(leftNode?.hash ?? getZeroHash(childLevel));
|
|
7578
|
+
nodeId = node.rightId;
|
|
7579
|
+
} else {
|
|
7580
|
+
const rightNode = node.rightId ? await this.storage.getChairmanMerkleNode(chainId, node.rightId) : null;
|
|
7581
|
+
mainSiblings.push(rightNode?.hash ?? getZeroHash(childLevel));
|
|
7582
|
+
nodeId = node.leftId;
|
|
7583
|
+
}
|
|
7584
|
+
}
|
|
7585
|
+
for (let i = mainSiblings.length - 1; i >= 0; i--) {
|
|
7586
|
+
path2.push(mainSiblings[i]);
|
|
7587
|
+
}
|
|
7588
|
+
return path2;
|
|
7589
|
+
}
|
|
7590
|
+
// ── Remote helpers ──
|
|
7553
7591
|
async fetchRemoteRootOnly(chainId) {
|
|
7554
7592
|
const remote = await this.fetchRemoteProofFromService({ chainId, cids: [0] });
|
|
7555
7593
|
return _MerkleEngine.normalizeHex32(remote.merkle_root, "remote.merkle_root");
|
|
7556
7594
|
}
|
|
7557
|
-
/**
|
|
7558
|
-
* Fetch proofs from the remote merkle service.
|
|
7559
|
-
*/
|
|
7560
7595
|
async fetchRemoteProofFromService(input) {
|
|
7561
7596
|
const chain = this.getChain(input.chainId);
|
|
7562
7597
|
if (!chain.merkleProofUrl) {
|
|
@@ -7565,9 +7600,7 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7565
7600
|
const client = new MerkleClient(chain.merkleProofUrl);
|
|
7566
7601
|
return client.getProofByCids(input.cids);
|
|
7567
7602
|
}
|
|
7568
|
-
|
|
7569
|
-
* Build membership witnesses for provided UTXOs from a remote proof response.
|
|
7570
|
-
*/
|
|
7603
|
+
// ── Witness builders (unchanged) ──
|
|
7571
7604
|
buildAccMemberWitnesses(input) {
|
|
7572
7605
|
return input.utxos.map((utxo, idx) => {
|
|
7573
7606
|
const remoteProof = input.remote.proof[idx];
|
|
@@ -7581,9 +7614,6 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7581
7614
|
};
|
|
7582
7615
|
});
|
|
7583
7616
|
}
|
|
7584
|
-
/**
|
|
7585
|
-
* Convert UTXOs into circuit input secrets, decrypting memos and padding if needed.
|
|
7586
|
-
*/
|
|
7587
7617
|
async buildInputSecretsFromUtxos(input) {
|
|
7588
7618
|
if (!Array.isArray(input.utxos) || input.utxos.length === 0) {
|
|
7589
7619
|
throw new SdkError("MERKLE", "No utxos provided", { count: 0 });
|
|
@@ -8767,8 +8797,9 @@ var FileStore = class {
|
|
|
8767
8797
|
this.cursors = /* @__PURE__ */ new Map();
|
|
8768
8798
|
this.utxos = /* @__PURE__ */ new Map();
|
|
8769
8799
|
this.operations = [];
|
|
8770
|
-
this.
|
|
8771
|
-
this.
|
|
8800
|
+
this.chairmanMerkleLatestVersions = {};
|
|
8801
|
+
this.chairmanMerkleVersions = {};
|
|
8802
|
+
this.chairmanMerkleNodes = {};
|
|
8772
8803
|
this.entryMemos = {};
|
|
8773
8804
|
this.entryNullifiers = {};
|
|
8774
8805
|
this.saveChain = Promise.resolve();
|
|
@@ -8872,8 +8903,9 @@ var FileStore = class {
|
|
|
8872
8903
|
this.utxos.clear();
|
|
8873
8904
|
this.operations = [];
|
|
8874
8905
|
this.merkleNextCid.clear();
|
|
8875
|
-
this.
|
|
8876
|
-
this.
|
|
8906
|
+
this.chairmanMerkleLatestVersions = {};
|
|
8907
|
+
this.chairmanMerkleVersions = {};
|
|
8908
|
+
this.chairmanMerkleNodes = {};
|
|
8877
8909
|
this.entryMemos = {};
|
|
8878
8910
|
this.entryNullifiers = {};
|
|
8879
8911
|
try {
|
|
@@ -8889,13 +8921,24 @@ var FileStore = class {
|
|
|
8889
8921
|
try {
|
|
8890
8922
|
const raw = await (0, import_promises.readFile)(this.sharedFilePath(), "utf8");
|
|
8891
8923
|
const parsed = JSON.parse(raw);
|
|
8892
|
-
const
|
|
8893
|
-
if (
|
|
8894
|
-
this.
|
|
8924
|
+
const chairmanMerkleVersionsRaw = parsed.chairmanMerkleVersions;
|
|
8925
|
+
if (chairmanMerkleVersionsRaw && typeof chairmanMerkleVersionsRaw === "object") {
|
|
8926
|
+
this.chairmanMerkleVersions = chairmanMerkleVersionsRaw;
|
|
8927
|
+
for (const [chainKey, versions] of Object.entries(chairmanMerkleVersionsRaw)) {
|
|
8928
|
+
let latest;
|
|
8929
|
+
for (const record of Object.values(versions)) {
|
|
8930
|
+
if (!latest || record.version > latest.version) {
|
|
8931
|
+
latest = record;
|
|
8932
|
+
}
|
|
8933
|
+
}
|
|
8934
|
+
if (latest) {
|
|
8935
|
+
this.chairmanMerkleLatestVersions[chainKey] = latest;
|
|
8936
|
+
}
|
|
8937
|
+
}
|
|
8895
8938
|
}
|
|
8896
|
-
const
|
|
8897
|
-
if (
|
|
8898
|
-
this.
|
|
8939
|
+
const chairmanMerkleNodesRaw = parsed.chairmanMerkleNodes;
|
|
8940
|
+
if (chairmanMerkleNodesRaw && typeof chairmanMerkleNodesRaw === "object") {
|
|
8941
|
+
this.chairmanMerkleNodes = chairmanMerkleNodesRaw;
|
|
8899
8942
|
}
|
|
8900
8943
|
const entryMemosRaw = parsed.entryMemos;
|
|
8901
8944
|
if (entryMemosRaw && typeof entryMemosRaw === "object") {
|
|
@@ -8935,8 +8978,8 @@ var FileStore = class {
|
|
|
8935
8978
|
this.saveChain = this.saveChain.catch(() => void 0).then(async () => {
|
|
8936
8979
|
await (0, import_promises.mkdir)(this.options.baseDir, { recursive: true });
|
|
8937
8980
|
const sharedState = {
|
|
8938
|
-
|
|
8939
|
-
|
|
8981
|
+
chairmanMerkleVersions: this.chairmanMerkleVersions,
|
|
8982
|
+
chairmanMerkleNodes: this.chairmanMerkleNodes,
|
|
8940
8983
|
entryMemos: this.entryMemos,
|
|
8941
8984
|
entryNullifiers: this.entryNullifiers
|
|
8942
8985
|
};
|
|
@@ -8948,56 +8991,70 @@ var FileStore = class {
|
|
|
8948
8991
|
return this.saveChain;
|
|
8949
8992
|
}
|
|
8950
8993
|
/**
|
|
8951
|
-
* Get
|
|
8994
|
+
* Get a chairmanMerkle tree node by id.
|
|
8952
8995
|
*/
|
|
8953
|
-
async
|
|
8954
|
-
|
|
8955
|
-
if (!row) return void 0;
|
|
8956
|
-
const totalElements = Number(row.totalElements);
|
|
8957
|
-
const lastUpdated = Number(row.lastUpdated);
|
|
8958
|
-
const root = row.root;
|
|
8959
|
-
if (typeof root !== "string" || !root.startsWith("0x")) return void 0;
|
|
8960
|
-
if (!Number.isFinite(totalElements) || totalElements < 0) return void 0;
|
|
8961
|
-
return { chainId, root, totalElements: Math.floor(totalElements), lastUpdated: Number.isFinite(lastUpdated) ? Math.floor(lastUpdated) : 0 };
|
|
8996
|
+
async getChairmanMerkleNode(chainId, id) {
|
|
8997
|
+
return this.chairmanMerkleNodes[String(chainId)]?.[id];
|
|
8962
8998
|
}
|
|
8963
8999
|
/**
|
|
8964
|
-
*
|
|
9000
|
+
* Put chairmanMerkle tree nodes for a chain and persist.
|
|
8965
9001
|
*/
|
|
8966
|
-
async
|
|
8967
|
-
|
|
9002
|
+
async putChairmanMerkleNodes(chainId, nodes) {
|
|
9003
|
+
if (!nodes.length) return;
|
|
9004
|
+
const key = String(chainId);
|
|
9005
|
+
const existing = this.chairmanMerkleNodes[key] ?? {};
|
|
9006
|
+
for (const node of nodes) {
|
|
9007
|
+
existing[node.id] = { ...node, chainId };
|
|
9008
|
+
}
|
|
9009
|
+
this.chairmanMerkleNodes[key] = existing;
|
|
8968
9010
|
await this.saveShared();
|
|
8969
9011
|
}
|
|
8970
9012
|
/**
|
|
8971
|
-
*
|
|
9013
|
+
* Get a specific chairmanMerkle tree version for a chain.
|
|
8972
9014
|
*/
|
|
8973
|
-
async
|
|
8974
|
-
|
|
8975
|
-
await this.saveShared();
|
|
9015
|
+
async getChairmanMerkleVersion(chainId, version) {
|
|
9016
|
+
return this.chairmanMerkleVersions[String(chainId)]?.[version];
|
|
8976
9017
|
}
|
|
8977
9018
|
/**
|
|
8978
|
-
* Get
|
|
9019
|
+
* Get the latest (highest version number) chairmanMerkle tree version for a chain.
|
|
8979
9020
|
*/
|
|
8980
|
-
async
|
|
8981
|
-
|
|
9021
|
+
async getLatestChairmanMerkleVersion(chainId) {
|
|
9022
|
+
const cached = this.chairmanMerkleLatestVersions[String(chainId)];
|
|
9023
|
+
if (cached) return { ...cached };
|
|
9024
|
+
const versions = this.chairmanMerkleVersions[String(chainId)];
|
|
9025
|
+
if (!versions) return void 0;
|
|
9026
|
+
let latest;
|
|
9027
|
+
for (const record of Object.values(versions)) {
|
|
9028
|
+
if (!latest || record.version > latest.version) {
|
|
9029
|
+
latest = record;
|
|
9030
|
+
}
|
|
9031
|
+
}
|
|
9032
|
+
if (latest) {
|
|
9033
|
+
this.chairmanMerkleLatestVersions[String(chainId)] = latest;
|
|
9034
|
+
}
|
|
9035
|
+
return latest ? { ...latest } : void 0;
|
|
8982
9036
|
}
|
|
8983
9037
|
/**
|
|
8984
|
-
*
|
|
9038
|
+
* Persist a chairmanMerkle tree version for a chain and track latest.
|
|
8985
9039
|
*/
|
|
8986
|
-
async
|
|
8987
|
-
if (!nodes.length) return;
|
|
9040
|
+
async putChairmanMerkleVersion(chainId, record) {
|
|
8988
9041
|
const key = String(chainId);
|
|
8989
|
-
const existing = this.
|
|
8990
|
-
|
|
8991
|
-
|
|
9042
|
+
const existing = this.chairmanMerkleVersions[key] ?? {};
|
|
9043
|
+
existing[record.version] = { ...record, chainId };
|
|
9044
|
+
this.chairmanMerkleVersions[key] = existing;
|
|
9045
|
+
const currentLatest = this.chairmanMerkleLatestVersions[key];
|
|
9046
|
+
if (!currentLatest || record.version >= currentLatest.version) {
|
|
9047
|
+
this.chairmanMerkleLatestVersions[key] = { ...record, chainId };
|
|
8992
9048
|
}
|
|
8993
|
-
this.merkleNodes[key] = existing;
|
|
8994
9049
|
await this.saveShared();
|
|
8995
9050
|
}
|
|
8996
9051
|
/**
|
|
8997
|
-
* Clear
|
|
9052
|
+
* Clear chairmanMerkle tree nodes and versions for a chain.
|
|
8998
9053
|
*/
|
|
8999
|
-
async
|
|
9000
|
-
delete this.
|
|
9054
|
+
async clearChairmanMerkleTree(chainId) {
|
|
9055
|
+
delete this.chairmanMerkleNodes[String(chainId)];
|
|
9056
|
+
delete this.chairmanMerkleVersions[String(chainId)];
|
|
9057
|
+
delete this.chairmanMerkleLatestVersions[String(chainId)];
|
|
9001
9058
|
await this.saveShared();
|
|
9002
9059
|
}
|
|
9003
9060
|
/**
|
|
@@ -9277,10 +9334,6 @@ function toChanges(result) {
|
|
|
9277
9334
|
if (typeof changes === "bigint") return Number(changes);
|
|
9278
9335
|
return 0;
|
|
9279
9336
|
}
|
|
9280
|
-
function toHexOrNull(value) {
|
|
9281
|
-
if (typeof value !== "string") return null;
|
|
9282
|
-
return value.startsWith("0x") ? value : null;
|
|
9283
|
-
}
|
|
9284
9337
|
function sortTextValues(value) {
|
|
9285
9338
|
if (value == null) return void 0;
|
|
9286
9339
|
const list = Array.isArray(value) ? value : [value];
|
|
@@ -9420,21 +9473,21 @@ var SqliteStore = class {
|
|
|
9420
9473
|
PRIMARY KEY (chain_id, cid)
|
|
9421
9474
|
);
|
|
9422
9475
|
|
|
9423
|
-
CREATE TABLE IF NOT EXISTS
|
|
9476
|
+
CREATE TABLE IF NOT EXISTS chairman_merkle_nodes (
|
|
9424
9477
|
chain_id INTEGER NOT NULL,
|
|
9425
9478
|
id TEXT NOT NULL,
|
|
9426
|
-
level INTEGER NOT NULL,
|
|
9427
|
-
position INTEGER NOT NULL,
|
|
9428
9479
|
hash TEXT NOT NULL,
|
|
9480
|
+
left_id TEXT,
|
|
9481
|
+
right_id TEXT,
|
|
9429
9482
|
PRIMARY KEY (chain_id, id)
|
|
9430
9483
|
);
|
|
9431
|
-
CREATE INDEX IF NOT EXISTS idx_merkle_nodes_chain_level_pos ON merkle_nodes(chain_id, level, position);
|
|
9432
9484
|
|
|
9433
|
-
CREATE TABLE IF NOT EXISTS
|
|
9434
|
-
chain_id INTEGER
|
|
9435
|
-
|
|
9436
|
-
|
|
9437
|
-
|
|
9485
|
+
CREATE TABLE IF NOT EXISTS chairman_merkle_versions (
|
|
9486
|
+
chain_id INTEGER NOT NULL,
|
|
9487
|
+
version INTEGER NOT NULL,
|
|
9488
|
+
root_id TEXT NOT NULL,
|
|
9489
|
+
root_hash TEXT NOT NULL,
|
|
9490
|
+
PRIMARY KEY (chain_id, version)
|
|
9438
9491
|
);
|
|
9439
9492
|
|
|
9440
9493
|
CREATE TABLE IF NOT EXISTS entry_memos (
|
|
@@ -9671,29 +9724,29 @@ var SqliteStore = class {
|
|
|
9671
9724
|
async clearMerkleLeaves(chainId) {
|
|
9672
9725
|
this.run(`DELETE FROM merkle_leaves WHERE chain_id = ?`, [chainId]);
|
|
9673
9726
|
}
|
|
9674
|
-
async
|
|
9727
|
+
async getChairmanMerkleNode(chainId, id) {
|
|
9675
9728
|
const row = this.row(
|
|
9676
|
-
`SELECT id,
|
|
9729
|
+
`SELECT id, hash, left_id, right_id FROM chairman_merkle_nodes WHERE chain_id = ? AND id = ?`,
|
|
9677
9730
|
[chainId, id]
|
|
9678
9731
|
);
|
|
9679
9732
|
if (!row) return void 0;
|
|
9680
|
-
return { chainId, id: row.id,
|
|
9733
|
+
return { chainId, id: row.id, hash: row.hash, leftId: row.left_id, rightId: row.right_id };
|
|
9681
9734
|
}
|
|
9682
|
-
async
|
|
9735
|
+
async putChairmanMerkleNodes(chainId, nodes) {
|
|
9683
9736
|
if (!nodes.length) return;
|
|
9684
9737
|
const db = this.ensureDb();
|
|
9685
9738
|
const stmt = db.prepare(
|
|
9686
|
-
`INSERT INTO
|
|
9739
|
+
`INSERT INTO chairman_merkle_nodes (chain_id, id, hash, left_id, right_id)
|
|
9687
9740
|
VALUES (?, ?, ?, ?, ?)
|
|
9688
9741
|
ON CONFLICT(chain_id, id) DO UPDATE SET
|
|
9689
|
-
|
|
9690
|
-
|
|
9691
|
-
|
|
9742
|
+
hash = excluded.hash,
|
|
9743
|
+
left_id = excluded.left_id,
|
|
9744
|
+
right_id = excluded.right_id`
|
|
9692
9745
|
);
|
|
9693
9746
|
db.exec("BEGIN IMMEDIATE");
|
|
9694
9747
|
try {
|
|
9695
9748
|
for (const node of nodes) {
|
|
9696
|
-
stmt.run(chainId, node.id, node.
|
|
9749
|
+
stmt.run(chainId, node.id, node.hash, node.leftId ?? null, node.rightId ?? null);
|
|
9697
9750
|
}
|
|
9698
9751
|
db.exec("COMMIT");
|
|
9699
9752
|
} catch (error) {
|
|
@@ -9701,40 +9754,35 @@ var SqliteStore = class {
|
|
|
9701
9754
|
throw error;
|
|
9702
9755
|
}
|
|
9703
9756
|
}
|
|
9704
|
-
async
|
|
9705
|
-
|
|
9757
|
+
async getChairmanMerkleVersion(chainId, version) {
|
|
9758
|
+
const row = this.row(
|
|
9759
|
+
`SELECT version, root_id, root_hash FROM chairman_merkle_versions WHERE chain_id = ? AND version = ?`,
|
|
9760
|
+
[chainId, version]
|
|
9761
|
+
);
|
|
9762
|
+
if (!row) return void 0;
|
|
9763
|
+
return { chainId, version: row.version, rootId: row.root_id, rootHash: row.root_hash };
|
|
9706
9764
|
}
|
|
9707
|
-
async
|
|
9765
|
+
async getLatestChairmanMerkleVersion(chainId) {
|
|
9708
9766
|
const row = this.row(
|
|
9709
|
-
`SELECT
|
|
9767
|
+
`SELECT version, root_id, root_hash FROM chairman_merkle_versions WHERE chain_id = ? ORDER BY version DESC LIMIT 1`,
|
|
9710
9768
|
[chainId]
|
|
9711
9769
|
);
|
|
9712
9770
|
if (!row) return void 0;
|
|
9713
|
-
|
|
9714
|
-
if (!root) return void 0;
|
|
9715
|
-
const totalElements = Number(row.total_elements);
|
|
9716
|
-
if (!Number.isFinite(totalElements) || totalElements < 0) return void 0;
|
|
9717
|
-
const lastUpdated = Number(row.last_updated);
|
|
9718
|
-
return {
|
|
9719
|
-
chainId,
|
|
9720
|
-
root,
|
|
9721
|
-
totalElements: Math.floor(totalElements),
|
|
9722
|
-
lastUpdated: Number.isFinite(lastUpdated) ? Math.floor(lastUpdated) : 0
|
|
9723
|
-
};
|
|
9771
|
+
return { chainId, version: row.version, rootId: row.root_id, rootHash: row.root_hash };
|
|
9724
9772
|
}
|
|
9725
|
-
async
|
|
9773
|
+
async putChairmanMerkleVersion(chainId, record) {
|
|
9726
9774
|
this.run(
|
|
9727
|
-
`INSERT INTO
|
|
9775
|
+
`INSERT INTO chairman_merkle_versions (chain_id, version, root_id, root_hash)
|
|
9728
9776
|
VALUES (?, ?, ?, ?)
|
|
9729
|
-
ON CONFLICT(chain_id) DO UPDATE SET
|
|
9730
|
-
|
|
9731
|
-
|
|
9732
|
-
|
|
9733
|
-
[chainId, tree.root, tree.totalElements, tree.lastUpdated]
|
|
9777
|
+
ON CONFLICT(chain_id, version) DO UPDATE SET
|
|
9778
|
+
root_id = excluded.root_id,
|
|
9779
|
+
root_hash = excluded.root_hash`,
|
|
9780
|
+
[chainId, record.version, record.rootId, record.rootHash]
|
|
9734
9781
|
);
|
|
9735
9782
|
}
|
|
9736
|
-
async
|
|
9737
|
-
this.run(`DELETE FROM
|
|
9783
|
+
async clearChairmanMerkleTree(chainId) {
|
|
9784
|
+
this.run(`DELETE FROM chairman_merkle_nodes WHERE chain_id = ?`, [chainId]);
|
|
9785
|
+
this.run(`DELETE FROM chairman_merkle_versions WHERE chain_id = ?`, [chainId]);
|
|
9738
9786
|
}
|
|
9739
9787
|
async upsertEntryMemos(memos) {
|
|
9740
9788
|
if (!memos.length) return;
|