diff --git a/dist/codecs/blockHeader.js b/dist/codecs/blockHeader.js index 0675e59633a14c34148e90aa8c32bcfbc437330c..691ac4ede3c816bad444a6054860ed0bb3fcd37d 100644 --- a/dist/codecs/blockHeader.js +++ b/dist/codecs/blockHeader.js @@ -1,4 +1,4 @@ -import { enhanceCodec, Bytes, _void } from 'scale-ts'; +import { enhanceCodec, Bytes, _void, u32 } from 'scale-ts'; import '../utils/ss58-util.js'; import './scale/Binary.js'; import './scale/bitSequence.js'; @@ -33,7 +33,23 @@ const diggest = Variant( [0, 4, 5, 6, 8] ); const hex32 = Hex(32); -const blockHeader = Struct({ + +// Quantus patch: this console serves two header layouts. +// +// `sp_runtime::generic::Header` encodes `number` with #[codec(compact)] and has +// no field between `extrinsics_root` and `digest`. Quantus's `qp_header::Header` +// does neither: `number` is a plain u32, and a `zk_tree_root: H256` sits before +// the digest — deliberately, so the ZK root has a fixed offset in the header +// preimage that miners cannot shift by manipulating the digest. +// +// Reading a Quantus header as a Substrate one consumes 2 bytes where 4 were +// written; every field after shifts, and the digest vector eventually reads a +// byte that is not a known DigestItem index. That surfaces three layers away as +// `TypeError: innerDecoder is not a function`, papi retries the subfollow +// forever, `runtime$` never emits, and the page renders nothing at all. +// +// See quantus/papi-console#4. +const substrateHeader = Struct({ parentHash: hex32, number: compactNumber, stateRoot: hex32, @@ -41,5 +57,63 @@ const blockHeader = Struct({ digests: Vector(diggest) }); +const quantusHeader = Struct({ + parentHash: hex32, + number: u32, + stateRoot: hex32, + extrinsicRoot: hex32, + zkTreeRoot: hex32, + digests: Vector(diggest) +}); + +const asBytes = (input) => + typeof input === "string" + ? Uint8Array.from( + input + .slice(2) + .match(/../g) + ?.map((b) => parseInt(b, 16)) ?? [] + ) + : input; + +const sameBytes = (a, b) => + a.length === b.length && a.every((x, i) => x === b[i]); + +// Decide by round trip, not by guessing. A layout is right only if decoding and +// re-encoding reproduces the input byte for byte — which also catches a short +// read, trailing bytes, and a non-canonical compact. Trying the layouts in a +// fixed order and taking the first that survives that check is deterministic; +// picking one by inspecting a byte would not be. +const decodeEitherLayout = (input) => { + const bytes = asBytes(input); + let firstError; + + for (const codec of [substrateHeader, quantusHeader]) { + try { + const value = codec.dec(bytes); + + if (sameBytes(codec.enc(value), bytes)) return value; + } catch (error) { + firstError ??= error; + } + } + + throw firstError ?? new Error("block header matches no known layout"); +}; + +// Keep every property the original codec carried and override only the decoder. +// A scale-ts Struct exposes `inner` — its field codecs — and consumers reach for +// it: this console's own block.state.ts does `blockHeader.inner.digests.inner`. +// Replacing the codec with a bare [enc, dec] pair drops that, block.state.ts +// throws while its module is evaluating, `createRoot().render()` never runs, and +// the page is blank with no error on screen — the same symptom as the bug this +// patch exists to fix, from the opposite cause. +const blockHeader = Object.assign([], substrateHeader, { + 0: substrateHeader.enc, + 1: decodeEitherLayout, + enc: substrateHeader.enc, + dec: decodeEitherLayout +}); + export { blockHeader }; //# sourceMappingURL=blockHeader.js.map