Context & versions
- Pre-fork:
node-1.0.0 — mainnet parity (specVersion 1000000, midnight_ledgerVersion "=8.1.0").
- Post-fork:
main @ dd9fba007 — node 2.1.0-dd9fba00, specVersion 2001000. Also seen on 74a91156b.
- Network:
local-environment local-env, 5 validators, federated governance runtime upgrade (npm run governance-runtime-upgrade:local-env).
- Clients involved:
subxt 0.50 (as used by chain-indexer via indexer-common), and @polkadot/api as used by local-environment/src/commands/federatedRuntimeUpgrade.ts.
- Frequency: intermittent — observed in 4 of 6 upgrade runs. Two runs on
dd9fba007 printed Runtime upgrade completed successfully instead, which is consistent with a race on when the client refreshes metadata.
Steps to reproduce
- Run a ledger-8 local-env chain (5 validators).
- Apply the governance runtime upgrade to spec 2001000:
npm run governance-runtime-upgrade:local-env -- \
--wasm test/midnight_node_runtime.compact.compressed.wasm \
--rpc-url ws://localhost:9933 \
--council-uris "//Four" "//Five" --technical-uris "//One" "//Two" \
--executor-uri "//One" --skip-run
- Observe the tool's final step (it looks for
System.CodeUpdated in the block that applied the upgrade). Repeat a few times — it does not fail every run.
- Independently, decode the events of the upgrade block with a subxt 0.50 client, and with the same client forced to use the pre-upgrade metadata version.
Expected behavior
The events of the block that applied the runtime upgrade should be decodable, and a successful runtime upgrade should not be reported as a failure.
Actual behavior
A block's events are encoded by the runtime of its parent state, but both subxt 0.50 and polkadot-js resolve metadata at the block's own hash. At the upgrade block those disagree, so event decoding fails there — and only there.
Evidence from a standalone subxt 0.50 probe against the live chain (querying one block at a time):
block N-1 : OK (events decoded)
block N : FAIL - Can't decode event topics: Not enough data to fill buffer
block N+1 : OK (events decoded)
same client, block N, forced to pre-upgrade metadata (v15 and v14): OK (events decoded)
In subxt 0.50.1 the spec version comes from Core_version at the block's own hash and the metadata from Metadata_metadata_at_version at that same hash (src/client/online_client.rs, at_block_hash_and_number), which is what produces the mismatch.
Consequence 1 — governance-runtime-upgrade reports a false failure. Its last step asserts System.CodeUpdated is present in the applying block (local-environment/src/commands/federatedRuntimeUpgrade.ts:63-67). polkadot-js fails to decode that block's events, so the assertion throws even though the upgrade executed:
RPC-CORE: getStorage ... Unable to decode storage system.events ::
createType(Vec<FrameSystemEventRecord>) ... H256:: Expected input with 32 bytes (256 bits), found 21 bytes
system.applyAuthorizedUpgrade included in block 0x891b...
Error: Runtime upgrade executed but System.CodeUpdated event not found.
The 21-vs-32 byte mismatch is the shape change itself: the ledger-8 runtime's CodeUpdated has no fields, the ledger-9 one carries a hash. state_getRuntimeVersion confirms the upgrade did take effect (1000000 → 2001000) in every run, including the ones where the tool threw.
This is operationally risky: an operator sees a hard failure on a successful upgrade and may retry it. Suggested fix for the tool: confirm via the state_getRuntimeVersion spec-version change rather than by inspecting the applying block's events.
Consequence 2 — chain-indexer exits on that block. With a ledger-9-capable indexer build, the chain-indexer dies at the same height:
index_blocks_task failed: get next block from node: cannot get next event:
Can't decode event topics: Not enough data to fill buffer
and, in another run,
Can't decode field "hash" in event System.CodeUpdated: Decode error: Not enough data to fill buffer
(The differing message between runs is the same root cause; which field trips first depends on the events present.)
Scope note: the underlying metadata-resolution behaviour sits in the client libraries (subxt, polkadot-js) rather than in this repo, so the actionable part here is (a) the governance-runtime-upgrade success check, and (b) deciding whether the node should expose something that lets clients resolve the correct metadata for an upgrade block. Worth raising the general case upstream separately.
Context & versions
node-1.0.0— mainnet parity (specVersion 1000000,midnight_ledgerVersion "=8.1.0").main@dd9fba007— node2.1.0-dd9fba00,specVersion 2001000. Also seen on74a91156b.local-environmentlocal-env, 5 validators, federated governance runtime upgrade (npm run governance-runtime-upgrade:local-env).subxt0.50 (as used by chain-indexer viaindexer-common), and@polkadot/apias used bylocal-environment/src/commands/federatedRuntimeUpgrade.ts.dd9fba007printedRuntime upgrade completed successfullyinstead, which is consistent with a race on when the client refreshes metadata.Steps to reproduce
System.CodeUpdatedin the block that applied the upgrade). Repeat a few times — it does not fail every run.Expected behavior
The events of the block that applied the runtime upgrade should be decodable, and a successful runtime upgrade should not be reported as a failure.
Actual behavior
A block's events are encoded by the runtime of its parent state, but both subxt 0.50 and polkadot-js resolve metadata at the block's own hash. At the upgrade block those disagree, so event decoding fails there — and only there.
Evidence from a standalone subxt 0.50 probe against the live chain (querying one block at a time):
In subxt 0.50.1 the spec version comes from
Core_versionat the block's own hash and the metadata fromMetadata_metadata_at_versionat that same hash (src/client/online_client.rs,at_block_hash_and_number), which is what produces the mismatch.Consequence 1 —
governance-runtime-upgradereports a false failure. Its last step assertsSystem.CodeUpdatedis present in the applying block (local-environment/src/commands/federatedRuntimeUpgrade.ts:63-67). polkadot-js fails to decode that block's events, so the assertion throws even though the upgrade executed:The 21-vs-32 byte mismatch is the shape change itself: the ledger-8 runtime's
CodeUpdatedhas no fields, the ledger-9 one carries a hash.state_getRuntimeVersionconfirms the upgrade did take effect (1000000→2001000) in every run, including the ones where the tool threw.This is operationally risky: an operator sees a hard failure on a successful upgrade and may retry it. Suggested fix for the tool: confirm via the
state_getRuntimeVersionspec-version change rather than by inspecting the applying block's events.Consequence 2 — chain-indexer exits on that block. With a ledger-9-capable indexer build, the chain-indexer dies at the same height:
and, in another run,
(The differing message between runs is the same root cause; which field trips first depends on the events present.)
Scope note: the underlying metadata-resolution behaviour sits in the client libraries (subxt, polkadot-js) rather than in this repo, so the actionable part here is (a) the
governance-runtime-upgradesuccess check, and (b) deciding whether the node should expose something that lets clients resolve the correct metadata for an upgrade block. Worth raising the general case upstream separately.