feat(paseo): substitute relay genesis preset + launch/handover tooling#389
Merged
Conversation
Add tooling to launch a fresh Paseo relay from block 0 (the "substitute" relay that will take over once the current relay is wound down) and hand validator election to Asset Hub. Paseo is post-AHM: Session::SessionManager = NoteHistoricalRoot<_, StakingAhClient>, so the relay's validator set comes from pallet_staking_async_ah_client, with local pallet_staking only as a Passive-mode fallback. The substitute relay boots in Passive (self-elects a small bootstrap set from local staking), then transitions ah_client Passive -> Buffered -> Active to consume Asset Hub's elected set. Changes: - relay/paseo/src/genesis_config_presets.rs: new `substitute` preset (paseo_substitute_genesis) — 4 bootstrap authorities (dev-key placeholders), sudo reused from the current on-chain key, forceEra=ForceNone, num_cores=2, ah_client defaults to Passive. Registered in preset_names + get_preset. - substitute-relay/README.md: launch + staking-handover runbook. - substitute-relay/zombienet-launch.toml: boot the 4-validator relay. - substitute-relay/chopsticks/: relay-only, sudo-driven dry-run of Passive->Buffered->Active and the MinimumValidatorSetSize gate (no AH/XCM). - substitute-relay/tools/derive-session-keys.mjs: real-key substitution helper.
Wrap a doc comment and collapse an iterator chain in the substitute genesis preset (nightly rustfmt), and reformat zombienet-launch.toml (taplo) to satisfy the fmt CI checks.
substitute_host_configuration previously derived from default_parachains_host_configuration, which is the runtime's original genesis default and has drifted from what governance has set on-chain (validation upgrade delays, HRMP channel limits, scheduler params, node_features, ...). Replace it with a faithful snapshot of the live Paseo relay configuration.activeConfig (spec 2_003_001, surveyed 2026-06-30), overriding only num_cores (56 -> bootstrap value; raised post-launch via coretime.request_core_count). max_validators_per_core carries over the live value of 3, already the target ratio. Also fix a stale doc reference: the handover extrinsic is set_mode, not buffer()/activate() (those are internal helpers).
Make the substitute relay ready to take real community-operator keys without putting secrets in source: - add substitute_authority(stash, babe, grandpa, para_validator, para_assignment, authority_discovery, beefy), building the bootstrap authority tuple from PUBLIC keys via hex! + unchecked_into; - express the 4 bootstrap slots through it (still dev keys, clearly marked PLACEHOLDER, so zombienet/chopsticks keep working); - add substitute-relay/tools/format-operator-keys.mjs: validates an operator's stash + author_rotateKeys blob (or the 6 keys individually) and emits the ready-to-paste initial_authorities block; - document the intake process + submission format in substitute-relay/README.md. All six session keys are required (babe/grandpa/para_validator/para_assignment/ authority_discovery/beefy) — authoring-only would give no finality and no parachain backing.
Replace the operator-1 dev placeholder in the substitute genesis with real keys submitted by turboflakes (turboflakes.io): stash + 6 session keys (babe, grandpa, para_validator, para_assignment, authority_discovery, beefy), all public. Operators 2-4 remain DEV placeholders pending their submissions.
Slot turboflakes' second validator (turboflakes.io/01) into bootstrap position 2 of the substitute genesis: stash + 6 session keys, all public. Operators 3-4 remain DEV placeholders (Charlie/Dave) pending their submissions.
Slot CoinStudio into bootstrap position 3 of the substitute genesis: stash + 6 session keys, all public. Operator 4 remains a DEV placeholder (Dave) pending the final provider's submission.
Slot ParaNodes into bootstrap position 4 of the substitute genesis (stash + 6 session keys, all public; their submission included an im_online key from an older runtime, dropped since our SessionKeys has no im_online). All four bootstrap authorities are now real community providers — no dev placeholders.
Generate the launch chain spec for the substitute relay and the supporting bits: - lib.rs: SS58Prefix 0 -> 42. NOTE: this is a relay/paseo runtime-wide constant, so any chain built from this runtime becomes prefix 42 — intended, as the substitute IS the new Paseo; the old relay is being retired. - genesis preset: substitute_host_configuration uses the MAX_CODE_SIZE/MAX_POV_SIZE hard limits. The live max_code_size=3_500_000 was set post-genesis by governance and exceeds MAX_CODE_SIZE (3 MiB), so it can't be used at genesis; raise it after launch via configuration.set_max_code_size if a system chain's wasm needs >3 MiB. - generate_chain_specs.sh: paseo-substitute case (name "Paseo", id "paseo", protocol-id "pad", type live, is-relay, per-package ss58Format=42, chain=substitute). - chain-specs/paseo-substitute.json: generated raw spec — 4 real provider bootstrap validators, sudo = current on-chain key, num_cores=2, ah_client Passive. - substitute-relay/PROVIDER-KEY-INSTRUCTIONS.md: provider key-intake guide.
Add the two initial bootstrap nodes (193.39.195.54, tcp + ws each) to chain-specs/paseo-substitute.json, matching the published paseo.raw.json.
Match the published paseo.raw.json: single turboflakes bootnode (/dns/n8r2.bn.turboflakes.io/tcp/30433/wss/p2p/12D3KooWC3no…nPXbjG).
…rypto check CoinStudio's submission had babe/grandpa reversed (babe was their ed25519 key, grandpa their sr25519 key) — the runtime's SessionKeys is grandpa-first, so a rotateKeys blob split babe-first swaps them. Swap them back in the genesis preset and regenerate paseo-substitute.json (verified: grandpa=ed25519, babe=sr25519 for all four operators, correctly placed). Also harden format-operator-keys.mjs: LAYOUT is now grandpa-first (matches the runtime), and it verifies each key's crypto (babe/para/authority=sr25519, grandpa=ed25519, beefy=secp256k1), hard-failing a wrong-type/swapped submission.
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.
What & why
Tooling to stand up a fresh Paseo relay from block 0 — the substitute relay that takes over once the current relay is wound down — and to hand validator election to Asset Hub afterwards.
The plan hinges on the post-AHM architecture:
Session::SessionManager = NoteHistoricalRoot<Self, StakingAhClient>, so the relay's validator set comes frompallet_staking_async_ah_client, with localpallet_stakingonly as a fallback. Everything keys offah_client's operating mode:pallet_staking(Fallback)Blocked)Blocked)ValidatorSetMinimumValidatorSetSize)Transitions are forward-only:
Passive → Buffered → Active.The substitute relay boots in Passive, self-electing a small bootstrap set from local staking — fully independent of Asset Hub. Once AH is onboarded and its staking populated, the operator drives
ah_client.set_mode(Buffered)→set_mode(Active)and the relay starts consuming AH's elected set.What's in this PR
Runtime —
relay/paseo/src/genesis_config_presets.rs:substitute(paseo_substitute_genesis()), registered inpreset_names()+get_preset().sudo.keyreused from the current on-chain relay sudo (13uYxsEfJL5FYbJ1E7cW85ihp5LckYTyZT6Bqpc7tS4NAArK, embedded as raw bytes).forceEra: ForceNone(freezes the bootstrap set — noElectionProviderMultiPhasechurn during the Passive window).scheduler_params.num_cores = 2(the default leaves it at 0, which can't back any parachain; raise toward 20 post-launch viacoretime.request_core_countas validators scale).ah_clientomitted →Modedefaults to Passive. No paras/HRMP at genesis.cargo check -p paseo-runtimepasses.Tooling —
substitute-relay/:README.md— launch + staking-handover runbook (genesis spec, chain-spec-builder steps, Phases 1–5, event-watch table, failure modes).zombienet-launch.toml— boot the 4-validator relay and confirm it self-elects in Passive.chopsticks/— relay-only, sudo-driven dry-run ofPassive → Buffered → Activeand theMinimumValidatorSetSizegate (no Asset Hub, no XCM — exploits thatvalidator_setaccepts Root, so synthetic sets can be fed via sudo).tools/derive-session-keys.mjs— derive real bootstrap keys + keystore-insert commands to replace the dev placeholders.Verified
The chopsticks dry-run was run against a fork of the live relay (spec 2003001, 152 live validators):
Passivevalidator_set@ Passivesudo.Sudid: Err=StakingAhClient::Blockedset_mode(Buffered)Bufferedset_mode(Active)Activevalidator_set(4)@ ActiveSetTooSmallAndDropped, not storedvalidator_set(152)@ ActiveValidatorSetReceived, storedNotes captured during the run: the live extrinsic is
set_mode(notbuffer()/activate(), which are internal helpers); chopsticks 1.5.0 needs the ESM cli + ayargs@17pin under Node 20; the driver funds//Aliceon the fork (else1010 invalid payment). All documented inchopsticks/README.md.Before a real launch (not in this PR)
tools/derive-session-keys.mjs).MinimumValidatorSetSize(default 100) to ≤ the first real cohort before the handover; raise it as you scale.bond → validate → set_keyson AH so the new relay'sSessionkeys exist (a fork-off of AH ledgers does not carry relay session keys).Out of scope / follow-ups
Testing
cargo check -p paseo-runtime✅