Support for 3 async cores - #131
Open
ilchu wants to merge 50 commits into
Open
Conversation
Switch both runtimes from 6s blocks to 2s blocks with 3 cores assigned per parachain, mirroring polkadot-bulletin-chain PRs #417 (slot-based authoring) and #231 (2s + 3 cores). Closes #113. Runtime changes (both web3-storage and Paseo): - MILLISECS_PER_BLOCK: 6000 -> 2000 - BLOCK_PROCESSING_VELOCITY: 1 -> 3 (3 parachain blocks per relay slot) - UNINCLUDED_SEGMENT_CAPACITY: 3 -> (3 + RELAY_PARENT_OFFSET) * VELOCITY = 12 - New RELAY_PARENT_OFFSET = 1, wired into cumulus_pallet_parachain_system::Config and the RelayParentOffsetApi runtime impl (slot-based authoring requirement) - RELAY_CHAIN_SLOT_DURATION_MILLIS stays 6000 (relay slot is independent) - spec_version: 1 -> 2 Storage-pallet checkpoint cadence is now wall-clock-coupled so it survives block-time changes: DefaultCheckpointInterval = 10 * MINUTES, DefaultCheckpointGrace = 2 * MINUTES (resolves to the prior 100/20 blocks at 6s and 300/60 at 2s — same ~10 min cadence either way). All other timeouts in storage.rs (ChallengeTimeout, SettlementTimeout, RequestTimeout, DeregisterAnnouncementPeriod) auto-rescale via HOURS. Zombienet topology (zombienet.toml + zombienet/storage-paseo-local.toml): - 6 relay validators (alice, bob, charlie, dave, eve, ferdie) to support 3-core scheduling - Relay genesis patched with scheduler_params.num_cores = 3 and async_backing_params (allowed_ancestry_len = 6, max_candidate_depth = 6) - Parachain num_cores = 3; second collator (bob) added - Both collators run with --authoring=slot-based New examples/papi/assign-cores.js + `just assign-cores` recipe: zombienet's `num_cores=3` only sets the relay-side budget; actual assignment of cores to a para needs an explicit Coretime::assign_core extrinsic. The script sudo-batches one assign_core per core (mirrors polkadot-bulletin-chain's examples/assign_cores.js). Use after `just start-chain` to realize full 3-core throughput locally. Verified: - cargo fmt / check / clippy --all-features clean on both runtimes - RPC reports AuraApi::slot_duration() = 2000 ms and RelayParentOffsetApi::relay_parent_offset() = 1 - Relay availability_cores shows para 4000 occupying cores 0/1/2 after assign-cores - ~2.5-2.85s avg parachain block production (sampled over 60-91s windows) - fs-demo-ci (Rust subxt L1) passes end-to-end Known limitation: The PAPI demos under examples/papi/ are still on polkadot-api 1.23 while the rest of the repo is on 2.x. At 2s blocks PAPI 1.23's chainHead pin window evicts the runtime-context block before `getValue()` can resolve, producing BlockNotPinnedError. Migrating examples/papi/ to PAPI 2.x is substantive (event payload reshape, H256 -> SizedHex<N>, signature enum form, etc.) and tracked as follow-up; just demo will be red on dev until that lands.
Brings examples/papi in line with the rest of the repo (the UIs are
already on polkadot-api@^2.1.0 + @polkadot-api/cli@^0.20.4) and gets the
PAPI demo working under 2-second blocks.
The previous full-flow.js failure modes on this branch (BlockNotPinnedError
on the first storage query after a tx) turned out to be two separate
issues, both of which are addressed here:
- Event watching destabilises chainHead. On PAPI 2.x, a global
`api.event.X.Y.watch().subscribe()` interferes with the pinned-block
set; subsequent `api.query.X.Y.getValue(...)` then throws
`BlockNotPinnedError ... (getRuntimeCtx)`. `watchDefendedEvents` is
removed — `respondToChallenge` already returns the ChallengeDefended
event extracted from its own extrinsic events, so the demo counts
those instead.
- finalizedBlock$ is now a BehaviorSubject. `waitForNextBlock` /
`waitForBlock` declared `const sub = papi.finalizedBlock$.subscribe(...)`
and referenced `sub` from inside the callback. On 2.x the BehaviorSubject
fires synchronously on subscribe, before the `const` is assigned — TDZ
ReferenceError. Switched to a `let sub; sub = papi.finalizedBlock$.subscribe(...)`
pattern guarded by a `resolved` flag.
Type/shape adjustments to match the 2.x generated descriptors
(`SizedHex<N>` replaces `Binary` for `H256` / `[u8; N]` fields,
anonymous enums take `{ type, value }` object form):
- `mmr_root`, `data_root`, peaks, siblings: drop
`Binary.fromBytes(hexToBytes(...))`, pass the raw hex string straight
through.
- `provider_signature`: drop `Enum("Sr25519", Binary.fromBytes(...))`,
use `{ type: "Sr25519", value: hexSignature }` (MultiSignature is an
AnonymousEnum in the descriptors).
- `respond_to_challenge` response: same — `{ type: "Proof", value: proof }`.
- `chunk_data`: `Uint8Array` field, pass raw `Uint8Array` instead of
`Binary.fromBytes(...)` (the descriptor type check now rejects Binary
where Uint8Array is required).
Other plumbing for the version bump:
- `requireOneEvent` now returns `matched[0].payload` — 2.x wraps filtered
events as `{ original, payload }`.
- Import path `polkadot-api/ws-provider` → `polkadot-api/ws` (and in
assign-cores.js).
- `papi:generate` script: trailing `&& papi` is a no-op on 2.x; replace
with `&& papi generate`.
Verified end-to-end against a running zombienet (chain producing at
~2.5–2.8 s/block after `just assign-cores`):
- `just demo`: all 8 steps green, "PASSED: Provider received payment!"
- `just drain-tx-pool-then fs-demo-ci`: passes
- `just drain-tx-pool-then s3-demo-ci`: passes
Collaborator
The 'Merge branch dev into ic/2s-blocks-3-cores' conflict resolution kept both the feature branch's @polkadot-api/cli bump (^0.20.4) and dev's new c8 dependency, but dropped the comma between them, producing invalid JSON.
# Conflicts: # runtimes/web3-storage-paseo/src/storage.rs
…c_version, document UNINCLUDED_SEGMENT_CAPACITY - examples/papi/common.js: drop undeclared `resolved` reference (ReferenceError) and redundant unsubscribe in waitForNextBlock/waitForBlock - paseo runtime: bump spec_version 4_001 -> 4_002 for the consensus change - runtimes: replace magic 3 in UNINCLUDED_SEGMENT_CAPACITY with a named RELAY_SLOTS_OF_CAPACITY constant + derivation comment
Collaborator
|
@ilchu please, you can continue, after W3S presentation (today 15:00 CET), we can go with this to PreviewNet for sure |
The migration check builds one post-upgrade block on a snapshot of the live chain and mocks its Aura slot as snapshot_timestamp / blocktime. previewnet paseo runs at slot_duration 6000, so the stored Aura slot is timestamp/6000 and pallet-aura requires the new slot not to decrease, i.e. blocktime <= 6000. The 24000 value made the mocked slot ~4x smaller and tripped "Slot must not decrease". 2000 matches the new SLOT_DURATION and stays valid once previewnet itself moves to 2s blocks.
…line The 2.x migration bumped polkadot-api and @polkadot-api/cli but left @polkadot-api/substrate-bindings at ^0.16.5, a 1.x-era codec line. With no committed lockfile, that floor hoists into the generated descriptors, so their checksums are computed on a different codec than polkadot-api@2.x uses at runtime — failing every StorageProvider call with "Incompatible runtime entry". Bump to ^0.20.3 (what polkadot-api@2.1.6 and the user-interfaces workspace already use) and pin polkadot-api ^2.1.6.
The 2.x migration (0894b92) bumped polkadot-api/cli but left the Binary value-class API in place; substrate-bindings@0.16.5 still shipped Binary.fromBytes so it ran locally, masking the break. With bindings on the 2.x line (0.20.x) that API is gone — Vec<u8>/[u8;N] fields are plain Uint8Array, AccountId is SS58String, and H160 is a hex string. - write sites: drop the Binary.fromBytes(x) wrapper; pass the Uint8Array directly (including the inline (await import(...)).Binary.fromBytes form in the e2e specs) - read sites: drop .asBytes()/.asText() on Uint8Array fields; decode multiaddr via TextDecoder (the defensive `x.asBytes ? ... : x` guards were already 2.x-safe) - revive: dest is SizedHex<20> (hex string, as returned by instantiated.contract); code/data are Uint8Array - drop now-unused Binary imports Verified against live previewnet: Providers reads yield Uint8Array multiaddr/public_key; update_provider_multiaddr encodes from a raw Uint8Array.
…Array The previous commit converted all byte fields to raw Uint8Array, but that is only correct for variable Vec<u8> (multiaddr, name, key, content_type, code, data). Fixed-size [u8; N] fields are SizedHex<N> in polkadot-api 2.x — i.e. 0x-prefixed hex strings — and passing a Uint8Array there fails the arg-shape check as "Incompatible runtime entry": - establish_storage_agreement sig (MultiSignature, [u8; 64]) — hex - put_object_metadata cid (blake2-256, [u8; 32]) — hex; the read-side comparison now compares the SizedHex strings directly provider-supplied fixed fields (mmr_root, provider_signature, mmr proof hashes) already arrive as hex from the provider node, and the revive H160 dest is the hex string returned by instantiated.contract — all unchanged. Verified against live previewnet with the CI dependency set: establish (hex sig) and put_object_metadata (hex cid) encode COMPATIBLE; the old Uint8Array forms reproduce the "Incompatible runtime entry" error.
respondToChallenge returned the extracted ChallengeDefended payload, but every consumer that uses the return (full-flow recordDefended, e2e/05) reads result.events — which is undefined on a payload, so PAPI's eventDescriptor.filter(undefined) threw "Cannot read properties of undefined (reading 'filter')" right after the on-chain respond succeeded. Return the full tx result instead; keep the requireOneEvent call only to assert the challenge was defended (the sc-flow/sc-coverage callers ignore the return and rely on that throw).
In polkadot-api 2.x the Revive contract address (Instantiated.contract, ContractEmitted.contract) and the Revive.call `dest` are SizedHex<20> hex strings, not byte arrays: - deployContract: keep instantiated.contract as the hex string (toHex on a hex string coerces it to an array length and throws RangeError at deploy) - h160ToSubstrate: accept hex or raw bytes for publicKey.set() - decodeContractEmitted: feed viem hex data/topics without re-encoding the already-hex values - sc-coverage: precompile addresses are SizedHex<20>, used directly as Revive.call dest (drop the now-unused hexToBytes) Verified locally on an omni-node dev chain + provider: sc-flow, sc-coverage, sc-team-drive, sc-token-gated and the L0 demo all pass end to end.
# Conflicts: # examples/papi/api.js # examples/papi/common.js # examples/papi/e2e/01-provider-registration.ts # examples/papi/e2e/03-s3-bucket-and-objects.ts # examples/papi/package.json # examples/papi/sc-api.js # examples/papi/sc-coverage.ts
After merging dev (PR #160 migrated examples/papi fully to TS), assign-cores was the only remaining .js file in the repo: run via bare `node` and excluded from `tsc --noEmit`. Convert it to .ts so it matches the sibling papi scripts and gets typechecked. - type the two implicit-any params (argv, seed) - replace the push loop with Array.from to avoid an evolving any[] under strict - run via `node --import tsx` in the justfile recipe Keeps its self-contained makeSigner: it targets the relay chain via the untyped getUnsafeApi(), so it deliberately avoids the parachain SDK/descriptors. tsc --noEmit passes under strict.
Add a BlockNumberProvider associated type to pallet-storage-provider's Config and route every block-number read in the pallet (and the drive/s3 registries, which reuse it via their Config supertrait) through it. Production runtimes supply RelaychainDataProvider, so all timeouts, expiries, checkpoint windows and nonce-recency checks are measured in relay chain blocks (6s) and keep their wall-clock meaning when the parachain block time changes (#113 / PR #131). Mocks supply System. The challenge slash sweep can no longer probe the single key n in on_finalize(n): relay numbers advance by a variable amount (including zero) between consecutive parachain blocks. Replace it with a drain-range sweep in on_initialize that tracks progress in a new LastSweptChallengeBlock cursor, drains all deadline keys strictly below the previous block's relay parent (provably unrespondable), and returns the exact weight consumed instead of pre-reserving it. Two independent bounds keep the block budget safe: a 32-key span cap on probing and a MaxChallengesPerDeadline slash budget per block (the old single-key worst case), with mid-key carry-over via the cursor. Runtime timing constants now derive from a new relay_time module based on RELAY_CHAIN_SLOT_DURATION_MILLIS instead of the parachain MILLISECS_PER_BLOCK; values are numerically unchanged today. Benchmarks advance both clocks via a set_block_number helper (the provider implements set_block_number under runtime-benchmarks), and the paseo runtime tests gain a set_clocks helper for the same reason. Closes #233
The pallet now measures every duration against the relay chain clock, so off-chain actors must snapshot ParachainSystem.LastRelayChainBlockNumber instead of the parachain height wherever a value meets an on-chain check. Provider node: chain_state.current_block now tracks the relay block anchored to each finalized parachain block (one extra storage read per block), feeding /negotiate's valid_until and the checkpoint window math; the fetch/decode is shared via a new storage_client::substrate::fetch_last_relay_block_number helper. JS SDK: new currentRelayBlock/waitForRelayBlock helpers in @web3-storage/layer0. Demos and e2e suites switch their CommitmentPayload nonce snapshots off System.Number (which would be rejected on any network where relay numbers dwarf parachain heights) and wait for agreement expiry / checkpoint windows on the relay clock. full-flow's demo agreement duration grows 15 -> 40 relay blocks since the relay clock keeps ticking while the parachain onboards.
Coverage-gated (tests/*_integration.rs) additions: - /negotiate signs valid_until = relay current_block + RequestTimeout, asserted with a Paseo-scale relay number, and returns 503 chain_state_not_ready when either the clock or the constant is still unknown (both refusal branches were untested). Plus checkpoint-coordinator duty tests (coordinators target): windows derive from the relay-scale block number and move only when the relay clock crosses an interval boundary, never on a repeated relay parent (velocity > 1) or an intra-window advance. Provider coverage: 64.79% locally vs 64.59% base (the dip came from the coordinator's relay-fetch lines, which need a live chain; the gate ignores subxt_client.rs).
PR #131 now targets #268 (relay-block denomination) instead of dev. Conflict resolutions: - runtimes/*/storage.rs: took the relay-block-provider side wholesale. This PR's DefaultCheckpointInterval/Grace rework (10*MINUTES/2*MINUTES of parachain time) is obsolete — the constants are relay-denominated now (100/20 relay blocks = the same ~10 min / ~2 min wall-clock) and no longer rescale with parachain block time, which was the whole point of that rework. - runtimes/*/lib.rs spec_version: both sides bumped independently from the merge base, so take the union: local runtime 2 vs 3 -> 4, paseo 4_002 vs 4_003 -> 4_004, with lineage comments. - Everything else (2s consensus constants, velocity 3, RelayParentOffset, zombienet topology, assign-cores) merged clean.
#275 extracted the wire types into provider-negotiation and removed the provider node's storage-client dependency, but two callers still reached into storage_client::substrate::fetch_last_relay_block_number. Inline the one-shot ParachainSystem::LastRelayChainBlockNumber query as a pub(crate) helper in subxt_client so the node reads the relay clock over its own subxt connection without depending on the client SDK.
The relay_time module's MINUTES/HOURS are name-twins of the parachain time module's MINUTES/HOURS, so a call site like 48 * HOURS gave no hint which clock it measured. Rename the relay-chain units to RC_MINUTES / RC_HOURS in both runtimes (constants + storage.rs timeout params) so the relay-chain denomination is explicit at every use. Values unchanged; the parachain time::* units (Session Period/SessionLength) keep their names.
Rewrite the on_initialize sweep docs as bullets with the safety formula (keys < previous relay parent are final), tighten MAX_SWEEP_SPAN and LastSweptChallengeBlock, and fix stale on_finalize references left over from the on_initialize move. Addresses review comments on #268.
The benchmark still called the dropped on_finalize hook, which is now the Hooks default no-op, so it measured nothing and under-charged the slash sweep. Anchor the cursor and relay clock so on_initialize drains exactly the loaded deadline key, and add a verify guard asserting the drain actually happened. Addresses review comment on #268.
The Automatic Slashing sequence still showed the old on_finalize(block_number) single-key take. Reflect the relay-block range drain tracked by LastSweptChallengeBlock. Addresses review comment on #268.
A single deadline can hold up to MaxChallengesPerDeadline (1000) challenges; slashing all of them in one block costs ~5 MB PoV — the whole block budget. Introduce MAX_SWEEP_SLASH_BUDGET (100) and cap the per-block budget at min(MaxChallengesPerDeadline, MAX_SWEEP_SLASH_BUDGET), letting a full deadline drain over several blocks via the existing carry-over cursor. Tighten the benchmark component to the effective budget so weight regeneration stays within the sweep's real ceiling. Addresses review comment on #268.
The field holds the relay-chain block anchored to the latest finalized parachain block, not the parachain height. Rename it (and the local it feeds in /negotiate) so the relay-clock semantics are self-evident, resolving the ambiguity flagged in review. The separate get_current_block trait methods are untouched. Addresses review comment on #268.
Assert no unresolved challenge sits at or below LastSweptChallengeBlock: the sweep drains everything up to its cursor and new challenges always land above it, so a violation flags a stranded-unslashed challenge (e.g. an un-migrated upgrade leaving keys below the anchor). Addresses review comment on #268.
Conflict in pallet/src/lib.rs (both branches added a try_state hook): - imports: kept our `One` and dev's cfg-gated `TryRuntimeError`. - Hooks::try_state: took dev's delegating form (-> do_try_state), which supersedes our inline sweep-cursor check. The sweep-cursor invariant is re-homed into dev's impls/try_state.rs in the following commit.
The merge with dev adopted its Pallet::do_try_state() structure, which superseded the inline try_state hook from the earlier commit. Move the sweep-cursor invariant (no unresolved challenge at or below LastSweptChallengeBlock) in as check_challenge_sweep_cursor (P1.5), with a detection test matching the module's convention.
The ChainState.current_block -> current_relay_block rename missed the tests/ integration files (the earlier clippy check omitted --all-targets, so they were never compiled). Rename the field accesses so `cargo clippy --all-targets` and the integration tests build again.
Per review: the provider should not reach into ParachainSystem's LastRelayChainBlockNumber (a relay-specific storage item) to learn the clock on-chain durations are measured against. Introduce a block-notion-agnostic abstraction: - Pallet: rename Pallet::current_block -> current_anchor_block (the sole place the BlockNumberProvider is read) and expose it via a new StorageProviderApi::current_anchor_block runtime API, implemented in both runtimes. - Provider: replace the dynamic LastRelayChainBlockNumber storage read with a dynamic current_anchor_block runtime-API call, and rename ChainState.current_relay_block -> current_anchor_block. The provider no longer knows or cares whether the anchor is a relay, parachain, or future block number. - Registries call sites updated to current_anchor_block. Behavior-preserving: the runtime API resolves to the same value the provider read before. Note: crates/storage-subxt static bindings are regenerated from node metadata and do not yet include the new API; the provider uses a dynamic call, so this is not a blocker.
dev's #288 bumped subxt 0.44 -> 0.50 across chain-facing crates, which overlapped the two provider files this branch rewrote for the current_anchor_block runtime API. Resolution: - provider-node/src/subxt_client.rs (conflict): kept the anchor-block semantics but ported to subxt 0.50 — fetch via runtime_apis().call_raw ("StorageProviderApi_current_anchor_block") and decode the raw u32, which also avoids depending on the API being in the node metadata snapshot. current_anchor_block() now uses at_current_block(). - chain_state_coordinator.rs (silent auto-merge, 0.44 call left behind): fetch the anchor via block.at().runtime_apis(); hoisted the block handle so anchor + events share one at(). Added a state_call mock arm and updated the field-rename in the coordinator test. - client/src/substrate.rs: took dev's 0.50 version. The branch's fetch_last_relay_block_number helper here was dead code (orphaned when the provider inlined its own copy in f636e25) and 0.44-only, so dropping it matches dev. Full workspace compiles; pallet/provider/client/registry/paseo tests green; clippy clean.
analyze_provider compared the parachain head against the relay-denominated snapshot.checkpoint_block, so on live networks the age saturated to 0 and every provider read as freshly checkpointed. Re-add the SDK-side anchor helper (dropped with the subxt 0.50 signatures) as a runtime-API read and measure the age on it, reusing the block-pinned handle for a consistent snapshot.
Locals holding Pallet::current_anchor_block() were still named current_block, reading as parachain height. Rename them (and the checkpoint helpers' parameters) to anchor_block, matching the negotiate handler's naming; fix the stale Pallet::current_block doc link and the ChainStateNotReady messages along the way.
current_anchor_block tells clients where the pallet clock is but not how fast it ticks, so the provider dashboard humanized anchor-denominated durations (agreement duration, checkpoint interval/grace, min/max duration) with Aura.SlotDuration — identical today, silently wrong once the parachain block time changes. Add the paired runtime API (both runtimes return RELAY_CHAIN_SLOT_DURATION_MILLIS) and read it in the dashboard via the unsafe API (no descriptor regeneration; older runtimes keep the 6s default), renaming blockTimeMs to anchorBlockTimeMs so parachain block time can't be rewired in by accident.
The anchor_block_time_millis runtime API hardcoded RELAY_CHAIN_SLOT_DURATION_MILLIS in each runtime's impl block, away from the BlockNumberProvider wiring it describes. Make it a #[pallet::constant] instead: the clock and its tick are now declared side by side in each runtime's storage.rs, the runtime API delegates to the pallet like current_anchor_block does, integrity_test rejects a zero value, and the constant lands in metadata for free.
…arisons The provider dashboard and s3-ui compared the parachain height against anchor-denominated on-chain values, which silently passes on zombienet and breaks on any network where relay numbers dwarf parachain heights: agreement expiry and challenge status in chain-client.ts (whose local blockNumber$ was additionally never fed, so both always read 0), checkpoint-overdue detection, the deregister cooldown, earnings progress, and the s3-ui challenge countdown. Add an anchorBlock$ to each UI's chain state, refreshed per finalized block from the current_anchor_block runtime API via the unsafe API (live metadata, no descriptor regeneration; pre-anchor runtimes fall back to the parachain height, which is their pallet clock), and point all six comparisons at it.
The per-block anchor refresh is a fire-and-forget promise, so a result resolving after disconnect() (or a network switch) could write a stale anchor from the previous chain into the fresh state. Publish only if the client the call was made on is still the current client.
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.
Summary
Switches both runtimes from 6-second to 2-second blocks with 3 relay cores per parachain, matching what polkadot-bulletin-chain PRs #417 (slot-based authoring) and #231 (2 s + 3 cores) did for Bulletin.
Stacked on #268 (relay-chain block denomination, #233). That ordering makes this PR a pure throughput/consensus change: every storage-pallet timeout, expiry and checkpoint window is denominated in relay blocks and reads its clock from
RelaychainDataProvider, so nothing here touches storage timing at all — the constants keep their wall-clock meaning at any parachain block time.Closes #113.
Runtime changes (
runtimes/web3-storage-local/,runtimes/web3-storage-paseo/)MILLISECS_PER_BLOCK: 6000 → 2000 (SLOT_DURATIONfollows)BLOCK_PROCESSING_VELOCITY: 1 → 3 (three parachain blocks per relay slot)RELAY_PARENT_OFFSET = 1, wired intocumulus_pallet_parachain_system::Config::RelayParentOffsetand theRelayParentOffsetApiruntime impl — required by slot-based authoringUNINCLUDED_SEGMENT_CAPACITYderived from those two:(3 + RELAY_PARENT_OFFSET) * BLOCK_PROCESSING_VELOCITY = 12RELAY_CHAIN_SLOT_DURATION_MILLISstays 6000 (the relay sets its own slot time; it is also what therelay_timeconstants module from Measure pallet timeouts in relay chain blocks, not parachain blocks #268 anchors to, which is why storage timing is untouched)spec_version: local runtime → 4, paseo → 4_004 (both sides of the Measure pallet timeouts in relay chain blocks, not parachain blocks #268 merge had bumped independently, so the merge takes the union)scripts/runtimes-matrix.json: try-runtime--blocktime6000 → 2000pallet_aura::AllowMultipleBlocksPerSlot = true,FixedVelocityConsensusHook, and theRelayParentOffsetApishell were already in place — they just needed the new constants threaded through.Zombienet topology (
zombienet.toml,zombienet/storage-paseo-local.toml)3-core scheduling needs more validators and explicit core capacity: 6 relay validators (alice…ferdie), relay genesis patched with
scheduler_params.num_cores = 3andasync_backing_params { allowed_ancestry_len = 6, max_candidate_depth = 6 },num_cores = 3on the parachain block, and two collators (alice, bob) running--authoring=slot-based.examples/papi/assign-cores.ts+just assign-coresnum_cores = 3in genesis only sets the relay-side budget; cores still have to be assigned to the para via the relay'sCoretime::assign_coreextrinsic before the parachain can actually use them — without it block production stays at ~12 s/block. The script sudo-batches oneassign_coreper core (same pattern as Bulletin'sexamples/assign_cores.js). Afterjust start-chain, runjust assign-coresonce and the parachain settles at ~2 s blocks.Plus a one-line drive-by in
provider-discovery.ts: decodemultiaddrviaTextDecoder(papi 2.x returns raw bytes here, not aBinary).No longer in this PR (superseded since the original version)
DefaultCheckpointInterval/Gracere-expression as10 * MINUTES/2 * MINUTES— dropped in the Measure pallet timeouts in relay chain blocks, not parachain blocks #268 merge. Those constants are relay-denominated now (100/20 relay blocks ≈ the same 10 min / 2 min) and no longer rescale with parachain block time, which was the entire reason for that rework.examples/papimigration to polkadot-api 2.x — landed ondevindependently and was absorbed via earlier merges.Verified
The original 6s→2s bring-up was verified live against zombienet with
just assign-coresapplied:AuraApi::slot_duration() = 2000 ms,RelayParentOffsetApi::relay_parent_offset() = 1, para 4000 occupying relay cores 0/1/2, ~2–2.9 s/block sustained, andjust demo/fs-demo-ci/s3-demo-cipassing end-to-end. Post-retarget onto #268: both runtimes compile, all paseo runtime tests (incl. XCM e2e andintegrity_test) pass under the 2s constants; the live 2s zombienet run has not yet been repeated on the stacked branch.