@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.js
CHANGED
|
@@ -3222,8 +3222,8 @@ var MemoryStore = class {
|
|
|
3222
3222
|
this.utxos = /* @__PURE__ */ new Map();
|
|
3223
3223
|
this.operations = [];
|
|
3224
3224
|
this.merkleLeavesByChain = /* @__PURE__ */ new Map();
|
|
3225
|
-
this.
|
|
3226
|
-
this.
|
|
3225
|
+
this.chairmanMerkleVersionsByChain = /* @__PURE__ */ new Map();
|
|
3226
|
+
this.chairmanMerkleNodesByChain = /* @__PURE__ */ new Map();
|
|
3227
3227
|
this.entryMemosByChain = /* @__PURE__ */ new Map();
|
|
3228
3228
|
this.entryNullifiersByChain = /* @__PURE__ */ new Map();
|
|
3229
3229
|
const max = options?.maxOperations;
|
|
@@ -3239,8 +3239,8 @@ var MemoryStore = class {
|
|
|
3239
3239
|
this.utxos.clear();
|
|
3240
3240
|
this.operations = [];
|
|
3241
3241
|
this.merkleLeavesByChain.clear();
|
|
3242
|
-
this.
|
|
3243
|
-
this.
|
|
3242
|
+
this.chairmanMerkleVersionsByChain.clear();
|
|
3243
|
+
this.chairmanMerkleNodesByChain.clear();
|
|
3244
3244
|
this.entryMemosByChain.clear();
|
|
3245
3245
|
this.entryNullifiersByChain.clear();
|
|
3246
3246
|
}
|
|
@@ -3356,49 +3356,64 @@ var MemoryStore = class {
|
|
|
3356
3356
|
return { chainId, cid: row.cid, commitment: row.commitment };
|
|
3357
3357
|
}
|
|
3358
3358
|
/**
|
|
3359
|
-
* Get a
|
|
3359
|
+
* Get a chairmanMerkle tree node by id.
|
|
3360
3360
|
*/
|
|
3361
|
-
async
|
|
3362
|
-
return this.
|
|
3361
|
+
async getChairmanMerkleNode(chainId, id) {
|
|
3362
|
+
return this.chairmanMerkleNodesByChain.get(chainId)?.get(id);
|
|
3363
3363
|
}
|
|
3364
3364
|
/**
|
|
3365
|
-
*
|
|
3365
|
+
* Put chairmanMerkle tree nodes for a chain.
|
|
3366
3366
|
*/
|
|
3367
|
-
async
|
|
3367
|
+
async putChairmanMerkleNodes(chainId, nodes) {
|
|
3368
3368
|
if (!nodes.length) return;
|
|
3369
|
-
let map = this.
|
|
3369
|
+
let map = this.chairmanMerkleNodesByChain.get(chainId);
|
|
3370
3370
|
if (!map) {
|
|
3371
3371
|
map = /* @__PURE__ */ new Map();
|
|
3372
|
-
this.
|
|
3372
|
+
this.chairmanMerkleNodesByChain.set(chainId, map);
|
|
3373
3373
|
}
|
|
3374
3374
|
for (const node of nodes) {
|
|
3375
3375
|
map.set(node.id, { ...node, chainId });
|
|
3376
3376
|
}
|
|
3377
3377
|
}
|
|
3378
3378
|
/**
|
|
3379
|
-
*
|
|
3379
|
+
* Get a chairmanMerkle version record by chain and version number.
|
|
3380
3380
|
*/
|
|
3381
|
-
async
|
|
3382
|
-
this.
|
|
3381
|
+
async getChairmanMerkleVersion(chainId, version) {
|
|
3382
|
+
const byVersion = this.chairmanMerkleVersionsByChain.get(chainId);
|
|
3383
|
+
const record = byVersion?.get(version);
|
|
3384
|
+
return record ? { ...record } : void 0;
|
|
3383
3385
|
}
|
|
3384
3386
|
/**
|
|
3385
|
-
* Get
|
|
3387
|
+
* Get the latest chairmanMerkle version record (highest version number) for a chain.
|
|
3386
3388
|
*/
|
|
3387
|
-
async
|
|
3388
|
-
const
|
|
3389
|
-
|
|
3389
|
+
async getLatestChairmanMerkleVersion(chainId) {
|
|
3390
|
+
const byVersion = this.chairmanMerkleVersionsByChain.get(chainId);
|
|
3391
|
+
if (!byVersion || byVersion.size === 0) return void 0;
|
|
3392
|
+
let latest;
|
|
3393
|
+
for (const record of byVersion.values()) {
|
|
3394
|
+
if (!latest || record.version > latest.version) {
|
|
3395
|
+
latest = record;
|
|
3396
|
+
}
|
|
3397
|
+
}
|
|
3398
|
+
return latest ? { ...latest } : void 0;
|
|
3390
3399
|
}
|
|
3391
3400
|
/**
|
|
3392
|
-
* Persist
|
|
3401
|
+
* Persist a chairmanMerkle version record.
|
|
3393
3402
|
*/
|
|
3394
|
-
async
|
|
3395
|
-
this.
|
|
3403
|
+
async putChairmanMerkleVersion(chainId, record) {
|
|
3404
|
+
let byVersion = this.chairmanMerkleVersionsByChain.get(chainId);
|
|
3405
|
+
if (!byVersion) {
|
|
3406
|
+
byVersion = /* @__PURE__ */ new Map();
|
|
3407
|
+
this.chairmanMerkleVersionsByChain.set(chainId, byVersion);
|
|
3408
|
+
}
|
|
3409
|
+
byVersion.set(record.version, { ...record, chainId });
|
|
3396
3410
|
}
|
|
3397
3411
|
/**
|
|
3398
|
-
* Clear
|
|
3412
|
+
* Clear all chairmanMerkle tree state (both nodes and versions) for a chain.
|
|
3399
3413
|
*/
|
|
3400
|
-
async
|
|
3401
|
-
this.
|
|
3414
|
+
async clearChairmanMerkleTree(chainId) {
|
|
3415
|
+
this.chairmanMerkleNodesByChain.delete(chainId);
|
|
3416
|
+
this.chairmanMerkleVersionsByChain.delete(chainId);
|
|
3402
3417
|
}
|
|
3403
3418
|
/**
|
|
3404
3419
|
* Upsert entry memos (raw EntryService cache).
|
|
@@ -3922,13 +3937,14 @@ var KeyValueStore = class {
|
|
|
3922
3937
|
this.utxoCache = /* @__PURE__ */ new Map();
|
|
3923
3938
|
this.operationCache = /* @__PURE__ */ new Map();
|
|
3924
3939
|
this.merkleLeafCids = {};
|
|
3925
|
-
this.
|
|
3926
|
-
this.
|
|
3940
|
+
this.chairmanMerkleLatestVersions = {};
|
|
3941
|
+
this.chairmanMerkleNodeIds = {};
|
|
3942
|
+
this.chairmanMerkleVersionNums = {};
|
|
3927
3943
|
this.entryMemoCids = {};
|
|
3928
3944
|
this.entryNullifierNids = {};
|
|
3929
3945
|
this.loadedMerkleLeaves = /* @__PURE__ */ new Set();
|
|
3930
|
-
this.
|
|
3931
|
-
this.
|
|
3946
|
+
this.loadedChairmanMerkleVersions = /* @__PURE__ */ new Set();
|
|
3947
|
+
this.loadedChairmanMerkleNodes = /* @__PURE__ */ new Set();
|
|
3932
3948
|
this.loadedEntryMemos = /* @__PURE__ */ new Set();
|
|
3933
3949
|
this.loadedEntryNullifiers = /* @__PURE__ */ new Set();
|
|
3934
3950
|
this.saveChain = Promise.resolve();
|
|
@@ -3962,9 +3978,6 @@ var KeyValueStore = class {
|
|
|
3962
3978
|
walletOperationKey(id) {
|
|
3963
3979
|
return `${this.walletBaseKey()}:operation:${id}`;
|
|
3964
3980
|
}
|
|
3965
|
-
sharedChainKey(part, chainId) {
|
|
3966
|
-
return `${this.keyPrefix()}:shared:${part}:${chainId}`;
|
|
3967
|
-
}
|
|
3968
3981
|
sharedChainMetaKey(part, chainId) {
|
|
3969
3982
|
return `${this.keyPrefix()}:shared:${part}:${chainId}:meta`;
|
|
3970
3983
|
}
|
|
@@ -4017,13 +4030,14 @@ var KeyValueStore = class {
|
|
|
4017
4030
|
this.operationCache.clear();
|
|
4018
4031
|
this.walletMetaLoaded = false;
|
|
4019
4032
|
this.merkleLeafCids = {};
|
|
4020
|
-
this.
|
|
4021
|
-
this.
|
|
4033
|
+
this.chairmanMerkleLatestVersions = {};
|
|
4034
|
+
this.chairmanMerkleNodeIds = {};
|
|
4035
|
+
this.chairmanMerkleVersionNums = {};
|
|
4022
4036
|
this.entryMemoCids = {};
|
|
4023
4037
|
this.entryNullifierNids = {};
|
|
4024
4038
|
this.loadedMerkleLeaves.clear();
|
|
4025
|
-
this.
|
|
4026
|
-
this.
|
|
4039
|
+
this.loadedChairmanMerkleVersions.clear();
|
|
4040
|
+
this.loadedChairmanMerkleNodes.clear();
|
|
4027
4041
|
this.loadedEntryMemos.clear();
|
|
4028
4042
|
this.loadedEntryNullifiers.clear();
|
|
4029
4043
|
}
|
|
@@ -4118,20 +4132,19 @@ var KeyValueStore = class {
|
|
|
4118
4132
|
this.merkleLeafCids[key] = new Set(this.parseNumberIndex(cidsRaw));
|
|
4119
4133
|
this.loadedMerkleLeaves.add(chainId);
|
|
4120
4134
|
}
|
|
4121
|
-
async
|
|
4122
|
-
if (this.
|
|
4135
|
+
async ensureChairmanMerkleVersionsLoaded(chainId) {
|
|
4136
|
+
if (this.loadedChairmanMerkleVersions.has(chainId)) return;
|
|
4123
4137
|
const key = String(chainId);
|
|
4124
|
-
const
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
this.loadedMerkleTrees.add(chainId);
|
|
4138
|
+
const numsRaw = await this.options.client.get(this.sharedChainMetaKey("chairmanMerkleVersions", chainId));
|
|
4139
|
+
this.chairmanMerkleVersionNums[key] = new Set(this.parseNumberIndex(numsRaw));
|
|
4140
|
+
this.loadedChairmanMerkleVersions.add(chainId);
|
|
4128
4141
|
}
|
|
4129
|
-
async
|
|
4130
|
-
if (this.
|
|
4142
|
+
async ensureChairmanMerkleNodesLoaded(chainId) {
|
|
4143
|
+
if (this.loadedChairmanMerkleNodes.has(chainId)) return;
|
|
4131
4144
|
const key = String(chainId);
|
|
4132
|
-
const idsRaw = await this.options.client.get(this.sharedChainMetaKey("
|
|
4133
|
-
this.
|
|
4134
|
-
this.
|
|
4145
|
+
const idsRaw = await this.options.client.get(this.sharedChainMetaKey("chairmanMerkleNodes", chainId));
|
|
4146
|
+
this.chairmanMerkleNodeIds[key] = new Set(this.parseStringIndex(idsRaw));
|
|
4147
|
+
this.loadedChairmanMerkleNodes.add(chainId);
|
|
4135
4148
|
}
|
|
4136
4149
|
async ensureEntryMemosLoaded(chainId) {
|
|
4137
4150
|
if (this.loadedEntryMemos.has(chainId)) return;
|
|
@@ -4147,64 +4160,88 @@ var KeyValueStore = class {
|
|
|
4147
4160
|
this.entryNullifierNids[key] = new Set(this.parseNumberIndex(nidsRaw));
|
|
4148
4161
|
this.loadedEntryNullifiers.add(chainId);
|
|
4149
4162
|
}
|
|
4150
|
-
async
|
|
4151
|
-
await this.
|
|
4152
|
-
if (!this.
|
|
4153
|
-
const raw = await this.options.client.get(this.sharedRecordKey("
|
|
4163
|
+
async getChairmanMerkleNode(chainId, id) {
|
|
4164
|
+
await this.ensureChairmanMerkleNodesLoaded(chainId);
|
|
4165
|
+
if (!this.chairmanMerkleNodeIds[String(chainId)]?.has(id)) return void 0;
|
|
4166
|
+
const raw = await this.options.client.get(this.sharedRecordKey("chairmanMerkleNodes", chainId, id));
|
|
4154
4167
|
const node = this.parseJson(raw, null);
|
|
4155
4168
|
if (!node) return void 0;
|
|
4156
4169
|
const hash = node.hash;
|
|
4157
4170
|
if (typeof hash !== "string" || !hash.startsWith("0x")) return void 0;
|
|
4158
4171
|
return { ...node, chainId };
|
|
4159
4172
|
}
|
|
4160
|
-
async
|
|
4173
|
+
async putChairmanMerkleNodes(chainId, nodes) {
|
|
4161
4174
|
if (!nodes.length) return;
|
|
4162
|
-
await this.
|
|
4175
|
+
await this.ensureChairmanMerkleNodesLoaded(chainId);
|
|
4163
4176
|
const key = String(chainId);
|
|
4164
|
-
const ids = this.
|
|
4177
|
+
const ids = this.chairmanMerkleNodeIds[key] ?? /* @__PURE__ */ new Set();
|
|
4165
4178
|
const beforeSize = ids.size;
|
|
4166
4179
|
for (const node of nodes) {
|
|
4167
4180
|
ids.add(node.id);
|
|
4168
4181
|
}
|
|
4169
|
-
this.
|
|
4182
|
+
this.chairmanMerkleNodeIds[key] = ids;
|
|
4170
4183
|
await this.enqueueWrite(async () => {
|
|
4171
|
-
await Promise.all(nodes.map((node) => this.writeJson(this.sharedRecordKey("
|
|
4184
|
+
await Promise.all(nodes.map((node) => this.writeJson(this.sharedRecordKey("chairmanMerkleNodes", chainId, node.id), { ...node, chainId })));
|
|
4172
4185
|
if (ids.size !== beforeSize) {
|
|
4173
|
-
await this.writeJson(this.sharedChainMetaKey("
|
|
4186
|
+
await this.writeJson(this.sharedChainMetaKey("chairmanMerkleNodes", chainId), Array.from(ids));
|
|
4174
4187
|
}
|
|
4175
4188
|
});
|
|
4176
4189
|
}
|
|
4177
|
-
async
|
|
4178
|
-
await this.
|
|
4179
|
-
|
|
4180
|
-
|
|
4190
|
+
async getChairmanMerkleVersion(chainId, version) {
|
|
4191
|
+
await this.ensureChairmanMerkleVersionsLoaded(chainId);
|
|
4192
|
+
if (!this.chairmanMerkleVersionNums[String(chainId)]?.has(version)) return void 0;
|
|
4193
|
+
const raw = await this.options.client.get(this.sharedRecordKey("chairmanMerkleVersions", chainId, version));
|
|
4194
|
+
const record = this.parseJson(raw, null);
|
|
4195
|
+
if (!record) return void 0;
|
|
4196
|
+
if (typeof record.rootHash !== "string" || !record.rootHash.startsWith("0x")) return void 0;
|
|
4197
|
+
if (typeof record.rootId !== "string") return void 0;
|
|
4198
|
+
const v = Number(record.version);
|
|
4199
|
+
if (!Number.isFinite(v) || v < 0) return void 0;
|
|
4200
|
+
return { chainId, version: Math.floor(v), rootId: record.rootId, rootHash: record.rootHash };
|
|
4201
|
+
}
|
|
4202
|
+
async getLatestChairmanMerkleVersion(chainId) {
|
|
4203
|
+
await this.ensureChairmanMerkleVersionsLoaded(chainId);
|
|
4204
|
+
const nums = this.chairmanMerkleVersionNums[String(chainId)];
|
|
4205
|
+
if (!nums || nums.size === 0) return void 0;
|
|
4206
|
+
const maxVersion = Math.max(...nums);
|
|
4207
|
+
return this.getChairmanMerkleVersion(chainId, maxVersion);
|
|
4208
|
+
}
|
|
4209
|
+
async putChairmanMerkleVersion(chainId, record) {
|
|
4210
|
+
await this.ensureChairmanMerkleVersionsLoaded(chainId);
|
|
4211
|
+
const key = String(chainId);
|
|
4212
|
+
const nums = this.chairmanMerkleVersionNums[key] ?? /* @__PURE__ */ new Set();
|
|
4213
|
+
const beforeSize = nums.size;
|
|
4214
|
+
nums.add(record.version);
|
|
4215
|
+
this.chairmanMerkleVersionNums[key] = nums;
|
|
4216
|
+
const current = this.chairmanMerkleLatestVersions[key];
|
|
4217
|
+
if (!current || record.version >= current.version) {
|
|
4218
|
+
this.chairmanMerkleLatestVersions[key] = { ...record, chainId };
|
|
4219
|
+
}
|
|
4220
|
+
const row = { ...record, chainId };
|
|
4181
4221
|
await this.enqueueWrite(async () => {
|
|
4182
|
-
await
|
|
4183
|
-
|
|
4222
|
+
await this.writeJson(this.sharedRecordKey("chairmanMerkleVersions", chainId, record.version), row);
|
|
4223
|
+
if (nums.size !== beforeSize) {
|
|
4224
|
+
await this.writeJson(this.sharedChainMetaKey("chairmanMerkleVersions", chainId), Array.from(nums).sort((a, b) => a - b));
|
|
4225
|
+
}
|
|
4184
4226
|
});
|
|
4185
4227
|
}
|
|
4186
|
-
async
|
|
4187
|
-
await this.
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
const
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
if (!Number.isFinite(totalElements) || totalElements < 0) return void 0;
|
|
4195
|
-
return { chainId, root, totalElements: Math.floor(totalElements), lastUpdated: Number.isFinite(lastUpdated) ? Math.floor(lastUpdated) : 0 };
|
|
4196
|
-
}
|
|
4197
|
-
async setMerkleTree(chainId, tree) {
|
|
4198
|
-
await this.ensureMerkleTreeLoaded(chainId);
|
|
4199
|
-
const row = { ...tree, chainId };
|
|
4200
|
-
this.merkleTrees[String(chainId)] = row;
|
|
4201
|
-
await this.enqueueWrite(() => this.writeJson(this.sharedChainKey("merkleTrees", chainId), row));
|
|
4202
|
-
}
|
|
4203
|
-
async clearMerkleTree(chainId) {
|
|
4204
|
-
await this.ensureMerkleTreeLoaded(chainId);
|
|
4205
|
-
delete this.merkleTrees[String(chainId)];
|
|
4228
|
+
async clearChairmanMerkleTree(chainId) {
|
|
4229
|
+
await this.ensureChairmanMerkleNodesLoaded(chainId);
|
|
4230
|
+
await this.ensureChairmanMerkleVersionsLoaded(chainId);
|
|
4231
|
+
const nodeIds = Array.from(this.chairmanMerkleNodeIds[String(chainId)] ?? []);
|
|
4232
|
+
const versionNums = Array.from(this.chairmanMerkleVersionNums[String(chainId)] ?? []);
|
|
4233
|
+
delete this.chairmanMerkleNodeIds[String(chainId)];
|
|
4234
|
+
delete this.chairmanMerkleVersionNums[String(chainId)];
|
|
4235
|
+
delete this.chairmanMerkleLatestVersions[String(chainId)];
|
|
4206
4236
|
await this.enqueueWrite(async () => {
|
|
4207
|
-
await
|
|
4237
|
+
await Promise.all([
|
|
4238
|
+
...nodeIds.map((id) => this.deleteOrReset(this.sharedRecordKey("chairmanMerkleNodes", chainId, id), null)),
|
|
4239
|
+
...versionNums.map((v) => this.deleteOrReset(this.sharedRecordKey("chairmanMerkleVersions", chainId, v), null))
|
|
4240
|
+
]);
|
|
4241
|
+
await Promise.all([
|
|
4242
|
+
this.deleteOrReset(this.sharedChainMetaKey("chairmanMerkleNodes", chainId), []),
|
|
4243
|
+
this.deleteOrReset(this.sharedChainMetaKey("chairmanMerkleVersions", chainId), [])
|
|
4244
|
+
]);
|
|
4208
4245
|
});
|
|
4209
4246
|
}
|
|
4210
4247
|
async upsertEntryMemos(memos) {
|
|
@@ -7048,6 +7085,7 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7048
7085
|
this.hydrateInFlight = /* @__PURE__ */ new Map();
|
|
7049
7086
|
this.mode = options?.mode ?? "hybrid";
|
|
7050
7087
|
this.treeDepth = Math.max(1, Math.floor(options?.treeDepth ?? TREE_DEPTH_DEFAULT));
|
|
7088
|
+
this.readContractRoot = options?.readContractRoot;
|
|
7051
7089
|
}
|
|
7052
7090
|
/**
|
|
7053
7091
|
* Compute the current merkle root index from total elements.
|
|
@@ -7056,9 +7094,6 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7056
7094
|
if (totalElements <= tempArraySize) return 0;
|
|
7057
7095
|
return Math.floor((totalElements - 1) / tempArraySize);
|
|
7058
7096
|
}
|
|
7059
|
-
/**
|
|
7060
|
-
* Get or initialize the pending leaf buffer for a chain.
|
|
7061
|
-
*/
|
|
7062
7097
|
ensurePendingLeaves(chainId) {
|
|
7063
7098
|
let pending = this.pendingLeavesByChain.get(chainId);
|
|
7064
7099
|
if (!pending) {
|
|
@@ -7067,9 +7102,6 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7067
7102
|
}
|
|
7068
7103
|
return pending;
|
|
7069
7104
|
}
|
|
7070
|
-
/**
|
|
7071
|
-
* Get or initialize chain-level merkle state.
|
|
7072
|
-
*/
|
|
7073
7105
|
ensureChainState(chainId) {
|
|
7074
7106
|
let state = this.chainStateByChain.get(chainId);
|
|
7075
7107
|
if (!state) {
|
|
@@ -7078,15 +7110,32 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7078
7110
|
}
|
|
7079
7111
|
return state;
|
|
7080
7112
|
}
|
|
7081
|
-
|
|
7082
|
-
* Poseidon2 merkle hash for a left/right pair.
|
|
7083
|
-
*/
|
|
7113
|
+
// ── Hashing ──
|
|
7084
7114
|
static hashPair(left, right) {
|
|
7085
7115
|
return Poseidon2.hashToHex(BigInt(left), BigInt(right), Poseidon2Domain.Merkle);
|
|
7086
7116
|
}
|
|
7117
|
+
static normalizeHex32(value, name) {
|
|
7118
|
+
try {
|
|
7119
|
+
const bi = BigInt(value);
|
|
7120
|
+
if (bi < 0n) throw new Error("negative");
|
|
7121
|
+
const hex = bi.toString(16).padStart(64, "0");
|
|
7122
|
+
if (hex.length > 64) throw new Error("too_large");
|
|
7123
|
+
return `0x${hex}`;
|
|
7124
|
+
} catch (error) {
|
|
7125
|
+
throw new SdkError("MERKLE", `Invalid ${name}`, { value }, error);
|
|
7126
|
+
}
|
|
7127
|
+
}
|
|
7128
|
+
// ── Static helpers ──
|
|
7129
|
+
static totalElementsInTree(totalElements, tempArraySize = TEMP_ARRAY_SIZE_DEFAULT) {
|
|
7130
|
+
if (tempArraySize <= 0) throw new SdkError("MERKLE", "tempArraySize must be greater than zero", { tempArraySize });
|
|
7131
|
+
if (totalElements <= 0n) return 0;
|
|
7132
|
+
const size = BigInt(tempArraySize);
|
|
7133
|
+
return Number((totalElements - 1n) / size * size);
|
|
7134
|
+
}
|
|
7135
|
+
// ── Subtree (levels 0-5, 32 leaves → 1 root) ──
|
|
7087
7136
|
/**
|
|
7088
7137
|
* Build a fixed-depth subtree from 32 contiguous leaves.
|
|
7089
|
-
* Returns the subtree root and all intermediate nodes for storage.
|
|
7138
|
+
* Returns the subtree root hash and all intermediate nodes for storage.
|
|
7090
7139
|
*/
|
|
7091
7140
|
static buildSubtree(leafCommitments, baseIndex) {
|
|
7092
7141
|
if (leafCommitments.length !== SUBTREE_SIZE) {
|
|
@@ -7107,71 +7156,71 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7107
7156
|
const position = basePos + i;
|
|
7108
7157
|
nodesToStore.push({
|
|
7109
7158
|
chainId: 0,
|
|
7110
|
-
id:
|
|
7111
|
-
|
|
7112
|
-
|
|
7113
|
-
|
|
7159
|
+
id: `st-${level}-${position}`,
|
|
7160
|
+
hash: next[i],
|
|
7161
|
+
leftId: null,
|
|
7162
|
+
rightId: null
|
|
7114
7163
|
});
|
|
7115
7164
|
}
|
|
7116
7165
|
currentLevel = next;
|
|
7117
7166
|
}
|
|
7118
7167
|
return { subtreeRoot: currentLevel[0], nodesToStore };
|
|
7119
7168
|
}
|
|
7169
|
+
// ── ChairmanMerkle tree (persistent segment tree, levels 5-32) ──
|
|
7120
7170
|
/**
|
|
7121
|
-
*
|
|
7122
|
-
|
|
7123
|
-
|
|
7124
|
-
|
|
7125
|
-
|
|
7126
|
-
|
|
7127
|
-
|
|
7128
|
-
*
|
|
7129
|
-
*/
|
|
7130
|
-
async
|
|
7131
|
-
|
|
7132
|
-
|
|
7133
|
-
const
|
|
7134
|
-
|
|
7135
|
-
|
|
7136
|
-
|
|
7137
|
-
|
|
7138
|
-
|
|
7139
|
-
|
|
7140
|
-
|
|
7141
|
-
|
|
7142
|
-
|
|
7143
|
-
|
|
7144
|
-
|
|
7145
|
-
|
|
7171
|
+
* Insert a subtree root into the persistent main tree.
|
|
7172
|
+
*
|
|
7173
|
+
* Top-down recursive: descends from root (level treeDepth) to the target
|
|
7174
|
+
* leaf position (level SUBTREE_DEPTH). At each level only the node on the
|
|
7175
|
+
* update path is newly created; the sibling is shared from the previous
|
|
7176
|
+
* version's tree.
|
|
7177
|
+
*
|
|
7178
|
+
* @returns new root node ID/hash and all newly created nodes.
|
|
7179
|
+
*/
|
|
7180
|
+
async insertSubtreeRoot(chainId, prevRootId, subtreeRootHash, batchIndex, version) {
|
|
7181
|
+
const MAIN_DEPTH = this.treeDepth - SUBTREE_DEPTH;
|
|
7182
|
+
const nodes = [];
|
|
7183
|
+
const descend = async (nodeId, depth) => {
|
|
7184
|
+
const originalLevel = this.treeDepth - depth;
|
|
7185
|
+
if (depth === MAIN_DEPTH) {
|
|
7186
|
+
const newId2 = `cm-${version}-${originalLevel}`;
|
|
7187
|
+
nodes.push({ chainId, id: newId2, hash: subtreeRootHash, leftId: null, rightId: null });
|
|
7188
|
+
return { id: newId2, hash: subtreeRootHash };
|
|
7189
|
+
}
|
|
7190
|
+
let prevLeftId = null;
|
|
7191
|
+
let prevRightId = null;
|
|
7192
|
+
if (nodeId) {
|
|
7193
|
+
const prevNode = await this.storage?.getChairmanMerkleNode?.(chainId, nodeId);
|
|
7194
|
+
if (prevNode) {
|
|
7195
|
+
prevLeftId = prevNode.leftId;
|
|
7196
|
+
prevRightId = prevNode.rightId;
|
|
7146
7197
|
}
|
|
7147
|
-
currentValue = _MerkleEngine.hashPair(currentValue, getZeroHash(level));
|
|
7148
|
-
} else {
|
|
7149
|
-
const leftHash = await this.getNodeHash(input.chainId, `frontier-${level}`) ?? getZeroHash(level);
|
|
7150
|
-
currentValue = _MerkleEngine.hashPair(leftHash, currentValue);
|
|
7151
7198
|
}
|
|
7152
|
-
const
|
|
7153
|
-
|
|
7154
|
-
|
|
7155
|
-
|
|
7156
|
-
|
|
7157
|
-
|
|
7158
|
-
hash:
|
|
7159
|
-
|
|
7160
|
-
|
|
7161
|
-
|
|
7162
|
-
|
|
7163
|
-
|
|
7164
|
-
|
|
7165
|
-
|
|
7166
|
-
|
|
7167
|
-
|
|
7168
|
-
|
|
7169
|
-
|
|
7170
|
-
|
|
7199
|
+
const childLevel = originalLevel - 1;
|
|
7200
|
+
const remainingDepth = MAIN_DEPTH - depth - 1;
|
|
7201
|
+
const goRight = (batchIndex >> remainingDepth & 1) === 1;
|
|
7202
|
+
let leftResult;
|
|
7203
|
+
let rightResult;
|
|
7204
|
+
if (goRight) {
|
|
7205
|
+
const leftHash = prevLeftId ? (await this.storage?.getChairmanMerkleNode?.(chainId, prevLeftId))?.hash ?? getZeroHash(childLevel) : getZeroHash(childLevel);
|
|
7206
|
+
leftResult = { id: prevLeftId, hash: leftHash };
|
|
7207
|
+
const right = await descend(prevRightId, depth + 1);
|
|
7208
|
+
rightResult = { id: right.id, hash: right.hash };
|
|
7209
|
+
} else {
|
|
7210
|
+
const left = await descend(prevLeftId, depth + 1);
|
|
7211
|
+
leftResult = { id: left.id, hash: left.hash };
|
|
7212
|
+
const rightHash = prevRightId ? (await this.storage?.getChairmanMerkleNode?.(chainId, prevRightId))?.hash ?? getZeroHash(childLevel) : getZeroHash(childLevel);
|
|
7213
|
+
rightResult = { id: prevRightId, hash: rightHash };
|
|
7214
|
+
}
|
|
7215
|
+
const hash = _MerkleEngine.hashPair(leftResult.hash, rightResult.hash);
|
|
7216
|
+
const newId = `cm-${version}-${originalLevel}`;
|
|
7217
|
+
nodes.push({ chainId, id: newId, hash, leftId: leftResult.id, rightId: rightResult.id });
|
|
7218
|
+
return { id: newId, hash };
|
|
7219
|
+
};
|
|
7220
|
+
const root = await descend(prevRootId, 0);
|
|
7221
|
+
return { rootId: root.id, rootHash: root.hash, nodes };
|
|
7171
7222
|
}
|
|
7172
|
-
|
|
7173
|
-
* Hydrate local merkle state from storage on first use.
|
|
7174
|
-
*/
|
|
7223
|
+
// ── Hydration ──
|
|
7175
7224
|
async hydrateFromStorage(chainId) {
|
|
7176
7225
|
if (this.mode === "remote") return;
|
|
7177
7226
|
if (this.hydratedChains.has(chainId)) return;
|
|
@@ -7180,31 +7229,17 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7180
7229
|
const task = (async () => {
|
|
7181
7230
|
try {
|
|
7182
7231
|
const state = this.ensureChainState(chainId);
|
|
7183
|
-
const leaves = await this.storage?.getMerkleLeaves?.(chainId);
|
|
7184
|
-
if (!leaves || leaves.length === 0) return;
|
|
7185
|
-
const sorted = [...leaves].map((l) => ({
|
|
7186
|
-
cid: l.cid,
|
|
7187
|
-
commitment: _MerkleEngine.normalizeHex32(l.commitment, "memo.commitment")
|
|
7188
|
-
})).sort((a, b) => a.cid - b.cid);
|
|
7189
|
-
for (let i = 0; i < sorted.length; i++) {
|
|
7190
|
-
if (sorted[i].cid !== i) throw new Error(`Non-contiguous persisted merkle leaves: expected cid=${i}, got cid=${sorted[i].cid}`);
|
|
7191
|
-
}
|
|
7192
|
-
const storedTree = await this.storage?.getMerkleTree?.(chainId);
|
|
7193
|
-
const leafCount = sorted.length;
|
|
7194
|
-
const mergedFromLeaves = _MerkleEngine.totalElementsInTree(BigInt(leafCount));
|
|
7195
|
-
const mergedFromTree = typeof storedTree?.totalElements === "number" && storedTree.totalElements > 0 ? storedTree.totalElements : 0;
|
|
7196
|
-
const mergedElements = Math.max(mergedFromLeaves, mergedFromTree);
|
|
7197
7232
|
const pending = this.ensurePendingLeaves(chainId);
|
|
7198
|
-
|
|
7199
|
-
|
|
7200
|
-
|
|
7201
|
-
|
|
7233
|
+
const latest = await this.storage?.getLatestChairmanMerkleVersion?.(chainId);
|
|
7234
|
+
if (latest) {
|
|
7235
|
+
state.mergedElements = latest.version;
|
|
7236
|
+
state.root = _MerkleEngine.normalizeHex32(latest.rootHash, "chairmanMerkleVersion.rootHash");
|
|
7202
7237
|
}
|
|
7203
|
-
|
|
7204
|
-
|
|
7205
|
-
|
|
7206
|
-
|
|
7207
|
-
|
|
7238
|
+
const leaves = await this.storage?.getMerkleLeaves?.(chainId);
|
|
7239
|
+
if (leaves && leaves.length > state.mergedElements) {
|
|
7240
|
+
const sorted = [...leaves].sort((a, b) => a.cid - b.cid).slice(state.mergedElements);
|
|
7241
|
+
pending.length = 0;
|
|
7242
|
+
pending.push(...sorted.map((l) => _MerkleEngine.normalizeHex32(l.commitment, "leaf.commitment")));
|
|
7208
7243
|
}
|
|
7209
7244
|
} catch (error) {
|
|
7210
7245
|
if (this.mode === "hybrid") return;
|
|
@@ -7217,26 +7252,7 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7217
7252
|
this.hydrateInFlight.set(chainId, task);
|
|
7218
7253
|
return task;
|
|
7219
7254
|
}
|
|
7220
|
-
|
|
7221
|
-
* Normalize unknown values to a 32-byte hex string.
|
|
7222
|
-
*/
|
|
7223
|
-
static normalizeHex32(value, name) {
|
|
7224
|
-
try {
|
|
7225
|
-
const bi = BigInt(value);
|
|
7226
|
-
if (bi < 0n) throw new Error("negative");
|
|
7227
|
-
const hex = bi.toString(16).padStart(64, "0");
|
|
7228
|
-
if (hex.length > 64) throw new Error("too_large");
|
|
7229
|
-
return `0x${hex}`;
|
|
7230
|
-
} catch (error) {
|
|
7231
|
-
throw new SdkError("MERKLE", `Invalid ${name}`, { value }, error);
|
|
7232
|
-
}
|
|
7233
|
-
}
|
|
7234
|
-
/**
|
|
7235
|
-
* Feed contiguous (cid-ordered) memo leaves into the local merkle tree.
|
|
7236
|
-
*
|
|
7237
|
-
* This mirrors the client/app behavior: only after we have a full consecutive batch of 32 leaves
|
|
7238
|
-
* do we merge them into the main tree. Leaves that are still in the buffer do not get local proofs.
|
|
7239
|
-
*/
|
|
7255
|
+
// ── Ingestion ──
|
|
7240
7256
|
async ingestEntryMemos(chainId, memos) {
|
|
7241
7257
|
if (this.mode === "remote") return;
|
|
7242
7258
|
await this.hydrateFromStorage(chainId);
|
|
@@ -7264,26 +7280,42 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7264
7280
|
expected++;
|
|
7265
7281
|
while (pending.length >= SUBTREE_SIZE) {
|
|
7266
7282
|
const batch = pending.splice(0, SUBTREE_SIZE);
|
|
7267
|
-
const
|
|
7268
|
-
const subtree = _MerkleEngine.buildSubtree(batch,
|
|
7269
|
-
const
|
|
7270
|
-
|
|
7271
|
-
|
|
7272
|
-
const
|
|
7273
|
-
|
|
7274
|
-
|
|
7275
|
-
|
|
7276
|
-
|
|
7277
|
-
|
|
7278
|
-
|
|
7279
|
-
|
|
7280
|
-
|
|
7281
|
-
|
|
7283
|
+
const batchIndex = state.mergedElements / SUBTREE_SIZE;
|
|
7284
|
+
const subtree = _MerkleEngine.buildSubtree(batch, state.mergedElements);
|
|
7285
|
+
const subtreeNodes = subtree.nodesToStore.map((n) => ({ ...n, chainId }));
|
|
7286
|
+
const prevVersion = await this.storage?.getLatestChairmanMerkleVersion?.(chainId);
|
|
7287
|
+
const prevRootId = prevVersion?.rootId ?? null;
|
|
7288
|
+
const newVersion = state.mergedElements + SUBTREE_SIZE;
|
|
7289
|
+
const result = await this.insertSubtreeRoot(chainId, prevRootId, subtree.subtreeRoot, batchIndex, newVersion);
|
|
7290
|
+
if (this.readContractRoot) {
|
|
7291
|
+
const rootIndex = newVersion / SUBTREE_SIZE;
|
|
7292
|
+
const onChainRoot = await this.readContractRoot(chainId, rootIndex).catch(() => null);
|
|
7293
|
+
if (onChainRoot !== null) {
|
|
7294
|
+
const onChainNorm = _MerkleEngine.normalizeHex32(onChainRoot, "onChainRoot");
|
|
7295
|
+
const isZero = BigInt(onChainNorm) === 0n;
|
|
7296
|
+
if (!isZero && onChainNorm !== result.rootHash) {
|
|
7297
|
+
const target = state.mergedElements;
|
|
7298
|
+
await this._rollback(chainId, target);
|
|
7299
|
+
throw new SdkError("MERKLE", "Local merkle root mismatch with on-chain root \u2014 rolled back", {
|
|
7300
|
+
chainId,
|
|
7301
|
+
rootIndex,
|
|
7302
|
+
localRoot: result.rootHash,
|
|
7303
|
+
onChainRoot: onChainNorm,
|
|
7304
|
+
version: newVersion,
|
|
7305
|
+
rollbackTarget: target
|
|
7306
|
+
});
|
|
7307
|
+
}
|
|
7308
|
+
}
|
|
7309
|
+
}
|
|
7310
|
+
await this.storage?.putChairmanMerkleNodes?.(chainId, [...subtreeNodes, ...result.nodes]);
|
|
7311
|
+
await this.storage?.putChairmanMerkleVersion?.(chainId, {
|
|
7282
7312
|
chainId,
|
|
7283
|
-
|
|
7284
|
-
|
|
7285
|
-
|
|
7313
|
+
version: newVersion,
|
|
7314
|
+
rootId: result.rootId,
|
|
7315
|
+
rootHash: result.rootHash
|
|
7286
7316
|
});
|
|
7317
|
+
state.mergedElements = newVersion;
|
|
7318
|
+
state.root = result.rootHash;
|
|
7287
7319
|
}
|
|
7288
7320
|
}
|
|
7289
7321
|
this.hydratedChains.add(chainId);
|
|
@@ -7294,20 +7326,33 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7294
7326
|
throw new SdkError("MERKLE", "Failed to ingest local merkle leaves", { chainId, leafCount: leaves.length }, error);
|
|
7295
7327
|
}
|
|
7296
7328
|
}
|
|
7329
|
+
// ── Rollback (tree O(1) + sync cursor reset) ──
|
|
7297
7330
|
/**
|
|
7298
|
-
*
|
|
7331
|
+
* Public rollback: step back one batch (32 elements) from the current position.
|
|
7332
|
+
* Upper-layer code calls this on any error to reset and retry.
|
|
7333
|
+
*
|
|
7334
|
+
* What gets rolled back:
|
|
7335
|
+
* - ChairmanMerkle tree version pointer (O(1) — old nodes still in storage)
|
|
7336
|
+
* - Pending leaves buffer (cleared)
|
|
7337
|
+
* - Sync cursor: memo + merkle fields (nullifier left unchanged — independent)
|
|
7299
7338
|
*
|
|
7300
|
-
*
|
|
7301
|
-
|
|
7339
|
+
* @returns true if rollback succeeded, false if already at 0 or target version doesn't exist.
|
|
7340
|
+
*/
|
|
7341
|
+
async rollback(chainId) {
|
|
7342
|
+
const state = this.ensureChainState(chainId);
|
|
7343
|
+
const target = Math.max(0, state.mergedElements - SUBTREE_SIZE);
|
|
7344
|
+
return this._rollback(chainId, target);
|
|
7345
|
+
}
|
|
7346
|
+
/**
|
|
7347
|
+
* Internal rollback to an exact batch boundary.
|
|
7302
7348
|
*
|
|
7303
|
-
* @param targetMergedElements Must be a non-negative multiple of 32
|
|
7349
|
+
* @param targetMergedElements Must be a non-negative multiple of 32.
|
|
7304
7350
|
* Pass 0 to reset to the empty tree.
|
|
7305
|
-
* @returns true if rollback succeeded, false if
|
|
7306
|
-
* subtree roots) is missing in storage.
|
|
7351
|
+
* @returns true if rollback succeeded, false if the target version doesn't exist.
|
|
7307
7352
|
*/
|
|
7308
|
-
async
|
|
7353
|
+
async _rollback(chainId, targetMergedElements) {
|
|
7309
7354
|
if (targetMergedElements < 0 || targetMergedElements % SUBTREE_SIZE !== 0) {
|
|
7310
|
-
throw new SdkError("MERKLE", "
|
|
7355
|
+
throw new SdkError("MERKLE", "_rollback target must be a non-negative multiple of 32", { targetMergedElements });
|
|
7311
7356
|
}
|
|
7312
7357
|
const state = this.ensureChainState(chainId);
|
|
7313
7358
|
const pending = this.ensurePendingLeaves(chainId);
|
|
@@ -7315,78 +7360,35 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7315
7360
|
state.mergedElements = 0;
|
|
7316
7361
|
state.root = getZeroHash(this.treeDepth);
|
|
7317
7362
|
pending.length = 0;
|
|
7318
|
-
await this.
|
|
7319
|
-
chainId,
|
|
7320
|
-
root: state.root,
|
|
7321
|
-
totalElements: 0,
|
|
7322
|
-
lastUpdated: Date.now()
|
|
7323
|
-
});
|
|
7363
|
+
await this.resetSyncCursor(chainId, 0);
|
|
7324
7364
|
return true;
|
|
7325
7365
|
}
|
|
7326
|
-
const
|
|
7327
|
-
if (!
|
|
7328
|
-
const numBatches = targetMergedElements / SUBTREE_SIZE;
|
|
7329
|
-
const subtreeRoots = [];
|
|
7330
|
-
for (let batch = 0; batch < numBatches; batch++) {
|
|
7331
|
-
const node = await this.storage?.getMerkleNode?.(chainId, `${SUBTREE_DEPTH}-${batch}`);
|
|
7332
|
-
if (!node) return false;
|
|
7333
|
-
subtreeRoots.push(node.hash);
|
|
7334
|
-
}
|
|
7335
|
-
const resetNodes = [];
|
|
7336
|
-
for (let level = SUBTREE_DEPTH; level < this.treeDepth; level++) {
|
|
7337
|
-
resetNodes.push({
|
|
7338
|
-
chainId,
|
|
7339
|
-
id: `frontier-${level}`,
|
|
7340
|
-
level,
|
|
7341
|
-
position: 0,
|
|
7342
|
-
hash: getZeroHash(level)
|
|
7343
|
-
});
|
|
7344
|
-
}
|
|
7345
|
-
await this.storage?.upsertMerkleNodes?.(chainId, resetNodes);
|
|
7346
|
-
let replayRoot = getZeroHash(this.treeDepth);
|
|
7347
|
-
for (let batch = 0; batch < numBatches; batch++) {
|
|
7348
|
-
const merged = await this.mergeSubtreeToMainTree({
|
|
7349
|
-
chainId,
|
|
7350
|
-
subtreeRoot: subtreeRoots[batch],
|
|
7351
|
-
newTotalElements: (batch + 1) * SUBTREE_SIZE
|
|
7352
|
-
});
|
|
7353
|
-
replayRoot = merged.finalRoot;
|
|
7354
|
-
await this.storage?.upsertMerkleNodes?.(chainId, merged.nodesToStore.map((n) => ({ ...n, chainId })));
|
|
7355
|
-
}
|
|
7356
|
-
const replayNorm = _MerkleEngine.normalizeHex32(replayRoot, "replay.root");
|
|
7357
|
-
const checkpointNorm = _MerkleEngine.normalizeHex32(checkpoint.hash, "checkpoint.root");
|
|
7358
|
-
if (replayNorm !== checkpointNorm) {
|
|
7359
|
-
return false;
|
|
7360
|
-
}
|
|
7366
|
+
const version = await this.storage?.getChairmanMerkleVersion?.(chainId, targetMergedElements);
|
|
7367
|
+
if (!version) return false;
|
|
7361
7368
|
state.mergedElements = targetMergedElements;
|
|
7362
|
-
state.root =
|
|
7369
|
+
state.root = _MerkleEngine.normalizeHex32(version.rootHash, "version.rootHash");
|
|
7363
7370
|
pending.length = 0;
|
|
7364
7371
|
this.hydratedChains.add(chainId);
|
|
7365
|
-
await this.
|
|
7366
|
-
chainId,
|
|
7367
|
-
root: state.root,
|
|
7368
|
-
totalElements: targetMergedElements,
|
|
7369
|
-
lastUpdated: Date.now()
|
|
7370
|
-
});
|
|
7371
|
-
const updatedCheckpoint = {
|
|
7372
|
-
chainId,
|
|
7373
|
-
id: `checkpoint-${targetMergedElements}`,
|
|
7374
|
-
level: -1,
|
|
7375
|
-
position: 0,
|
|
7376
|
-
hash: checkpointNorm
|
|
7377
|
-
};
|
|
7378
|
-
await this.storage?.upsertMerkleNodes?.(chainId, [updatedCheckpoint]);
|
|
7372
|
+
await this.resetSyncCursor(chainId, targetMergedElements);
|
|
7379
7373
|
return true;
|
|
7380
7374
|
}
|
|
7381
7375
|
/**
|
|
7382
|
-
*
|
|
7376
|
+
* Reset the sync cursor's memo field to `targetMemo` (and derive merkle cursor),
|
|
7377
|
+
* but only if the current cursor is ahead of the target.
|
|
7378
|
+
* Nullifier cursor is left unchanged — nullifiers are independent of tree state.
|
|
7383
7379
|
*/
|
|
7380
|
+
async resetSyncCursor(chainId, targetMemo) {
|
|
7381
|
+
if (!this.storage?.getSyncCursor || !this.storage?.setSyncCursor) return;
|
|
7382
|
+
const cursor = await this.storage.getSyncCursor(chainId);
|
|
7383
|
+
if (!cursor || cursor.memo <= targetMemo) return;
|
|
7384
|
+
cursor.memo = targetMemo;
|
|
7385
|
+
cursor.merkle = this.currentMerkleRootIndex(targetMemo);
|
|
7386
|
+
await this.storage.setSyncCursor(chainId, cursor);
|
|
7387
|
+
}
|
|
7388
|
+
// ── Proof generation ──
|
|
7384
7389
|
async getProofByCid(input) {
|
|
7385
7390
|
return this.getProofByCids({ chainId: input.chainId, cids: [input.cid], totalElements: input.totalElements });
|
|
7386
7391
|
}
|
|
7387
|
-
/**
|
|
7388
|
-
* Get merkle proofs for a set of cids using local/hybrid/remote logic.
|
|
7389
|
-
*/
|
|
7390
7392
|
async getProofByCids(input) {
|
|
7391
7393
|
const cids = [...input.cids];
|
|
7392
7394
|
if (cids.length === 0) throw new SdkError("MERKLE", "No cids provided", { chainId: input.chainId });
|
|
@@ -7401,15 +7403,16 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7401
7403
|
await this.hydrateFromStorage(input.chainId);
|
|
7402
7404
|
const canUseLocal = this.mode !== "remote";
|
|
7403
7405
|
if (canUseLocal) {
|
|
7404
|
-
const
|
|
7405
|
-
const hasDb = typeof this.storage?.getMerkleLeaf === "function" && typeof this.storage?.
|
|
7406
|
-
if (hasDb
|
|
7407
|
-
|
|
7406
|
+
const version = contractTreeElements > 0 ? await this.storage?.getChairmanMerkleVersion?.(input.chainId, contractTreeElements) : void 0;
|
|
7407
|
+
const hasDb = typeof this.storage?.getMerkleLeaf === "function" && typeof this.storage?.getChairmanMerkleNode === "function" && (contractTreeElements === 0 || !!version);
|
|
7408
|
+
if (hasDb) {
|
|
7409
|
+
const state = this.ensureChainState(input.chainId);
|
|
7410
|
+
if (contractTreeElements > 0 && state.mergedElements < contractTreeElements) {
|
|
7408
7411
|
if (this.mode === "local") {
|
|
7409
7412
|
throw new SdkError("MERKLE", "Local merkle db is behind contract", {
|
|
7410
7413
|
chainId: input.chainId,
|
|
7411
7414
|
cids,
|
|
7412
|
-
|
|
7415
|
+
localMergedElements: state.mergedElements,
|
|
7413
7416
|
contractTreeElements
|
|
7414
7417
|
});
|
|
7415
7418
|
}
|
|
@@ -7421,30 +7424,13 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7421
7424
|
proof.push({ leaf_index: cid, path: new Array(this.treeDepth + 1).fill("0") });
|
|
7422
7425
|
continue;
|
|
7423
7426
|
}
|
|
7424
|
-
const
|
|
7425
|
-
if (!leaf) throw new Error(`missing_leaf:${cid}`);
|
|
7426
|
-
const path2 = [leaf.commitment];
|
|
7427
|
-
for (let level = 1; level <= this.treeDepth; level++) {
|
|
7428
|
-
const siblingIndex = cid >> level - 1 ^ 1;
|
|
7429
|
-
if (level === 1) {
|
|
7430
|
-
const siblingLeaf = await this.storage.getMerkleLeaf(input.chainId, siblingIndex);
|
|
7431
|
-
path2.push(siblingLeaf?.commitment ?? getZeroHash(0));
|
|
7432
|
-
continue;
|
|
7433
|
-
}
|
|
7434
|
-
const targetLevel = level - 1;
|
|
7435
|
-
const siblingNode = await this.storage.getMerkleNode(input.chainId, `${targetLevel}-${siblingIndex}`);
|
|
7436
|
-
path2.push(siblingNode?.hash ?? getZeroHash(targetLevel));
|
|
7437
|
-
}
|
|
7427
|
+
const path2 = await this.buildLocalProofPath(input.chainId, cid, version);
|
|
7438
7428
|
proof.push({ leaf_index: cid, path: path2 });
|
|
7439
7429
|
}
|
|
7440
|
-
|
|
7441
|
-
if (tree.totalElements > contractTreeElements && contractTreeElements > 0) {
|
|
7442
|
-
const checkpoint = await this.storage.getMerkleNode(input.chainId, `checkpoint-${contractTreeElements}`);
|
|
7443
|
-
if (checkpoint) effectiveRoot = checkpoint.hash;
|
|
7444
|
-
}
|
|
7430
|
+
const effectiveRoot = contractTreeElements > 0 ? _MerkleEngine.normalizeHex32(version.rootHash, "version.rootHash") : getZeroHash(this.treeDepth);
|
|
7445
7431
|
return {
|
|
7446
7432
|
proof,
|
|
7447
|
-
merkle_root:
|
|
7433
|
+
merkle_root: effectiveRoot,
|
|
7448
7434
|
latest_cid: totalElements > 0n ? Number(totalElements - 1n) : -1
|
|
7449
7435
|
};
|
|
7450
7436
|
} catch (error) {
|
|
@@ -7454,7 +7440,7 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7454
7440
|
}
|
|
7455
7441
|
}
|
|
7456
7442
|
} else if (this.mode === "local" && needsTreeProof.length) {
|
|
7457
|
-
throw new SdkError("MERKLE", "Local merkle db unavailable", { chainId: input.chainId, cids, reason: "
|
|
7443
|
+
throw new SdkError("MERKLE", "Local merkle db unavailable", { chainId: input.chainId, cids, reason: "missing_adapter_or_version" });
|
|
7458
7444
|
}
|
|
7459
7445
|
}
|
|
7460
7446
|
if (needsTreeProof.length === 0) {
|
|
@@ -7479,15 +7465,64 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7479
7465
|
};
|
|
7480
7466
|
}
|
|
7481
7467
|
/**
|
|
7482
|
-
*
|
|
7468
|
+
* Build a local proof path by traversing the chairmanMerkle tree.
|
|
7469
|
+
*
|
|
7470
|
+
* Levels 0-4: sibling hashes from subtree internal nodes (st-{level}-{pos}).
|
|
7471
|
+
* Levels 5-31: sibling hashes from chairmanMerkle tree traversal (top-down from version root).
|
|
7483
7472
|
*/
|
|
7473
|
+
async buildLocalProofPath(chainId, cid, version) {
|
|
7474
|
+
const leaf = await this.storage.getMerkleLeaf(chainId, cid);
|
|
7475
|
+
if (!leaf) throw new Error(`missing_leaf:${cid}`);
|
|
7476
|
+
const path2 = [leaf.commitment];
|
|
7477
|
+
for (let level = 1; level <= SUBTREE_DEPTH; level++) {
|
|
7478
|
+
const siblingPos = cid >> level - 1 ^ 1;
|
|
7479
|
+
if (level === 1) {
|
|
7480
|
+
const siblingLeaf = await this.storage.getMerkleLeaf(chainId, siblingPos);
|
|
7481
|
+
path2.push(siblingLeaf?.commitment ?? getZeroHash(0));
|
|
7482
|
+
} else {
|
|
7483
|
+
const targetLevel = level - 1;
|
|
7484
|
+
const node = await this.storage.getChairmanMerkleNode(chainId, `st-${targetLevel}-${siblingPos}`);
|
|
7485
|
+
path2.push(node?.hash ?? getZeroHash(targetLevel));
|
|
7486
|
+
}
|
|
7487
|
+
}
|
|
7488
|
+
const batchIndex = cid >> SUBTREE_DEPTH;
|
|
7489
|
+
const MAIN_DEPTH = this.treeDepth - SUBTREE_DEPTH;
|
|
7490
|
+
const mainSiblings = [];
|
|
7491
|
+
let nodeId = version.rootId;
|
|
7492
|
+
for (let depth = 0; depth < MAIN_DEPTH; depth++) {
|
|
7493
|
+
const childLevel = this.treeDepth - depth - 1;
|
|
7494
|
+
if (!nodeId) {
|
|
7495
|
+
mainSiblings.push(getZeroHash(childLevel));
|
|
7496
|
+
continue;
|
|
7497
|
+
}
|
|
7498
|
+
const node = await this.storage.getChairmanMerkleNode(chainId, nodeId);
|
|
7499
|
+
if (!node) {
|
|
7500
|
+
mainSiblings.push(getZeroHash(childLevel));
|
|
7501
|
+
nodeId = null;
|
|
7502
|
+
continue;
|
|
7503
|
+
}
|
|
7504
|
+
const remainingDepth = MAIN_DEPTH - depth - 1;
|
|
7505
|
+
const goRight = (batchIndex >> remainingDepth & 1) === 1;
|
|
7506
|
+
if (goRight) {
|
|
7507
|
+
const leftNode = node.leftId ? await this.storage.getChairmanMerkleNode(chainId, node.leftId) : null;
|
|
7508
|
+
mainSiblings.push(leftNode?.hash ?? getZeroHash(childLevel));
|
|
7509
|
+
nodeId = node.rightId;
|
|
7510
|
+
} else {
|
|
7511
|
+
const rightNode = node.rightId ? await this.storage.getChairmanMerkleNode(chainId, node.rightId) : null;
|
|
7512
|
+
mainSiblings.push(rightNode?.hash ?? getZeroHash(childLevel));
|
|
7513
|
+
nodeId = node.leftId;
|
|
7514
|
+
}
|
|
7515
|
+
}
|
|
7516
|
+
for (let i = mainSiblings.length - 1; i >= 0; i--) {
|
|
7517
|
+
path2.push(mainSiblings[i]);
|
|
7518
|
+
}
|
|
7519
|
+
return path2;
|
|
7520
|
+
}
|
|
7521
|
+
// ── Remote helpers ──
|
|
7484
7522
|
async fetchRemoteRootOnly(chainId) {
|
|
7485
7523
|
const remote = await this.fetchRemoteProofFromService({ chainId, cids: [0] });
|
|
7486
7524
|
return _MerkleEngine.normalizeHex32(remote.merkle_root, "remote.merkle_root");
|
|
7487
7525
|
}
|
|
7488
|
-
/**
|
|
7489
|
-
* Fetch proofs from the remote merkle service.
|
|
7490
|
-
*/
|
|
7491
7526
|
async fetchRemoteProofFromService(input) {
|
|
7492
7527
|
const chain = this.getChain(input.chainId);
|
|
7493
7528
|
if (!chain.merkleProofUrl) {
|
|
@@ -7496,9 +7531,7 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7496
7531
|
const client = new MerkleClient(chain.merkleProofUrl);
|
|
7497
7532
|
return client.getProofByCids(input.cids);
|
|
7498
7533
|
}
|
|
7499
|
-
|
|
7500
|
-
* Build membership witnesses for provided UTXOs from a remote proof response.
|
|
7501
|
-
*/
|
|
7534
|
+
// ── Witness builders (unchanged) ──
|
|
7502
7535
|
buildAccMemberWitnesses(input) {
|
|
7503
7536
|
return input.utxos.map((utxo, idx) => {
|
|
7504
7537
|
const remoteProof = input.remote.proof[idx];
|
|
@@ -7512,9 +7545,6 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7512
7545
|
};
|
|
7513
7546
|
});
|
|
7514
7547
|
}
|
|
7515
|
-
/**
|
|
7516
|
-
* Convert UTXOs into circuit input secrets, decrypting memos and padding if needed.
|
|
7517
|
-
*/
|
|
7518
7548
|
async buildInputSecretsFromUtxos(input) {
|
|
7519
7549
|
if (!Array.isArray(input.utxos) || input.utxos.length === 0) {
|
|
7520
7550
|
throw new SdkError("MERKLE", "No utxos provided", { count: 0 });
|
|
@@ -8698,8 +8728,9 @@ var FileStore = class {
|
|
|
8698
8728
|
this.cursors = /* @__PURE__ */ new Map();
|
|
8699
8729
|
this.utxos = /* @__PURE__ */ new Map();
|
|
8700
8730
|
this.operations = [];
|
|
8701
|
-
this.
|
|
8702
|
-
this.
|
|
8731
|
+
this.chairmanMerkleLatestVersions = {};
|
|
8732
|
+
this.chairmanMerkleVersions = {};
|
|
8733
|
+
this.chairmanMerkleNodes = {};
|
|
8703
8734
|
this.entryMemos = {};
|
|
8704
8735
|
this.entryNullifiers = {};
|
|
8705
8736
|
this.saveChain = Promise.resolve();
|
|
@@ -8803,8 +8834,9 @@ var FileStore = class {
|
|
|
8803
8834
|
this.utxos.clear();
|
|
8804
8835
|
this.operations = [];
|
|
8805
8836
|
this.merkleNextCid.clear();
|
|
8806
|
-
this.
|
|
8807
|
-
this.
|
|
8837
|
+
this.chairmanMerkleLatestVersions = {};
|
|
8838
|
+
this.chairmanMerkleVersions = {};
|
|
8839
|
+
this.chairmanMerkleNodes = {};
|
|
8808
8840
|
this.entryMemos = {};
|
|
8809
8841
|
this.entryNullifiers = {};
|
|
8810
8842
|
try {
|
|
@@ -8820,13 +8852,24 @@ var FileStore = class {
|
|
|
8820
8852
|
try {
|
|
8821
8853
|
const raw = await readFile(this.sharedFilePath(), "utf8");
|
|
8822
8854
|
const parsed = JSON.parse(raw);
|
|
8823
|
-
const
|
|
8824
|
-
if (
|
|
8825
|
-
this.
|
|
8855
|
+
const chairmanMerkleVersionsRaw = parsed.chairmanMerkleVersions;
|
|
8856
|
+
if (chairmanMerkleVersionsRaw && typeof chairmanMerkleVersionsRaw === "object") {
|
|
8857
|
+
this.chairmanMerkleVersions = chairmanMerkleVersionsRaw;
|
|
8858
|
+
for (const [chainKey, versions] of Object.entries(chairmanMerkleVersionsRaw)) {
|
|
8859
|
+
let latest;
|
|
8860
|
+
for (const record of Object.values(versions)) {
|
|
8861
|
+
if (!latest || record.version > latest.version) {
|
|
8862
|
+
latest = record;
|
|
8863
|
+
}
|
|
8864
|
+
}
|
|
8865
|
+
if (latest) {
|
|
8866
|
+
this.chairmanMerkleLatestVersions[chainKey] = latest;
|
|
8867
|
+
}
|
|
8868
|
+
}
|
|
8826
8869
|
}
|
|
8827
|
-
const
|
|
8828
|
-
if (
|
|
8829
|
-
this.
|
|
8870
|
+
const chairmanMerkleNodesRaw = parsed.chairmanMerkleNodes;
|
|
8871
|
+
if (chairmanMerkleNodesRaw && typeof chairmanMerkleNodesRaw === "object") {
|
|
8872
|
+
this.chairmanMerkleNodes = chairmanMerkleNodesRaw;
|
|
8830
8873
|
}
|
|
8831
8874
|
const entryMemosRaw = parsed.entryMemos;
|
|
8832
8875
|
if (entryMemosRaw && typeof entryMemosRaw === "object") {
|
|
@@ -8866,8 +8909,8 @@ var FileStore = class {
|
|
|
8866
8909
|
this.saveChain = this.saveChain.catch(() => void 0).then(async () => {
|
|
8867
8910
|
await mkdir(this.options.baseDir, { recursive: true });
|
|
8868
8911
|
const sharedState = {
|
|
8869
|
-
|
|
8870
|
-
|
|
8912
|
+
chairmanMerkleVersions: this.chairmanMerkleVersions,
|
|
8913
|
+
chairmanMerkleNodes: this.chairmanMerkleNodes,
|
|
8871
8914
|
entryMemos: this.entryMemos,
|
|
8872
8915
|
entryNullifiers: this.entryNullifiers
|
|
8873
8916
|
};
|
|
@@ -8879,56 +8922,70 @@ var FileStore = class {
|
|
|
8879
8922
|
return this.saveChain;
|
|
8880
8923
|
}
|
|
8881
8924
|
/**
|
|
8882
|
-
* Get
|
|
8925
|
+
* Get a chairmanMerkle tree node by id.
|
|
8883
8926
|
*/
|
|
8884
|
-
async
|
|
8885
|
-
|
|
8886
|
-
if (!row) return void 0;
|
|
8887
|
-
const totalElements = Number(row.totalElements);
|
|
8888
|
-
const lastUpdated = Number(row.lastUpdated);
|
|
8889
|
-
const root = row.root;
|
|
8890
|
-
if (typeof root !== "string" || !root.startsWith("0x")) return void 0;
|
|
8891
|
-
if (!Number.isFinite(totalElements) || totalElements < 0) return void 0;
|
|
8892
|
-
return { chainId, root, totalElements: Math.floor(totalElements), lastUpdated: Number.isFinite(lastUpdated) ? Math.floor(lastUpdated) : 0 };
|
|
8927
|
+
async getChairmanMerkleNode(chainId, id) {
|
|
8928
|
+
return this.chairmanMerkleNodes[String(chainId)]?.[id];
|
|
8893
8929
|
}
|
|
8894
8930
|
/**
|
|
8895
|
-
*
|
|
8931
|
+
* Put chairmanMerkle tree nodes for a chain and persist.
|
|
8896
8932
|
*/
|
|
8897
|
-
async
|
|
8898
|
-
|
|
8933
|
+
async putChairmanMerkleNodes(chainId, nodes) {
|
|
8934
|
+
if (!nodes.length) return;
|
|
8935
|
+
const key = String(chainId);
|
|
8936
|
+
const existing = this.chairmanMerkleNodes[key] ?? {};
|
|
8937
|
+
for (const node of nodes) {
|
|
8938
|
+
existing[node.id] = { ...node, chainId };
|
|
8939
|
+
}
|
|
8940
|
+
this.chairmanMerkleNodes[key] = existing;
|
|
8899
8941
|
await this.saveShared();
|
|
8900
8942
|
}
|
|
8901
8943
|
/**
|
|
8902
|
-
*
|
|
8944
|
+
* Get a specific chairmanMerkle tree version for a chain.
|
|
8903
8945
|
*/
|
|
8904
|
-
async
|
|
8905
|
-
|
|
8906
|
-
await this.saveShared();
|
|
8946
|
+
async getChairmanMerkleVersion(chainId, version) {
|
|
8947
|
+
return this.chairmanMerkleVersions[String(chainId)]?.[version];
|
|
8907
8948
|
}
|
|
8908
8949
|
/**
|
|
8909
|
-
* Get
|
|
8950
|
+
* Get the latest (highest version number) chairmanMerkle tree version for a chain.
|
|
8910
8951
|
*/
|
|
8911
|
-
async
|
|
8912
|
-
|
|
8952
|
+
async getLatestChairmanMerkleVersion(chainId) {
|
|
8953
|
+
const cached = this.chairmanMerkleLatestVersions[String(chainId)];
|
|
8954
|
+
if (cached) return { ...cached };
|
|
8955
|
+
const versions = this.chairmanMerkleVersions[String(chainId)];
|
|
8956
|
+
if (!versions) return void 0;
|
|
8957
|
+
let latest;
|
|
8958
|
+
for (const record of Object.values(versions)) {
|
|
8959
|
+
if (!latest || record.version > latest.version) {
|
|
8960
|
+
latest = record;
|
|
8961
|
+
}
|
|
8962
|
+
}
|
|
8963
|
+
if (latest) {
|
|
8964
|
+
this.chairmanMerkleLatestVersions[String(chainId)] = latest;
|
|
8965
|
+
}
|
|
8966
|
+
return latest ? { ...latest } : void 0;
|
|
8913
8967
|
}
|
|
8914
8968
|
/**
|
|
8915
|
-
*
|
|
8969
|
+
* Persist a chairmanMerkle tree version for a chain and track latest.
|
|
8916
8970
|
*/
|
|
8917
|
-
async
|
|
8918
|
-
if (!nodes.length) return;
|
|
8971
|
+
async putChairmanMerkleVersion(chainId, record) {
|
|
8919
8972
|
const key = String(chainId);
|
|
8920
|
-
const existing = this.
|
|
8921
|
-
|
|
8922
|
-
|
|
8973
|
+
const existing = this.chairmanMerkleVersions[key] ?? {};
|
|
8974
|
+
existing[record.version] = { ...record, chainId };
|
|
8975
|
+
this.chairmanMerkleVersions[key] = existing;
|
|
8976
|
+
const currentLatest = this.chairmanMerkleLatestVersions[key];
|
|
8977
|
+
if (!currentLatest || record.version >= currentLatest.version) {
|
|
8978
|
+
this.chairmanMerkleLatestVersions[key] = { ...record, chainId };
|
|
8923
8979
|
}
|
|
8924
|
-
this.merkleNodes[key] = existing;
|
|
8925
8980
|
await this.saveShared();
|
|
8926
8981
|
}
|
|
8927
8982
|
/**
|
|
8928
|
-
* Clear
|
|
8983
|
+
* Clear chairmanMerkle tree nodes and versions for a chain.
|
|
8929
8984
|
*/
|
|
8930
|
-
async
|
|
8931
|
-
delete this.
|
|
8985
|
+
async clearChairmanMerkleTree(chainId) {
|
|
8986
|
+
delete this.chairmanMerkleNodes[String(chainId)];
|
|
8987
|
+
delete this.chairmanMerkleVersions[String(chainId)];
|
|
8988
|
+
delete this.chairmanMerkleLatestVersions[String(chainId)];
|
|
8932
8989
|
await this.saveShared();
|
|
8933
8990
|
}
|
|
8934
8991
|
/**
|
|
@@ -9208,10 +9265,6 @@ function toChanges(result) {
|
|
|
9208
9265
|
if (typeof changes === "bigint") return Number(changes);
|
|
9209
9266
|
return 0;
|
|
9210
9267
|
}
|
|
9211
|
-
function toHexOrNull(value) {
|
|
9212
|
-
if (typeof value !== "string") return null;
|
|
9213
|
-
return value.startsWith("0x") ? value : null;
|
|
9214
|
-
}
|
|
9215
9268
|
function sortTextValues(value) {
|
|
9216
9269
|
if (value == null) return void 0;
|
|
9217
9270
|
const list = Array.isArray(value) ? value : [value];
|
|
@@ -9351,21 +9404,21 @@ var SqliteStore = class {
|
|
|
9351
9404
|
PRIMARY KEY (chain_id, cid)
|
|
9352
9405
|
);
|
|
9353
9406
|
|
|
9354
|
-
CREATE TABLE IF NOT EXISTS
|
|
9407
|
+
CREATE TABLE IF NOT EXISTS chairman_merkle_nodes (
|
|
9355
9408
|
chain_id INTEGER NOT NULL,
|
|
9356
9409
|
id TEXT NOT NULL,
|
|
9357
|
-
level INTEGER NOT NULL,
|
|
9358
|
-
position INTEGER NOT NULL,
|
|
9359
9410
|
hash TEXT NOT NULL,
|
|
9411
|
+
left_id TEXT,
|
|
9412
|
+
right_id TEXT,
|
|
9360
9413
|
PRIMARY KEY (chain_id, id)
|
|
9361
9414
|
);
|
|
9362
|
-
CREATE INDEX IF NOT EXISTS idx_merkle_nodes_chain_level_pos ON merkle_nodes(chain_id, level, position);
|
|
9363
9415
|
|
|
9364
|
-
CREATE TABLE IF NOT EXISTS
|
|
9365
|
-
chain_id INTEGER
|
|
9366
|
-
|
|
9367
|
-
|
|
9368
|
-
|
|
9416
|
+
CREATE TABLE IF NOT EXISTS chairman_merkle_versions (
|
|
9417
|
+
chain_id INTEGER NOT NULL,
|
|
9418
|
+
version INTEGER NOT NULL,
|
|
9419
|
+
root_id TEXT NOT NULL,
|
|
9420
|
+
root_hash TEXT NOT NULL,
|
|
9421
|
+
PRIMARY KEY (chain_id, version)
|
|
9369
9422
|
);
|
|
9370
9423
|
|
|
9371
9424
|
CREATE TABLE IF NOT EXISTS entry_memos (
|
|
@@ -9602,29 +9655,29 @@ var SqliteStore = class {
|
|
|
9602
9655
|
async clearMerkleLeaves(chainId) {
|
|
9603
9656
|
this.run(`DELETE FROM merkle_leaves WHERE chain_id = ?`, [chainId]);
|
|
9604
9657
|
}
|
|
9605
|
-
async
|
|
9658
|
+
async getChairmanMerkleNode(chainId, id) {
|
|
9606
9659
|
const row = this.row(
|
|
9607
|
-
`SELECT id,
|
|
9660
|
+
`SELECT id, hash, left_id, right_id FROM chairman_merkle_nodes WHERE chain_id = ? AND id = ?`,
|
|
9608
9661
|
[chainId, id]
|
|
9609
9662
|
);
|
|
9610
9663
|
if (!row) return void 0;
|
|
9611
|
-
return { chainId, id: row.id,
|
|
9664
|
+
return { chainId, id: row.id, hash: row.hash, leftId: row.left_id, rightId: row.right_id };
|
|
9612
9665
|
}
|
|
9613
|
-
async
|
|
9666
|
+
async putChairmanMerkleNodes(chainId, nodes) {
|
|
9614
9667
|
if (!nodes.length) return;
|
|
9615
9668
|
const db = this.ensureDb();
|
|
9616
9669
|
const stmt = db.prepare(
|
|
9617
|
-
`INSERT INTO
|
|
9670
|
+
`INSERT INTO chairman_merkle_nodes (chain_id, id, hash, left_id, right_id)
|
|
9618
9671
|
VALUES (?, ?, ?, ?, ?)
|
|
9619
9672
|
ON CONFLICT(chain_id, id) DO UPDATE SET
|
|
9620
|
-
|
|
9621
|
-
|
|
9622
|
-
|
|
9673
|
+
hash = excluded.hash,
|
|
9674
|
+
left_id = excluded.left_id,
|
|
9675
|
+
right_id = excluded.right_id`
|
|
9623
9676
|
);
|
|
9624
9677
|
db.exec("BEGIN IMMEDIATE");
|
|
9625
9678
|
try {
|
|
9626
9679
|
for (const node of nodes) {
|
|
9627
|
-
stmt.run(chainId, node.id, node.
|
|
9680
|
+
stmt.run(chainId, node.id, node.hash, node.leftId ?? null, node.rightId ?? null);
|
|
9628
9681
|
}
|
|
9629
9682
|
db.exec("COMMIT");
|
|
9630
9683
|
} catch (error) {
|
|
@@ -9632,40 +9685,35 @@ var SqliteStore = class {
|
|
|
9632
9685
|
throw error;
|
|
9633
9686
|
}
|
|
9634
9687
|
}
|
|
9635
|
-
async
|
|
9636
|
-
|
|
9688
|
+
async getChairmanMerkleVersion(chainId, version) {
|
|
9689
|
+
const row = this.row(
|
|
9690
|
+
`SELECT version, root_id, root_hash FROM chairman_merkle_versions WHERE chain_id = ? AND version = ?`,
|
|
9691
|
+
[chainId, version]
|
|
9692
|
+
);
|
|
9693
|
+
if (!row) return void 0;
|
|
9694
|
+
return { chainId, version: row.version, rootId: row.root_id, rootHash: row.root_hash };
|
|
9637
9695
|
}
|
|
9638
|
-
async
|
|
9696
|
+
async getLatestChairmanMerkleVersion(chainId) {
|
|
9639
9697
|
const row = this.row(
|
|
9640
|
-
`SELECT
|
|
9698
|
+
`SELECT version, root_id, root_hash FROM chairman_merkle_versions WHERE chain_id = ? ORDER BY version DESC LIMIT 1`,
|
|
9641
9699
|
[chainId]
|
|
9642
9700
|
);
|
|
9643
9701
|
if (!row) return void 0;
|
|
9644
|
-
|
|
9645
|
-
if (!root) return void 0;
|
|
9646
|
-
const totalElements = Number(row.total_elements);
|
|
9647
|
-
if (!Number.isFinite(totalElements) || totalElements < 0) return void 0;
|
|
9648
|
-
const lastUpdated = Number(row.last_updated);
|
|
9649
|
-
return {
|
|
9650
|
-
chainId,
|
|
9651
|
-
root,
|
|
9652
|
-
totalElements: Math.floor(totalElements),
|
|
9653
|
-
lastUpdated: Number.isFinite(lastUpdated) ? Math.floor(lastUpdated) : 0
|
|
9654
|
-
};
|
|
9702
|
+
return { chainId, version: row.version, rootId: row.root_id, rootHash: row.root_hash };
|
|
9655
9703
|
}
|
|
9656
|
-
async
|
|
9704
|
+
async putChairmanMerkleVersion(chainId, record) {
|
|
9657
9705
|
this.run(
|
|
9658
|
-
`INSERT INTO
|
|
9706
|
+
`INSERT INTO chairman_merkle_versions (chain_id, version, root_id, root_hash)
|
|
9659
9707
|
VALUES (?, ?, ?, ?)
|
|
9660
|
-
ON CONFLICT(chain_id) DO UPDATE SET
|
|
9661
|
-
|
|
9662
|
-
|
|
9663
|
-
|
|
9664
|
-
[chainId, tree.root, tree.totalElements, tree.lastUpdated]
|
|
9708
|
+
ON CONFLICT(chain_id, version) DO UPDATE SET
|
|
9709
|
+
root_id = excluded.root_id,
|
|
9710
|
+
root_hash = excluded.root_hash`,
|
|
9711
|
+
[chainId, record.version, record.rootId, record.rootHash]
|
|
9665
9712
|
);
|
|
9666
9713
|
}
|
|
9667
|
-
async
|
|
9668
|
-
this.run(`DELETE FROM
|
|
9714
|
+
async clearChairmanMerkleTree(chainId) {
|
|
9715
|
+
this.run(`DELETE FROM chairman_merkle_nodes WHERE chain_id = ?`, [chainId]);
|
|
9716
|
+
this.run(`DELETE FROM chairman_merkle_versions WHERE chain_id = ?`, [chainId]);
|
|
9669
9717
|
}
|
|
9670
9718
|
async upsertEntryMemos(memos) {
|
|
9671
9719
|
if (!memos.length) return;
|