@secondlayer/cli 18.0.0 → 19.0.0

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/cli.js CHANGED
@@ -202495,7 +202495,7 @@ var {
202495
202495
  // package.json
202496
202496
  var package_default = {
202497
202497
  name: "@secondlayer/cli",
202498
- version: "18.0.0",
202498
+ version: "19.0.0",
202499
202499
  description: "Run a Stacks index on your own hardware — set it up, restore verified history, and keep it current.",
202500
202500
  repository: {
202501
202501
  type: "git",
@@ -202535,7 +202535,7 @@ var package_default = {
202535
202535
  "@secondlayer/bundler": "^0.6.1",
202536
202536
  "@secondlayer/scaffold": "^1.6.1",
202537
202537
  "@secondlayer/sdk": "^13.0.0",
202538
- "@secondlayer/shared": "^11.14.0",
202538
+ "@secondlayer/shared": "^11.15.1",
202539
202539
  "@secondlayer/stacks": "^6.1.0",
202540
202540
  "@secondlayer/subgraphs": "^6.2.5",
202541
202541
  esbuild: "^0.28.2",
@@ -203516,6 +203516,193 @@ async function reverify(db, reference, ranges) {
203516
203516
  }
203517
203517
  return remaining;
203518
203518
  }
203519
+ function findChildPartition(partitions, dataset, fromBlock, toBlock) {
203520
+ return partitions.find((p) => p.dataset === dataset && p.from_block === fromBlock && p.to_block === toBlock);
203521
+ }
203522
+ function expectedZeroRows(zeroRecordRanges, dataset, fromBlock, toBlock) {
203523
+ return zeroRecordRanges.some((r) => r.dataset === dataset && r.from_block === fromBlock && r.to_block === toBlock);
203524
+ }
203525
+ async function planChildPartitionDivergence(db, partitions, zeroRecordRanges, inRange) {
203526
+ const ranges = new Map;
203527
+ for (const p of partitions) {
203528
+ if (p.dataset !== "transactions" && p.dataset !== "events")
203529
+ continue;
203530
+ if (!inRange(p.from_block, p.to_block))
203531
+ continue;
203532
+ ranges.set(`${p.from_block}-${p.to_block}`, {
203533
+ from: p.from_block,
203534
+ to: p.to_block
203535
+ });
203536
+ }
203537
+ const mismatches = [];
203538
+ for (const { from, to } of ranges.values()) {
203539
+ const txPartition = findChildPartition(partitions, "transactions", from, to);
203540
+ const evPartition = findChildPartition(partitions, "events", from, to);
203541
+ const expectedTx = txPartition?.row_count ?? (expectedZeroRows(zeroRecordRanges, "transactions", from, to) ? 0 : null);
203542
+ const expectedEv = evPartition?.row_count ?? (expectedZeroRows(zeroRecordRanges, "events", from, to) ? 0 : null);
203543
+ if (expectedTx === null && expectedEv === null)
203544
+ continue;
203545
+ const actualTx = (await computeRangeDigest2(db, "transactions", from, to)).row_count;
203546
+ const actualEv = (await computeRangeDigest2(db, "events", from, to)).row_count;
203547
+ if (expectedTx !== null && actualTx !== expectedTx || expectedEv !== null && actualEv !== expectedEv) {
203548
+ mismatches.push({
203549
+ from_block: from,
203550
+ to_block: to,
203551
+ transactions: { expected: expectedTx ?? actualTx, actual: actualTx },
203552
+ events: { expected: expectedEv ?? actualEv, actual: actualEv }
203553
+ });
203554
+ }
203555
+ }
203556
+ return mismatches;
203557
+ }
203558
+ async function applyChildRangeFix(rawClient, reference, mismatch, gate) {
203559
+ const partitions = reference.manifest.partitions ?? [];
203560
+ const txPartition = findChildPartition(partitions, "transactions", mismatch.from_block, mismatch.to_block);
203561
+ const evPartition = findChildPartition(partitions, "events", mismatch.from_block, mismatch.to_block);
203562
+ const txBytes = txPartition ? await fetchVerifiedPartition(reference, txPartition, gate) : null;
203563
+ const evBytes = evPartition ? await fetchVerifiedPartition(reference, evPartition, gate) : null;
203564
+ const written = { transactions: 0, events: 0 };
203565
+ await rawClient.begin(async (tx) => {
203566
+ await tx.unsafe("DELETE FROM events WHERE block_height BETWEEN $1 AND $2", [
203567
+ mismatch.from_block,
203568
+ mismatch.to_block
203569
+ ]);
203570
+ await tx.unsafe("DELETE FROM transactions WHERE block_height BETWEEN $1 AND $2", [mismatch.from_block, mismatch.to_block]);
203571
+ if (txBytes) {
203572
+ const writable = await tx.unsafe(copyStatement2("transactions")).writable();
203573
+ written.transactions = await writeRowsToCopyStream2({
203574
+ writable,
203575
+ dataset: "transactions",
203576
+ rows: readPartitionRows(txBytes, `repair-transactions-${mismatch.from_block}`)
203577
+ });
203578
+ }
203579
+ if (evBytes) {
203580
+ const writable = await tx.unsafe(copyStatement2("events")).writable();
203581
+ written.events = await writeRowsToCopyStream2({
203582
+ writable,
203583
+ dataset: "events",
203584
+ rows: readPartitionRows(evBytes, `repair-events-${mismatch.from_block}`)
203585
+ });
203586
+ }
203587
+ });
203588
+ return written;
203589
+ }
203590
+ async function runChildOnlyRepair(params) {
203591
+ const { reference, childMismatches, apply, json, yes } = params;
203592
+ if (childMismatches.length === 0) {
203593
+ success("Nothing to repair — local data matches the archive.");
203594
+ output({ json, data: { status: "clean", fixes: [] }, human: () => {} });
203595
+ process.exit(REPAIR_EXIT.OK);
203596
+ }
203597
+ const partitions = reference.manifest.partitions ?? [];
203598
+ const fetchPaths = childMismatches.flatMap((m) => {
203599
+ const tx = findChildPartition(partitions, "transactions", m.from_block, m.to_block);
203600
+ const ev = findChildPartition(partitions, "events", m.from_block, m.to_block);
203601
+ return [tx?.path, ev?.path].filter((p) => p !== undefined);
203602
+ });
203603
+ let gate;
203604
+ let quoteLine;
203605
+ if (apply && isOfficialArchive(reference)) {
203606
+ const result = await quoteArchiveFetch(fetchPaths, "repair");
203607
+ if (!result.ok) {
203608
+ printError(result.kind === "not_configured" ? ARCHIVE_GATE_NOT_CONFIGURED_MESSAGE : result.message);
203609
+ process.exit(REPAIR_EXIT.UNANCHORED);
203610
+ }
203611
+ if (!result.quote.sufficient) {
203612
+ printError(formatInsufficientMessage(result.quote));
203613
+ process.exit(REPAIR_EXIT.UNANCHORED);
203614
+ }
203615
+ quoteLine = formatQuoteValue(result.quote, "repair");
203616
+ note(` metered: ${quoteLine}`);
203617
+ if (shouldPromptForGatedFetch({ yes })) {
203618
+ if (json) {
203619
+ writeData(JSON.stringify(confirmationRequiredPayload(result.quote)));
203620
+ process.exit(REPAIR_EXIT.UNANCHORED);
203621
+ }
203622
+ const proceed = await confirmDestructive({
203623
+ message: `Fetch ${fetchPaths.length} partition(s) from the archive?`,
203624
+ yes
203625
+ });
203626
+ if (!proceed) {
203627
+ note("Nothing was fetched.");
203628
+ process.exit(REPAIR_EXIT.UNANCHORED);
203629
+ }
203630
+ }
203631
+ gate = createGatedFetcher(fetchPaths, "repair");
203632
+ }
203633
+ const applied = { transactions: 0, events: 0 };
203634
+ if (apply) {
203635
+ const rawClient = getRawClient2("source");
203636
+ for (const mismatch of childMismatches) {
203637
+ const result = await applyChildRangeFix(rawClient, reference, mismatch, gate);
203638
+ applied.transactions += result.transactions;
203639
+ applied.events += result.events;
203640
+ }
203641
+ }
203642
+ const db = getDb2();
203643
+ let remaining = childMismatches.length;
203644
+ if (apply) {
203645
+ remaining = 0;
203646
+ for (const mismatch of childMismatches) {
203647
+ const [actualTx, actualEv] = await Promise.all([
203648
+ computeRangeDigest2(db, "transactions", mismatch.from_block, mismatch.to_block),
203649
+ computeRangeDigest2(db, "events", mismatch.from_block, mismatch.to_block)
203650
+ ]);
203651
+ if (actualTx.row_count !== mismatch.transactions.expected || actualEv.row_count !== mismatch.events.expected) {
203652
+ remaining++;
203653
+ }
203654
+ }
203655
+ }
203656
+ const complete = apply && remaining === 0;
203657
+ const report = {
203658
+ status: apply ? complete ? "repaired" : "incomplete" : "plan",
203659
+ reference: reference.origin,
203660
+ divergent_ranges: 0,
203661
+ blocks_to_replace: 0,
203662
+ blocks_to_insert: 0,
203663
+ local_only_heights: [],
203664
+ applied: 0,
203665
+ rows_written: { blocks: 0, ...applied },
203666
+ datasets_rewritten: apply ? ["transactions", "events"] : [],
203667
+ heights_missing_child_partitions: [],
203668
+ child_partition_mismatches: childMismatches,
203669
+ fixes: [],
203670
+ metered: quoteLine ?? null
203671
+ };
203672
+ output({
203673
+ json,
203674
+ data: report,
203675
+ human: () => {
203676
+ if (apply) {
203677
+ const written = `${applied.transactions} transactions, ${applied.events} events`;
203678
+ if (complete) {
203679
+ success(`Repaired ${childMismatches.length} range(s) (rewrote ${written}); blocks were already correct.`);
203680
+ } else {
203681
+ warn(`Rewrote ${written} but ${remaining} range(s) still diverge from the archive.`);
203682
+ }
203683
+ } else {
203684
+ warn(`${childMismatches.length} range(s) have transactions/events that diverge from the archive, while their blocks match.`);
203685
+ console.error("");
203686
+ console.error(formatTable([
203687
+ "RANGE",
203688
+ "TX EXPECTED",
203689
+ "TX LOCAL",
203690
+ "EVENTS EXPECTED",
203691
+ "EVENTS LOCAL"
203692
+ ], childMismatches.slice(0, 20).map((m) => [
203693
+ `${m.from_block}-${m.to_block}`,
203694
+ String(m.transactions.expected),
203695
+ String(m.transactions.actual),
203696
+ String(m.events.expected),
203697
+ String(m.events.actual)
203698
+ ])));
203699
+ console.error("");
203700
+ console.error(dim("(dry-run — pass --apply to write the repair)"));
203701
+ }
203702
+ }
203703
+ });
203704
+ process.exit(complete ? REPAIR_EXIT.OK : REPAIR_EXIT.DIVERGENCE_REMAINS);
203705
+ }
203519
203706
  function attachRepairCommand(cmd) {
203520
203707
  return cmd.requiredOption("--against <manifest>", "archive manifest: an https URL or a local file path").option("--from-block <n>", "first height to consider").option("--to-block <n>", "last height to consider").option("--apply", "write the repair (default is a dry-run plan)").option("--public-key <pem>", "pin a signing key; default is the archive key built into this release").option("-y, --yes", "skip the confirmation prompt for a metered fetch").option("--json", "Output as JSON").addHelpText("after", `
203521
203708
  Examples:
@@ -203528,6 +203715,11 @@ archive's partitions for that height. When the reference carries no
203528
203715
  transactions or events partition for a height, the block is rewritten alone,
203529
203716
  the run names the height, and it exits 1.
203530
203717
 
203718
+ When blocks already match the archive but a transactions or events partition
203719
+ does not, that range's transactions and events are replaced outright — there
203720
+ is no block or fork boundary to preserve, only stale rows underneath one that
203721
+ was never wrong.
203722
+
203531
203723
  Exit codes:
203532
203724
  0 nothing to repair, or the repair completed and re-verified clean
203533
203725
  1 divergence remains (dry-run, or repair did not fully resolve it)
@@ -203565,13 +203757,15 @@ Exit codes:
203565
203757
  }
203566
203758
  const divergent = compareRangeDigests(local, referenceDigests).filter((c) => c.status !== "match");
203567
203759
  if (divergent.length === 0) {
203568
- success("Nothing to repair — local blocks match the archive.");
203569
- output({
203570
- json: opts.json,
203571
- data: { status: "clean", fixes: [] },
203572
- human: () => {}
203760
+ const childMismatches = await planChildPartitionDivergence(db, reference.manifest.partitions ?? [], reference.manifest.zero_record_ranges ?? [], inRange);
203761
+ await runChildOnlyRepair({
203762
+ reference,
203763
+ childMismatches,
203764
+ apply: !!opts.apply,
203765
+ json: !!opts.json,
203766
+ yes: !!opts.yes
203573
203767
  });
203574
- process.exit(REPAIR_EXIT.OK);
203768
+ return;
203575
203769
  }
203576
203770
  const partitions = (reference.manifest.partitions ?? []).filter((p) => p.dataset === "blocks" && divergent.some((d) => d.from_block === p.from_block && d.to_block === p.to_block));
203577
203771
  const childPartitions = opts.apply ? (reference.manifest.partitions ?? []).filter((p) => (p.dataset === "transactions" || p.dataset === "events") && divergent.some((d) => p.from_block <= d.to_block && p.to_block >= d.from_block)) : [];
@@ -203672,7 +203866,7 @@ Exit codes:
203672
203866
  warn(` the reference has no ${dataset} partition for ${heights.length} height(s): ${heights.slice(0, 5).join(", ")}${heights.length > 5 ? ", …" : ""}`);
203673
203867
  }
203674
203868
  for (const height of missingHeights.slice(0, 5)) {
203675
- console.error(dim(` transactions/events at ${height}: run \`secondlayer bootstrap --from-block ${height} --to-block ${height}\``));
203869
+ console.error(dim(` the archive has no transactions/events at ${height} either: it cannot be repaired in place; re-ingest ${height} from your own node, or bootstrap a fresh database from the archive`));
203676
203870
  }
203677
203871
  }
203678
203872
  output({
@@ -209059,5 +209253,5 @@ registerCreditsCommand(program);
209059
209253
  registerArchiveCommand(program);
209060
209254
  program.parse();
209061
209255
 
209062
- //# debugId=268D1F88F7CF1FDE64756E2164756E21
209256
+ //# debugId=840D684799E83C9864756E2164756E21
209063
209257
  //# sourceMappingURL=cli.js.map