Skip to content

Support for 3 async cores#131

Open
ilchu wants to merge 2 commits into
devfrom
ic/2s-blocks-3-cores
Open

Support for 3 async cores#131
ilchu wants to merge 2 commits into
devfrom
ic/2s-blocks-3-cores

Conversation

@ilchu

@ilchu ilchu commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

Switches both runtimes from 6-second to 2-second blocks with 3 relay cores assigned per parachain, matching what polkadot-bulletin-chain PRs #417 (slot-based authoring) and #231 (2 s + 3 cores) did for Bulletin.

Closes #113.

Runtime changes (runtime/ + runtimes/web3-storage-paseo/)

  • MILLISECS_PER_BLOCK: 6000 → 2000
  • BLOCK_PROCESSING_VELOCITY: 1 → 3 (three parachain blocks per relay slot)
  • New RELAY_PARENT_OFFSET = 1, wired into both cumulus_pallet_parachain_system::Config::RelayParentOffset and the RelayParentOffsetApi runtime impl — required by slot-based authoring
  • UNINCLUDED_SEGMENT_CAPACITY derived from those two: (3 + RELAY_PARENT_OFFSET) * BLOCK_PROCESSING_VELOCITY = 12
  • RELAY_CHAIN_SLOT_DURATION_MILLIS stays 6000 (relay slot is set by the relay, not us)
  • spec_version bumped 1 → 2

pallet_aura::AllowMultipleBlocksPerSlot = true, FixedVelocityConsensusHook, and the RelayParentOffsetApi shell were all already in place — they just needed the new constants threaded through.

Storage-pallet checkpoint cadence (storage.rs × 2)

DefaultCheckpointInterval / DefaultCheckpointGrace were raw block counts (100 / 20); without re-expressing them, they would fire 3× more often under 2 s blocks and triple the CheckpointReward / CheckpointMissPenalty token flow per hour. Switched to:

pub const DefaultCheckpointInterval: BlockNumber = 10 * MINUTES;
pub const DefaultCheckpointGrace: BlockNumber = 2 * MINUTES;

These resolve to today's 100 / 20 at 6 s and 300 / 60 at 2 s — same ~10-minute cadence either way, and they survive future block-time changes. All other storage timeouts (ChallengeTimeout, SettlementTimeout, RequestTimeout, DeregisterAnnouncementPeriod)
auto-rescale through HOURS.

Zombienet topology (zombienet.toml, zombienet/storage-paseo-local.toml)

3-core scheduling needs more validators and explicit core capacity:

  • 6 relay validators: alice, bob, charlie, dave, eve, ferdie
  • Relay genesis patched with scheduler_params.num_cores = 3 and async_backing_params { allowed_ancestry_len = 6, max_candidate_depth = 6 }
  • Parachain block sets num_cores = 3
  • Two collators (alice, bob), both running with --authoring=slot-based

New examples/papi/assign-cores.js + just assign-cores

num_cores = 3 in genesis only sets the relay-side budget; cores still have to be assigned to a specific para via the relay's Coretime::assign_core extrinsic before the parachain can actually use them. Without this step block production stays at ~12 s/block. The
script sudo-batches one assign_core per core (same pattern as Bulletin's examples/assign_cores.js). After just start-chain, run just assign-cores once and the parachain settles at ~2 s blocks.

examples/papi migrated to polkadot-api 2.x

The rest of the repo (user-interfaces/*) was already on polkadot-api@^2.1.0 + @polkadot-api/cli@^0.20.4; only examples/papi/ was lagging on 1.23. At 2 s blocks the old version's chainHead pin window evicts the runtime-context anchor block faster than getValue()
can resolve, so this needed to come along. The bump landed two underlying fixes worth calling out:

  • api.event.X.Y.watch().subscribe(...) destabilises chainHead in 2.x. A global event watch corrupts the pinned-block set; the very next getValue() then throws BlockNotPinnedError ... (getRuntimeCtx). watchDefendedEvents is gone — respondToChallenge already
    returns the ChallengeDefended event extracted from its own extrinsic events, so the demo counts those.
  • finalizedBlock$ is now a BehaviorSubject. const sub = papi.finalizedBlock$.subscribe(cb) with cb referencing sub hits a TDZ ReferenceError because the BehaviorSubject fires synchronously on subscribe before const is assigned. Switched to let sub + a resolved
    flag.

Type-shape adjustments to match 2.x's generated descriptors: SizedHex for H256 / fixed-byte fields (pass raw hex string, not Binary), AnonymousEnum object form { type, value } for MultiSignature and respond_to_challenge's response, raw Uint8Array for
chunk_data. Plus housekeeping: polkadot-api/ws-provider → polkadot-api/ws, papi:generate script now runs papi generate (the trailing papi was a no-op on 2.x), and requireOneEvent returns matched[0].payload (2.x wraps filtered events as { original, payload }).

Verified

Against a running local zombienet, with just assign-cores applied:

  • cargo +nightly fmt, cargo check, cargo clippy --all-features -- -D warnings — clean on both runtimes.
  • RPC reports AuraApi::slot_duration() = 2000 ms, RelayParentOffsetApi::relay_parent_offset() = 1.
  • Relay availability_cores shows para 4000 occupying cores 0/1/2.
  • Parachain block production sampled at ~2.5–2.85 s/block over a 60–90 s window.
  • just demo (PAPI Layer-0 full flow): all 8 steps pass through to "Provider received payment".
  • just drain-tx-pool-then fs-demo-ci, just drain-tx-pool-then s3-demo-ci: pass.

ilchu added 2 commits June 4, 2026 18:40
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
@ilchu ilchu self-assigned this Jun 4, 2026
@bkontur

bkontur commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator

let's wait with this untill we resolve #129 and #130

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Setup 2s blocks (3 cores) for testnet runtime and paseo one

2 participants