@secondlayer/verify 0.1.0 → 0.3.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/index.js CHANGED
@@ -100,7 +100,34 @@ function parseNakamotoHeader(raw) {
100
100
  };
101
101
  }
102
102
  var signerSignatureHash = (h) => sha512_2562(h.signerSighashPreimage);
103
- var blockId = (h) => hashAll([signerSignatureHash(h), h.consensusHash]);
103
+ var EPOCH2_HEADER_LENGTH = 247;
104
+ function parseEpoch2Header(raw, consensusHash) {
105
+ if (consensusHash.length !== 20)
106
+ throw new Error(`consensus hash is ${consensusHash.length} bytes, not 20`);
107
+ const r = new Reader(raw);
108
+ const header = {
109
+ version: r.u8(),
110
+ burnSpent: r.u64(),
111
+ chainLength: r.u64(),
112
+ vrfProof: r.bytes(80),
113
+ parentBlockHash: r.bytes(32),
114
+ parentMicroblock: r.bytes(32),
115
+ parentMicroblockSequence: r.u16(),
116
+ txMerkleRoot: r.bytes(32),
117
+ stateIndexRoot: r.bytes(32),
118
+ microblockPubkeyHash: r.bytes(20)
119
+ };
120
+ return {
121
+ ...header,
122
+ blockHash: sha512_2562(raw.subarray(0, EPOCH2_HEADER_LENGTH)),
123
+ consensusHash
124
+ };
125
+ }
126
+ var isEpoch2Header = (h) => ("parentBlockHash" in h);
127
+ var blockId = (h) => hashAll([
128
+ isEpoch2Header(h) ? h.blockHash : signerSignatureHash(h),
129
+ h.consensusHash
130
+ ]);
104
131
  // src/marf.ts
105
132
  import { sha512_256 as sha512_2563 } from "@noble/hashes/sha2.js";
106
133
  var MARF_VALUE_SIZE = 40;
@@ -120,6 +147,7 @@ var NODE_PTR_COUNT = {
120
147
  };
121
148
  var isBackptr = (id) => (id & 128) !== 0;
122
149
  var marfPath = (key) => sha512_2563(new TextEncoder().encode(key));
150
+ var heightToHashKey = (height) => `__MARF_BLOCK_HEIGHT_TO_HASH::${height}`;
123
151
  function marfValue(value) {
124
152
  const v = new Uint8Array(MARF_VALUE_SIZE);
125
153
  v.set(sha512_2563(new TextEncoder().encode(value)));
@@ -352,6 +380,14 @@ function marfProofAncestors(proof) {
352
380
  }
353
381
  return [...ids];
354
382
  }
