@ocash/sdk 0.1.3 → 0.1.4-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/browser.cjs CHANGED
@@ -7257,15 +7257,17 @@ var MerkleEngine = class _MerkleEngine {
7257
7257
  for (let i = 0; i < sorted.length; i++) {
7258
7258
  if (sorted[i].cid !== i) throw new Error(`Non-contiguous persisted merkle leaves: expected cid=${i}, got cid=${sorted[i].cid}`);
7259
7259
  }
7260
- const totalElements = BigInt(sorted.length);
7261
- const mergedElements = _MerkleEngine.totalElementsInTree(totalElements);
7260
+ const storedTree = await this.storage?.getMerkleTree?.(chainId);
7261
+ const leafCount = sorted.length;
7262
+ const mergedFromLeaves = _MerkleEngine.totalElementsInTree(BigInt(leafCount));
7263
+ const mergedFromTree = typeof storedTree?.totalElements === "number" && storedTree.totalElements > 0 ? storedTree.totalElements : 0;
7264
+ const mergedElements = Math.max(mergedFromLeaves, mergedFromTree);
7262
7265
  const pending = this.ensurePendingLeaves(chainId);
7263
7266
  pending.length = 0;
7264
7267
  state.mergedElements = mergedElements;
7265
- if (sorted.length > mergedElements) {
7268
+ if (leafCount > mergedElements) {
7266
7269
  pending.push(...sorted.slice(mergedElements).map((l) => l.commitment));
7267
7270
  }
7268
- const storedTree = await this.storage?.getMerkleTree?.(chainId);
7269
7271
  if (storedTree?.root) {
7270
7272
  state.root = _MerkleEngine.normalizeHex32(storedTree.root, "merkleTree.root");
7271
7273
  } else {
@@ -7315,7 +7317,10 @@ var MerkleEngine = class _MerkleEngine {
7315
7317
  if (!leaves.length) return;
7316
7318
  const sorted = [...leaves].sort((a, b) => a.index - b.index);
7317
7319
  const persistLeaves = sorted.map((l) => ({ cid: l.index, commitment: l.commitment }));
7318
- void this.storage?.appendMerkleLeaves?.(chainId, persistLeaves).catch(() => void 0);
7320
+ try {
7321
+ await this.storage?.appendMerkleLeaves?.(chainId, persistLeaves);
7322
+ } catch {
7323
+ }
7319
7324
  try {
7320
7325
  let expected = state.mergedElements + pending.length;
7321
7326
  for (const leaf of sorted) {
@@ -7357,6 +7362,90 @@ var MerkleEngine = class _MerkleEngine {
7357
7362
  throw new SdkError("MERKLE", "Failed to ingest local merkle leaves", { chainId, leafCount: leaves.length }, error);
7358
7363
  }
7359
7364
  }
7365
+ /**
7366
+ * Roll back the local Merkle tree to a previous checkpoint.
7367
+ *
7368
+ * Reconstructs correct frontier state by replaying merges from stored subtree
7369
+ * roots (level-5 nodes). Cost: O(batches × depth) where batches = target / 32.
7370
+ *
7371
+ * @param targetMergedElements Must be a non-negative multiple of 32 (SUBTREE_SIZE).
7372
+ * Pass 0 to reset to the empty tree.
7373
+ * @returns true if rollback succeeded, false if required data (checkpoint or
7374
+ * subtree roots) is missing in storage.
7375
+ */
7376
+ async rollbackTree(chainId, targetMergedElements) {
7377
+ if (targetMergedElements < 0 || targetMergedElements % SUBTREE_SIZE !== 0) {
7378
+ throw new SdkError("MERKLE", "rollbackTree target must be a non-negative multiple of 32", { targetMergedElements });
7379
+ }
7380
+ const state = this.ensureChainState(chainId);
7381
+ const pending = this.ensurePendingLeaves(chainId);
7382
+ if (targetMergedElements === 0) {
7383
+ state.mergedElements = 0;
7384
+ state.root = getZeroHash(this.treeDepth);
7385
+ pending.length = 0;
7386
+ await this.storage?.setMerkleTree?.(chainId, {
7387
+ chainId,
7388
+ root: state.root,
7389
+ totalElements: 0,
7390
+ lastUpdated: Date.now()
7391
+ });
7392
+ return true;
7393
+ }
7394
+ const checkpoint = await this.storage?.getMerkleNode?.(chainId, `checkpoint-${targetMergedElements}`);
7395
+ if (!checkpoint) return false;
7396
+ const numBatches = targetMergedElements / SUBTREE_SIZE;
7397
+ const subtreeRoots = [];
7398
+ for (let batch = 0; batch < numBatches; batch++) {
7399
+ const node = await this.storage?.getMerkleNode?.(chainId, `${SUBTREE_DEPTH}-${batch}`);
7400
+ if (!node) return false;
7401
+ subtreeRoots.push(node.hash);
7402
+ }
7403
+ const resetNodes = [];
7404
+ for (let level = SUBTREE_DEPTH; level < this.treeDepth; level++) {
7405
+ resetNodes.push({
7406
+ chainId,
7407
+ id: `frontier-${level}`,
7408
+ level,
7409
+ position: 0,
7410
+ hash: getZeroHash(level)
7411
+ });
7412
+ }
7413
+ await this.storage?.upsertMerkleNodes?.(chainId, resetNodes);
7414
+ let replayRoot = getZeroHash(this.treeDepth);
7415
+ for (let batch = 0; batch < numBatches; batch++) {
7416
+ const merged = await this.mergeSubtreeToMainTree({
7417
+ chainId,
7418
+ subtreeRoot: subtreeRoots[batch],
7419
+ newTotalElements: (batch + 1) * SUBTREE_SIZE
7420
+ });
7421
+ replayRoot = merged.finalRoot;
7422
+ await this.storage?.upsertMerkleNodes?.(chainId, merged.nodesToStore.map((n) => ({ ...n, chainId })));
7423
+ }
7424
+ const replayNorm = _MerkleEngine.normalizeHex32(replayRoot, "replay.root");
7425
+ const checkpointNorm = _MerkleEngine.normalizeHex32(checkpoint.hash, "checkpoint.root");
7426
+ if (replayNorm !== checkpointNorm) {
7427
+ return false;
7428
+ }
7429
+ state.mergedElements = targetMergedElements;
7430
+ state.root = checkpointNorm;
7431
+ pending.length = 0;
7432
+ this.hydratedChains.add(chainId);
7433
+ await this.storage?.setMerkleTree?.(chainId, {
7434
+ chainId,
7435
+ root: state.root,
7436
+ totalElements: targetMergedElements,
7437
+ lastUpdated: Date.now()
7438
+ });
7439
+ const updatedCheckpoint = {
7440
+ chainId,
7441
+ id: `checkpoint-${targetMergedElements}`,
7442
+ level: -1,
7443
+ position: 0,
7444
+ hash: checkpointNorm
7445
+ };
7446
+ await this.storage?.upsertMerkleNodes?.(chainId, [updatedCheckpoint]);
7447
+ return true;
7448
+ }
7360
7449
  /**
7361
7450
  * Convenience wrapper to request a single proof.
7362
7451
  */