Repository navigation
feat(ismp): rebuild request commitments off-chain and bring event types to spec 13 - #3
Merged
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Rebuild request commitments off-chain, and bring event types to spec 13
0.3.0 modelled the ISMP events before runtime spec 13. The chain now emits twenty fields the SDK did not know about, so a consumer compiles today and reads
undefinedfrom data that is in fact arriving.Added
requestCommitment/encodePostRequest/encodeGetRequest— reproduce the commitment of a request from whatismp_queryRequestsreturns. This is what joins an event to the request it describes, and what a relayer must do to prove one.A commitment is
keccak256(abi.encode(request))— Solidity ABI, not SCALE.Request::encode()is an inherent method that shadows the SCALEEncodetrait (ismp-2606.1.0/src/router.rs:263), so Rust reads as if it were SCALE while emitting 32-byte-word ABI output. Off-chain the difference is silent: a SCALE encoding hashes fine and yields a value the chain has never seen, surfacing much later asUnknownRequest.Two details that are easy to get wrong, both taken from the pallet's own output rather than inferred:
sourceanddesttravel as their display strings ("SUBSTRATE-orbi","KUSAMA-4009"). The RPC returns exactly these, so its output is hashable as-is — converting to{ type: 'Kusama', value: 4009 }first breaks the hash.source, dest, nonce, from, timeoutTimestamp, keys, height, context.No new dependency:
keccak_256from@noble/hashes, ABI hand-rolled in ~40 lines.childTrieProof/ISMP_CHILD_TRIE/commitmentKey/receiptKey— what of Hyperbridge is actually readable is its ISMP child trie, not its global state: for the coprocessorismp-grandparecordsstate_root = child_trie_root(consensus.rs:142-150). A GET for a global key such asIsmp::Nonceis unverifiable by construction. Usesismp_queryChildTrieProof, the RPC Tesseract itself uses, with two encoding traps handled and documented.Changed
Event types match spec 13. New
RequestKind.timeoutTimestamphas three states and conflating any two is a bug:0nis never-expires (upstream's explicit branch, not 1970).GetResponseReceived.heightis the only genuine remote block number this pallet emits. On a GET,RequestDispatched.tois our own module — where the answer routes back to, not a recipient.relayeron the*Handledevents is not necessarily an account: the host takes whatever the submitter signed with, verbatim up to 32 bytes.And the operational fact behind all of this: the public relayer does not deliver GET responses to Substrate chains (
tesseract/messaging/messaging/src/events.rs:314-336), so such a GET goes unanswered unless something of yours carries the proof back. The chain verifies it either way.Breaking
PostRequest.nonce,PostRequest.timeoutTimestamp,GetRequest.nonce,GetRequest.heightandGetRequest.timeoutTimestampare nowbigint— the chain declares them u64 andnumberloses precision past 2^53. The RPC hands them over as JSON numbers, so widen them (BigInt(raw.nonce)) before hashing.Verification
pnpm check— 865 tests, typecheck ×2, lint, format, build.The encoder is pinned against two vectors the runtime itself printed (
0xe51e53…POST,0xc7f995…GET), not a reimplementation: a SCALE encoding of the same request also produces a hash, so "it returns a hash" proves nothing. A third test hashes the first real cross-chain read this chain performed (0x532f2d62…, dispatched at #786579, answered at #786580) back to its on-chain commitment, and a fourth confirms that changing any single field changes the result.childTrieProofwas run against live Gargantua: 1462 raw bytes decode to 7 SCALE nodes, one of which keccak-hashes to that height'sismp.childTrieRoot.Finally, the compiled bundle was checked end to end against testnet: read the request over RPC, widen the u64s, rebuild — matches.