383
+ function marfProofValue(proof) {
384
+ try {
385
+ const leaf = decodeProof(proof)[0];
386
+ return leaf?.kind === "Leaf" ? leaf.value : null;
387
+ } catch {
388
+ return null;
389
+ }
390
+ }
355
391
  function verifyMarfProof(input) {
356
392
  const rootToBlock = new Map;
357
393
  for (const h of input.headers)
@@ -982,6 +1018,15 @@ var required = (url, v) => {
982
1018
  return v;
983
1019
  };
984
1020
  var trimSlash = (s) => s.replace(/\/+$/, "");
1021
+ var toEpoch2 = (url, j) => {
1022
+ const tip = required(url, j)[0];
1023
+ if (typeof tip?.header !== "string" || typeof tip.consensus_hash !== "string")
1024
+ throw new SourceError(url, 200, "answer needs header and consensus_hash");
1025
+ return {
1026
+ header: unhex(tip.header),
1027
+ consensusHash: unhex(tip.consensus_hash)
1028
+ };
1029
+ };
985
1030
  var toMarf = (url, j) => {
986
1031
  if (j === null)
987
1032
  return null;
@@ -1018,6 +1063,7 @@ async function pages(field, url, http) {
1018
1063
 
1019
1064
  class SecondlayerProofSource {
1020
1065
  getBlock;
1066
+ getEpoch2Header;
1021
1067
  getMarfProof;
1022
1068
  getWitness;
1023
1069
  getBurnPreimage;
@@ -1034,6 +1080,10 @@ class SecondlayerProofSource {
1034
1080
  const url = typeof ref === "number" ? `${base}/v1/proofs/block/height/${ref}` : `${base}/v1/proofs/block/${ref}`;
1035
1081
  return required(url, await getBytes(url, http));
1036
1082
  };
1083
+ this.getEpoch2Header = async (blockId) => {
1084
+ const url = `${base}/v1/proofs/epoch2-header/${blockId}`;
1085
+ return toEpoch2(url, await getJson(url, http));
1086
+ };
1037
1087
  this.getMarfProof = async (pathHex, tipId) => {
1038
1088
  const url = `${base}/v1/proofs/marf/${pathHex}?tip=${tipId}`;
1039
1089
  return toMarf(url, await getJson(url, http));
@@ -1054,7 +1104,15 @@ class SecondlayerProofSource {
1054
1104
  throw new SourceError(url, 200, `headers start at ${j.from}`);
1055
1105
  return j.headers;
1056
1106
  };
1057
- this.getStateWrites = (height) => pages("state_writes", `${base}/v1/index/state-writes?block_height=${height}&limit=${PAGE}`, http);
1107
+ this.getStateWrites = async (height) => {
1108
+ const url = `${base}/v1/proofs/writes/${height}`;
1109
+ const j = await getJson(url, http);
1110
+ if (j === null)
1111
+ return null;
1112
+ if (!Array.isArray(j.state_writes))
1113
+ throw new SourceError(url, 200, "answer has no state_writes array");
1114
+ return j.state_writes;
1115
+ };
1058
1116
  this.getVmEvents = async (height) => {
1059
1117
  const out = [];
1060
1118
  for (const type of VM_WRITE_TYPES) {
@@ -1068,6 +1126,7 @@ class SecondlayerProofSource {
1068
1126
 
1069
1127
  class NodeRpcProofSource {
1070
1128
  getBlock;
1129
+ getEpoch2Header;
1071
1130
  getMarfProof;
1072
1131
  constructor(opts) {
1073
1132
  const base = trimSlash(opts.nodeUrl);
@@ -1076,6 +1135,10 @@ class NodeRpcProofSource {
1076
1135
  const url = typeof ref === "number" ? `${base}/v3/blocks/height/${ref}` : `${base}/v3/blocks/${ref}`;
1077
1136
  return required(url, await getBytes(url, http));
1078
1137
  };
1138
+ this.getEpoch2Header = async (blockId) => {
1139
+ const url = `${base}/v2/headers/1?tip=${blockId}`;
1140
+ return toEpoch2(url, await getJson(url, http));
1141
+ };
1079
1142
  this.getMarfProof = async (pathHex, tipId) => {
1080
1143
  const url = `${base}/v2/clarity/marf/${pathHex}?tip=${tipId}&proof=1`;
1081
1144
  return toMarf(url, await getJson(url, http));
@@ -1085,7 +1148,6 @@ class NodeRpcProofSource {
1085
1148
  // src/state.ts
1086
1149
  var INTERNAL = {
1087
1150
  self: "__MARF_BLOCK_HEIGHT_SELF",
1088
- heightToHash: "__MARF_BLOCK_HEIGHT_TO_HASH",
1089
1151
  hashToHeight: "__MARF_BLOCK_HASH_TO_HEIGHT"
1090
1152
  };
1091
1153
  var heightValue = (h) => {
@@ -1096,7 +1158,7 @@ var heightValue = (h) => {
1096
1158
  var padded = (b32) => `${hex(b32)}${"00".repeat(8)}`;
1097
1159
  function internalEntries(height, parentBlockId, leaves) {
1098
1160
  const out = [[INTERNAL.self, heightValue(height)]];
1099
- const ownKey = `${INTERNAL.heightToHash}::${height}`;
1161
+ const ownKey = heightToHashKey(height);
1100
1162
  const ownPath = marfPath(ownKey);
1101
1163
  const own = leaves.find((l) => bytesEqual(l.path, ownPath));
1102
1164
  if (own) {
@@ -1107,10 +1169,7 @@ function internalEntries(height, parentBlockId, leaves) {
1107
1169
  ]);
1108
1170
  }
1109
1171
  if (height > 0) {
1110
- out.push([
1111
- `${INTERNAL.heightToHash}::${height - 1}`,
1112
- padded(parentBlockId)
1113
- ]);
1172
+ out.push([heightToHashKey(height - 1), padded(parentBlockId)]);
1114
1173
  out.push([
1115
1174
  `${INTERNAL.hashToHeight}::${hex(parentBlockId)}`,
1116
1175
  heightValue(height - 1)
@@ -1250,6 +1309,7 @@ async function verifyBlockState(input) {
1250
1309
  ...leafHex(l),
1251
1310
  key: l.key,
1252
1311
  txIndex: last.get(l.key)?.txIndex ?? null,
1312
+ value: last.get(l.key)?.value,
1253
1313
  named: true
1254
1314
  })) : d.hidden.map((l) => ({ ...leafHex(l), named: false })),
1255
1315
  carried: d.carried.map(leafHex),
@@ -1278,6 +1338,58 @@ async function verifyBlockState(input) {
1278
1338
  out.rowsChecked = checkRows(input.rows, leaves, out.failures);
1279
1339
  return out;
1280
1340
  }
1341
+ // src/txs.ts
1342
+ import { sha512_256 as sha512_2564 } from "@noble/hashes/sha2.js";
1343
+ import { splitTransactions } from "@secondlayer/stacks/transactions";
1344
+ var LEAF_TAG = new Uint8Array([0]);
1345
+ var NODE_TAG = new Uint8Array([1]);
1346
+ function txMerkleRoot(txids) {
1347
+ if (txids.length === 0)
1348
+ throw new Error("no transactions");
1349
+ let level = txids.map((t) => sha512_2564(concat([LEAF_TAG, t])));
1350
+ do {
1351
+ if (level.length % 2 === 1)
1352
+ level.push(level[level.length - 1]);
1353
+ const next = [];
1354
+ for (let i = 0;i < level.length; i += 2) {
1355
+ next.push(sha512_2564(concat([NODE_TAG, level[i], level[i + 1]])));
1356
+ }
1357
+ level = next;
1358
+ } while (level.length > 1);
1359
+ return level[0];
1360
+ }
1361
+
1362
+ class TransactionsError extends Error {
1363
+ code;
1364
+ constructor(code, message) {
1365
+ super(message);
1366
+ this.code = code;
1367
+ }
1368
+ }
1369
+ function blockTransactions(raw, header) {
1370
+ const body = raw.subarray(header.byteLength);
1371
+ if (body.length === 0)
1372
+ return null;
1373
+ if (body.length < 4)
1374
+ throw new TransactionsError("malformed", "block body has no tx count");
1375
+ const count = new DataView(body.buffer, body.byteOffset, body.byteLength).getUint32(0);
1376
+ if (count === 0)
1377
+ throw new TransactionsError("malformed", "block has no transactions");
1378
+ let txs;
1379
+ try {
1380
+ txs = splitTransactions(body.subarray(4), count);
1381
+ } catch (err) {
1382
+ throw new TransactionsError("malformed", `transactions do not parse: ${err.message}`);
1383
+ }
1384
+ const used = txs.reduce((n, t) => n + t.length, 4);
1385
+ if (used !== body.length)
1386
+ throw new TransactionsError("malformed", `${body.length - used} bytes after the block's ${count} transactions`);
1387
+ const ids = txs.map((t) => sha512_2564(t));
1388
+ const root = txMerkleRoot(ids);
1389
+ if (!bytesEqual(root, header.txMerkleRoot))
1390
+ throw new TransactionsError("tx-root-mismatch", `transactions hash to ${hex(root)}, header tx_merkle_root is ${hex(header.txMerkleRoot)}`);
1391
+ return txs.map((t, i) => ({ txid: hex(ids[i]), raw: t }));
1392
+ }
1281
1393
  // src/verify-block.ts
1282
1394
  import { uintCV } from "@secondlayer/stacks/clarity";
1283
1395
  class Broken extends Error {
@@ -1305,6 +1417,7 @@ async function fetchFor(step, what, call, extra = {}) {
1305
1417
  }
1306
1418
  }
1307
1419
  var BITCOIN_BATCH = 2016;
1420
+ var MAX_WALK = 16;
1308
1421
  async function findAnchor(low, high, window, probe, maxProbes = 32) {
1309
1422
  let [lo, hi] = [low, high];
1310
1423
  const target = (window[0] + window[1]) / 2;
@@ -1339,8 +1452,10 @@ class BlockVerifier {
1339
1452
  sets = new Map;
1340
1453
  anchors = new Map;
1341
1454
  headers = new Map;
1455
+ below = new Map;
1342
1456
  burns = new Map;
1343
1457
  preimages = new Map;
1458
+ raws = new Map;
1344
1459
  start;
1345
1460
  constructor(opts) {
1346
1461
  this.source = opts.source;
@@ -1366,26 +1481,42 @@ class BlockVerifier {
1366
1481
  return out;
1367
1482
  }
1368
1483
  async run(ref, out) {
1369
- const src = this.source;
1370
- const raw = await fetchFor("block", `block ${ref}`, () => src.getBlock(ref));
1371
- const header = parseHeader2(raw, "block");
1484
+ const start = Number(this.start.header.chainLength);
1485
+ if (typeof ref === "number" && ref < start)
1486
+ return this.runBelow(ref, undefined, out);
1487
+ let header;
1488
+ if (typeof ref === "string") {
1489
+ const id = ref.replace(/^0x/, "").toLowerCase();
1490
+ header = await this.header(id, "block");
1491
+ const height = Number(header.chainLength);
1492
+ if (height < start)
1493
+ return this.runBelow(height, id, out);
1494
+ if (isEpoch2Header(header))
1495
+ fail({
1496
+ step: "block",
1497
+ code: "invalid-header",
1498
+ message: `epoch 2.x block ${id} claims height ${height}, at or above the checkpoint`
1499
+ });
1500
+ } else {
1501
+ const raw = await fetchFor("block", `block ${ref}`, () => this.source.getBlock(ref));
1502
+ header = parseHeader2(raw, "block");
1503
+ this.rememberRaw(hex(blockId(header)), raw);
1504
+ }
1372
1505
  const id = hex(blockId(header));
1373
1506
  const height = Number(header.chainLength);
1374
- if (typeof ref === "string" && id !== ref.replace(/^0x/, "").toLowerCase())
1375
- fail({
1376
- step: "block",
1377
- code: "id-mismatch",
1378
- message: `source returned block ${id} for ${ref}`
1379
- });
1380
1507
  if (typeof ref === "number" && height !== ref)
1381
1508
  fail({
1382
1509
  step: "block",
1383
1510
  code: "height-mismatch",
1384
1511
  message: `source returned height ${height} for ${ref}`
1385
1512
  });
1513
+ await this.runForward(header, id, out);
1514
+ }
1515
+ async runForward(header, id, out) {
1516
+ const height = Number(header.chainLength);
1386
1517
  out.height = height;
1387
1518
  out.blockId = id;
1388
- out.stateRoot = hex(header.stateIndexRoot);
1519
+ describeHeader(header, out);
1389
1520
  const burn = await this.bindBurn(header, "burn");
1390
1521
  const cycle = rewardCycle(burn.burnHeight);
1391
1522
  out.burnHeight = burn.burnHeight;
@@ -1401,20 +1532,174 @@ class BlockVerifier {
1401
1532
  out.threshold = sig.threshold;
1402
1533
  if (!sig.valid)
1403
1534
  fail(signatureFailure(sig, "signatures", cycle));
1535
+ await this.checkTransactions(header, id, out);
1536
+ await this.verifyState(header, id, out);
1537
+ }
1538
+ async runBelow(height, claimed, out) {
1539
+ out.height = height;
1540
+ const { id, ancestry } = await this.ancestor(height);
1541
+ out.ancestry = ancestry;
1542
+ if (claimed !== undefined && claimed !== id)
1543
+ fail({
1544
+ step: "ancestry",
1545
+ code: "not-ancestor",
1546
+ message: `block ${claimed} is not on the checkpoint's chain: its block at height ${height} is ${id}`,
1547
+ blockId: claimed
1548
+ });
1549
+ const header = await this.header(id, "block");
1550
+ if (Number(header.chainLength) !== height)
1551
+ fail({
1552
+ step: "block",
1553
+ code: "height-mismatch",
1554
+ message: `block ${id} claims height ${header.chainLength}, proven at ${height}`
1555
+ });
1556
+ out.blockId = id;
1557
+ describeHeader(header, out);
1558
+ await this.bindBurnBelow(header, out);
1559
+ await this.checkTransactions(header, id, out);
1560
+ await this.verifyState(header, id, out);
1561
+ }
1562
+ rememberRaw(id, raw) {
1563
+ this.raws.delete(id);
1564
+ this.raws.set(id, raw);
1565
+ while (this.raws.size > 4) {
1566
+ const oldest = this.raws.keys().next().value;
1567
+ this.raws.delete(oldest);
1568
+ }
1569
+ }
1570
+ async checkTransactions(header, id, out) {
1571
+ if (isEpoch2Header(header)) {
1572
+ out.notes.push("epoch 2.x: transactions not checked");
1573
+ return;
1574
+ }
1575
+ const raw = this.raws.get(id) ?? await fetchFor("txs", `block ${id}`, () => this.source.getBlock(id));
1576
+ let txs;
1577
+ try {
1578
+ txs = blockTransactions(raw, header);
1579
+ } catch (err) {
1580
+ if (!(err instanceof TransactionsError))
1581
+ throw err;
1582
+ return fail({ step: "txs", code: err.code, message: err.message });
1583
+ }
1584
+ if (txs === null) {
1585
+ out.notes.push(`transactions not checked: the source served block ${id}'s header only`);
1586
+ return;
1587
+ }
1588
+ out.transactions = txs;
1589
+ }
1590
+ async ancestor(height) {
1591
+ const known = this.below.get(height);
1592
+ if (known)
1593
+ return known;
1594
+ const from = this.nearestAbove(height);
1595
+ if (from.height - height <= MAX_WALK) {
1596
+ const walked = await this.walk(from, height);
1597
+ if (walked)
1598
+ return walked;
1599
+ }
1600
+ return this.jump(this.nearestAbove(height), height);
1601
+ }
1602
+ nearestAbove(height) {
1603
+ let best = {
1604
+ height: Number(this.start.header.chainLength),
1605
+ id: this.start.id
1606
+ };
1607
+ for (const [h, { id }] of this.below)
1608
+ if (h > height && h < best.height)
1609
+ best = { height: h, id };
1610
+ return best;
1611
+ }
1612
+ async walk(from, height) {
1613
+ const ancestry = {
1614
+ fromHeight: from.height,
1615
+ fromBlockId: from.id,
1616
+ via: "parents"
1617
+ };
1618
+ let child = await this.header(from.id, "ancestry");
1619
+ for (let h = from.height - 1;h >= height; h--) {
1620
+ if (isEpoch2Header(child))
1621
+ return null;
1622
+ const id = hex(child.parentBlockId);
1623
+ const parent = await this.header(id, "ancestry", { blockId: id });
1624
+ if (Number(parent.chainLength) !== h)
1625
+ fail({
1626
+ step: "ancestry",
1627
+ code: "height-mismatch",
1628
+ message: `parent ${id} claims height ${parent.chainLength}, expected ${h}`,
1629
+ blockId: id
1630
+ });
1631
+ this.below.set(h, { id, ancestry });
1632
+ child = parent;
1633
+ }
1634
+ return this.below.get(height) ?? null;
1635
+ }
1636
+ async jump(from, height) {
1637
+ const at = { blockId: from.id };
1638
+ const tip = await this.header(from.id, "ancestry", at);
1639
+ const key = heightToHashKey(height);
1640
+ const path = marfPath(key);
1641
+ const answer = await fetchFor("ancestry", `${key} at ${from.id}`, () => this.source.getMarfProof(hex(path), from.id), at);
1642
+ if (!answer)
1643
+ return fail({
1644
+ step: "ancestry",
1645
+ code: "unavailable",
1646
+ message: `source has no MARF proof of ${key} at block ${from.height}, ${from.height - height} blocks up; parent links reach ${MAX_WALK}`,
1647
+ ...at
1648
+ });
1649
+ const value = marfProofValue(answer.proof) ?? new Uint8Array;
1650
+ const proven = value.length === MARF_VALUE_SIZE && value.subarray(32).every((b) => b === 0) && await this.proveAt(tip, path, value, answer.proof, "ancestry", at);
1651
+ if (!proven)
1652
+ return fail({
1653
+ step: "ancestry",
1654
+ code: "ancestry-proof-invalid",
1655
+ message: `${key} is not proven in block ${from.height}'s state`,
1656
+ ...at
1657
+ });
1658
+ const ancestry = {
1659
+ fromHeight: from.height,
1660
+ fromBlockId: from.id,
1661
+ via: "marf"
1662
+ };
1663
+ const found = { id: hex(value.subarray(0, 32)), ancestry };
1664
+ this.below.set(height, found);
1665
+ return found;
1666
+ }
1667
+ async bindBurnBelow(header, out) {
1668
+ let hint;
1669
+ try {
1670
+ hint = await this.burnHint(header);
1671
+ } catch (err) {
1672
+ out.notes.push(`burn block not checked: ${err.message}`);
1673
+ return;
1674
+ }
1675
+ if (hint < this.chain.checkpointHeight) {
1676
+ out.notes.push(`burn block not checked: its height ${hint} is below the Bitcoin checkpoint ${this.chain.checkpointHeight}`);
1677
+ return;
1678
+ }
1679
+ const burn = await this.bindBurn(header, "burn");
1680
+ out.burnHeight = burn.burnHeight;
1681
+ out.bitcoinBlockHash = burn.hash;
1682
+ }
1683
+ async verifyState(header, id, out) {
1684
+ const src = this.source;
1685
+ const height = Number(header.chainLength);
1404
1686
  const witness = await fetchFor("witness", `witness ${id}`, () => src.getWitness(id));
1405
- const { getStateWrites, getVmEvents } = this.rows ? src : {};
1406
- const writes = getStateWrites ? await fetchFor("names", `state_writes ${height}`, () => getStateWrites.call(src, height)) : null;
1687
+ const writes = await this.stateWrites(height, out.notes);
1688
+ if (writes)
1689
+ out.writes = writes;
1690
+ const { getVmEvents } = this.rows ? src : {};
1407
1691
  const rows = getVmEvents ? await fetchFor("rows", `vm_events ${height}`, () => getVmEvents.call(src, height)) : undefined;
1408
- const parentId = hex(header.parentBlockId);
1692
+ const parent = isEpoch2Header(header) ? epoch2ParentId(witness, header) : header.parentBlockId;
1693
+ const parentId = parent && hex(parent);
1409
1694
  const state = await verifyBlockState({
1410
1695
  height,
1411
- parentBlockId: header.parentBlockId,
1696
+ parentBlockId: parent ?? new Uint8Array(32),
1412
1697
  stateRoot: header.stateIndexRoot,
1413
1698
  witness,
1414
1699
  writes,
1415
1700
  rows,
1416
- parentLeaves: await this.parentLeaves(parentId, out.notes),
1417
- parentHolds: (leaf) => this.parentHolds(parentId, leaf)
1701
+ parentLeaves: parentId ? await this.parentLeaves(parentId, out.notes) : undefined,
1702
+ parentHolds: async (leaf) => parentId ? this.parentHolds(parentId, leaf) : false
1418
1703
  });
1419
1704
  out.diff = state.diff;
1420
1705
  if (rows)
@@ -1422,12 +1707,23 @@ class BlockVerifier {
1422
1707
  out.notes.push(...state.notes);
1423
1708
  out.failures.push(...state.failures);
1424
1709
  }
1710
+ async stateWrites(height, notes) {
1711
+ const { getStateWrites } = this.source;
1712
+ if (!getStateWrites)
1713
+ return null;
1714
+ try {
1715
+ const writes = await getStateWrites.call(this.source, height);
1716
+ return writes?.length ? writes : null;
1717
+ } catch (err) {
1718
+ notes.push(`state writes for block ${height} unavailable: ${err.message}`);
1719
+ return null;
1720
+ }
1721
+ }
1425
1722
  async header(id, step, extra = {}) {
1426
1723
  const known = this.headers.get(id);
1427
1724
  if (known)
1428
1725
  return known;
1429
- const raw = await fetchFor(step, `block ${id}`, () => this.source.getBlock(id), extra);
1430
- const header = parseHeader2(raw, step);
1726
+ const header = await this.fetchHeader(id, step, extra);
1431
1727
  if (hex(blockId(header)) !== id)
1432
1728
  fail({
1433
1729
  step,
@@ -1439,6 +1735,31 @@ class BlockVerifier {
1439
1735
  this.headers.set(id, header);
1440
1736
  return header;
1441
1737
  }
1738
+ async fetchHeader(id, step, extra) {
1739
+ const src = this.source;
1740
+ let raw;
1741
+ try {
1742
+ raw = await src.getBlock(id);
1743
+ this.rememberRaw(id, raw);
1744
+ } catch (err) {
1745
+ const reason = `block ${id}: ${err.message}`;
1746
+ const { getEpoch2Header } = src;
1747
+ if (!getEpoch2Header)
1748
+ return fail({ step, code: "unavailable", message: reason, ...extra });
1749
+ const e2 = await fetchFor(step, `${reason}; epoch 2.x header`, () => getEpoch2Header.call(src, id), extra);
1750
+ try {
1751
+ return parseEpoch2Header(e2.header, e2.consensusHash);
1752
+ } catch (parseErr) {
1753
+ return fail({
1754
+ step,
1755
+ code: "invalid-header",
1756
+ message: `epoch 2.x header ${id} does not parse: ${parseErr.message}`,
1757
+ ...extra
1758
+ });
1759
+ }
1760
+ }
1761
+ return parseHeader2(raw, step);
1762
+ }
1442
1763
  async proveAt(tip, path, value, proof, step, extra = {}) {
1443
1764
  const headers = [tip];
1444
1765
  for (const id of marfProofAncestors(proof))
@@ -1639,6 +1960,14 @@ class BlockVerifier {
1639
1960
  return set;
1640
1961
  }
1641
1962
  }
1963
+ function describeHeader(header, out) {
1964
+ out.stateRoot = hex(header.stateIndexRoot);
1965
+ out.consensusHash = hex(header.consensusHash);
1966
+ if (!isEpoch2Header(header)) {
1967
+ out.blockHash = hex(signerSignatureHash(header));
1968
+ out.timestamp = Number(header.timestamp);
1969
+ }
1970
+ }
1642
1971
  function parseHeader2(raw, step) {
1643
1972
  try {
1644
1973
  return parseNakamotoHeader(raw);
@@ -1650,6 +1979,20 @@ function parseHeader2(raw, step) {
1650
1979
  });
1651
1980
  }
1652
1981
  }
1982
+ function epoch2ParentId(witness, header) {
1983
+ const height = Number(header.chainLength);
1984
+ if (height === 0)
1985
+ return null;
1986
+ try {
1987
+ const w = parseWitness(witness);
1988
+ if (!bytesEqual(w.root, header.stateIndexRoot))
1989
+ return null;
1990
+ const path = marfPath(heightToHashKey(height - 1));
1991
+ return w.leaves.find((l) => bytesEqual(l.path, path))?.valueHash ?? null;
1992
+ } catch {
1993
+ return null;
1994
+ }
1995
+ }
1653
1996
  function signatureFailure(sig, step, cycle) {
1654
1997
  const weight = `${sig.signedWeight}/${sig.totalWeight} signed, threshold ${sig.threshold}`;
1655
1998
  if (sig.unknown.length)
@@ -1693,21 +2036,26 @@ export {
1693
2036
  SIGNERS_CONTRACT,
1694
2037
  SecondlayerProofSource,
1695
2038
  SourceError,
2039
+ TransactionsError,
1696
2040
  blockId,
2041
+ blockTransactions,
1697
2042
  classifyBlockDiff,
1698
2043
  cycleStart,
1699
2044
  dataVarKey,
1700
2045
  decodeSignerSet,
1701
2046
  ftBalanceKey,
1702
2047
  hex,
2048
+ isEpoch2Header,
1703
2049
  mapEntryKey,
1704
2050
  marfPath,
1705
2051
  marfProofAncestors,
1706
2052
  marfValue,
2053
+ parseEpoch2Header,
1707
2054
  parseNakamotoHeader,
1708
2055
  parseWitness,
1709
2056
  rewardCycle,
1710
2057
  signerSignatureHash,
2058
+ txMerkleRoot,
1711
2059
  unhex,
1712
2060
  verifyBlock,
1713
2061
  verifyBlockState,
@@ -1716,5 +2064,5 @@ export {
1716
2064
  verifySignerSignatures
1717
2065
  };
1718
2066
 
1719
- //# debugId=67FB81E2AAB4645564756E2164756E21
2067
+ //# debugId=AFAA56E81913DBC664756E2164756E21
1720
2068
  //# sourceMappingURL=index.js.map