Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
dd2108d
feat(runtime): enable 2s blocks with 3-core elastic scaling
ilchu Jun 4, 2026
0894b92
chore(examples/papi): migrate to polkadot-api 2.x
ilchu Jun 4, 2026
e6ddf27
Merge branch 'dev' into ic/2s-blocks-3-cores
ilchu Jun 16, 2026
3bc0909
fix(examples/papi): add missing comma in package.json devDependencies
ilchu Jun 16, 2026
830d267
Merge remote-tracking branch 'origin/dev' into ic/2s-blocks-3-cores
bkontur Jun 18, 2026
c1aa8d5
fix(2s-blocks): correct waitForNextBlock/waitForBlock, bump paseo spe…
bkontur Jun 18, 2026
6d17b3f
Fix check-migrations
bkontur Jun 18, 2026
10ae34d
fix(ci): set try-runtime --blocktime to 2000 for both runtimes
ilchu Jun 18, 2026
66344f9
fix(examples/papi): align substrate-bindings to the polkadot-api 2.x …
ilchu Jun 18, 2026
40ff25e
fix(examples/papi): finish polkadot-api 2.x byte API migration
ilchu Jun 18, 2026
17bba24
fix(examples/papi): fixed-size byte fields are hex strings, not Uint8…
ilchu Jun 18, 2026
629b00c
fix(examples/papi): respond_to_challenge returns full tx result
ilchu Jun 19, 2026
c438412
fix(examples/papi): handle Revive H160 / precompile addresses as hex
ilchu Jun 19, 2026
96b0083
Merge branch 'dev' into ic/2s-blocks-3-cores
ilchu Jun 22, 2026
ffb78a4
Merge branch 'dev' into ic/2s-blocks-3-cores
ilchu Jun 24, 2026
fd1ab97
Merge branch 'dev' into ic/2s-blocks-3-cores
ilchu Jun 24, 2026
6fa1af2
Merge branch 'dev' into ic/2s-blocks-3-cores
ilchu Jun 25, 2026
2592c6e
Merge remote-tracking branch 'origin/dev' into ic/2s-blocks-3-cores
ilchu Jun 26, 2026
212daa3
refactor(papi): convert assign-cores.js to TypeScript
ilchu Jun 26, 2026
004d9a2
Merge branch 'ic/relay-block-provider' into ic/2s-blocks-3-cores
ilchu Jul 7, 2026
2e3ef9a
Merge branch 'ic/relay-block-provider' into ic/2s-blocks-3-cores
ilchu Jul 17, 2026
a7ddee9
Merge branch 'ic/relay-block-provider' into ic/2s-blocks-3-cores
ilchu Jul 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions examples/papi/assign-cores.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Assign N coretime cores to a parachain on the relay chain via sudo.
//
// Usage:
// node --import tsx assign-cores.ts <relay_ws> <para_id> <num_cores> [seed]
//
// Defaults: ws://127.0.0.1:9900, 4000, 3, //Alice.
//
// Mirrors what `polkadot-bulletin-chain`'s `examples/assign_cores.js` does for
// the Bulletin testnet: sudo-batches `Coretime::assign_core(core_idx, 0, [(Task(paraId), 57600)], None)`
// for each core. Required because zombienet's `num_cores=N` only sets the
// scheduler-params budget — actual assignment of cores to a para still needs
// a `Coretime::assign_core` extrinsic on the relay.
//
// Uses PAPI's untyped API (`client.getUnsafeApi()`) so we don't need to
// pre-generate relay-chain descriptors. The shape of the call is discovered
// from the running chain's metadata.

import { createClient } from "polkadot-api";
import { getWsProvider } from "polkadot-api/ws";
import { getPolkadotSigner } from "polkadot-api/signer";
import { sr25519CreateDerive } from "@polkadot-labs/hdkd";
import {
DEV_PHRASE,
entropyToMiniSecret,
mnemonicToEntropy,
ss58Address,
} from "@polkadot-labs/hdkd-helpers";

function parseArgs(argv: string[]) {
return {
relayWs: argv[2] || "ws://127.0.0.1:9900",
paraId: Number(argv[3] || 4000),
numCores: Number(argv[4] || 3),
seed: argv[5] || "//Alice",
};
}

function makeSigner(seed: string) {
const devMiniSecret = entropyToMiniSecret(mnemonicToEntropy(DEV_PHRASE));
const deriveSr25519 = sr25519CreateDerive(devMiniSecret);
const keyPair = deriveSr25519(seed);
return {
signer: getPolkadotSigner(keyPair.publicKey, "Sr25519", keyPair.sign),
address: ss58Address(keyPair.publicKey),
};
}

async function main() {
const { relayWs, paraId, numCores, seed } = parseArgs(process.argv);
console.log(`Assigning ${numCores} coretime cores to para ${paraId} via ${relayWs} (sudo: ${seed})`);

const client = createClient(getWsProvider(relayWs));
const api = client.getUnsafeApi();
const { signer, address } = makeSigner(seed);
console.log(`Sudo signer address: ${address}`);

// Compose the inner calls as their decoded RuntimeCall form so PAPI can
// re-encode them inside Utility.batch_all / Sudo.sudo without a typed
// descriptor for the relay chain.
const innerCalls = Array.from(
{ length: numCores },
(_, core) =>
api.tx.Coretime.assign_core({
core,
begin: 0,
assignment: [[{ type: "Task", value: paraId }, 57600]],
end_hint: undefined,
}).decodedCall
);

const batchAll = api.tx.Utility.batch_all({ calls: innerCalls }).decodedCall;
const sudoTx = api.tx.Sudo.sudo({ call: batchAll });

console.log("Submitting sudo(batch_all(assign_core x N))...");
const result = await sudoTx.signAndSubmit(signer);
if (!result.ok) {
console.error("Sudo call failed:");
console.error(JSON.stringify(result, null, 2));
process.exit(1);
}
console.log(`Included in block ${result.block.hash} at index ${result.block.index}`);
for (const e of result.events) {
console.log(` event: ${e.type}.${e.value.type}`);
}
client.destroy();
}

main().catch((e) => {
console.error(e);
process.exit(1);
});
2 changes: 1 addition & 1 deletion examples/papi/provider-discovery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ function printProvider({ address, info, score, reasons }: { address: string; inf
const free = cap === 0n ? "unlimited" : (cap - info.committed_bytes).toString();
console.log(` ${address}`);
console.log(` score = ${score}`);
console.log(` multiaddr = ${info.multiaddr.asText()}`);
console.log(` multiaddr = ${new TextDecoder().decode(info.multiaddr)}`);
console.log(` stake = ${info.stake}`);
console.log(` price_per_byte = ${info.settings.price_per_byte}`);
console.log(
Expand Down
7 changes: 7 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,13 @@ subxt-codegen URL=CHAIN_WS OUTPUT="crates/storage-subxt/src/storage_paseo_runtim
demo PROVIDER_URL=PROVIDER_URL PROVIDER_SEED="//Alice" CLIENT_SEED="//Bob": papi-setup
node --import tsx examples/papi/full-flow.ts "{{ CHAIN_WS }}" "{{ PROVIDER_URL }}" "{{ PROVIDER_SEED }}" "{{ CLIENT_SEED }}"

# Assign N coretime cores to a parachain on the relay via sudo.
# Required after `just start-chain` to actually realise 3-core elastic scaling —
# `num_cores` in zombienet's genesis only sets the budget; assignment to a
# specific para still needs a `Coretime::assign_core` extrinsic.
assign-cores PARA_ID="4000" NUM_CORES="3" SEED="//Alice": papi-setup
node --import tsx examples/papi/assign-cores.ts "{{ RELAY_WS }}" "{{ PARA_ID }}" "{{ NUM_CORES }}" "{{ SEED }}"

# Compile the example marketplace contract to PolkaVM bytecode + ABI.
# Requires: solc and resolc on PATH (see examples/contracts/README.md).
build-contracts:
Expand Down
32 changes: 25 additions & 7 deletions runtimes/web3-storage-local/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,30 +13,48 @@ pub mod system_parachain {
pub mod consensus {
use frame_support::weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight};

/// How many parachain blocks are processed by the relay chain per parent. Limits the
/// number of blocks authored per slot.
pub const BLOCK_PROCESSING_VELOCITY: u32 = 1;
/// Relay chain slot duration, in milliseconds.
/// How many parachain blocks are processed by the relay chain per parent. With 3 cores
/// assigned to the parachain and 2 s parachain blocks against a 6 s relay slot, each relay
/// slot includes 3 parachain candidates.
pub const BLOCK_PROCESSING_VELOCITY: u32 = 3;
/// Relay chain slot duration, in milliseconds. The relay chain is independent of the
/// parachain block time and continues to produce a slot every 6 s.
pub const RELAY_CHAIN_SLOT_DURATION_MILLIS: u32 = 6000;

/// Average expected block time targeted by the parachain. Picked up by `pallet_timestamp` and
/// `pallet_aura`.
pub const MILLISECS_PER_BLOCK: u64 = 6000;
pub const MILLISECS_PER_BLOCK: u64 = 2000;

/// Slot duration equals block time for this runtime.
pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK;

/// 2 seconds of compute with a 6 second average block.
/// Required by slot-based authoring with elastic scaling.
pub const RELAY_PARENT_OFFSET: u32 = 1;

/// 2 seconds of compute per parachain block (one relay-chain core).
pub const MAXIMUM_BLOCK_WEIGHT: Weight = Weight::from_parts(
WEIGHT_REF_TIME_PER_SECOND.saturating_mul(2),
cumulus_primitives_core::relay_chain::MAX_POV_SIZE as u64,
);

/// Parameters enabling async backing functionality.
pub mod async_backing {
use super::{BLOCK_PROCESSING_VELOCITY, RELAY_PARENT_OFFSET};

/// Maximum number of blocks simultaneously accepted by the Runtime, not yet included
/// into the relay chain.
pub const UNINCLUDED_SEGMENT_CAPACITY: u32 = 3;
///
/// Sized in relay slots, then converted to parachain blocks. A candidate takes ~2 relay
/// slots to be backed and included under async backing; `RELAY_PARENT_OFFSET` adds the
/// extra relay parents the offset lets us build against, and `+ 1` is the in-flight slot.
/// Multiplying by `BLOCK_PROCESSING_VELOCITY` turns relay slots into parachain blocks.
/// With offset 1 and velocity 3 this is `(3 + 1) * 3 = 12`.
pub const UNINCLUDED_SEGMENT_CAPACITY: u32 =
(RELAY_SLOTS_OF_CAPACITY + RELAY_PARENT_OFFSET) * BLOCK_PROCESSING_VELOCITY;

/// Relay slots of unincluded data to buffer, before accounting for the relay-parent
/// offset: 2 slots for the backing+inclusion pipeline plus 1 in-flight slot.
const RELAY_SLOTS_OF_CAPACITY: u32 = 3;
}
}

Expand Down
10 changes: 6 additions & 4 deletions runtimes/web3-storage-local/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ pub use pallet_storage_provider;
use constants::{
consensus::{
async_backing::UNINCLUDED_SEGMENT_CAPACITY, BLOCK_PROCESSING_VELOCITY,
MAXIMUM_BLOCK_WEIGHT, RELAY_CHAIN_SLOT_DURATION_MILLIS, SLOT_DURATION,
MAXIMUM_BLOCK_WEIGHT, RELAY_CHAIN_SLOT_DURATION_MILLIS, RELAY_PARENT_OFFSET, SLOT_DURATION,
},
currency::{EXISTENTIAL_DEPOSIT, MICROUNIT},
system::{AVERAGE_ON_INITIALIZE_RATIO, NORMAL_DISPATCH_RATIO},
Expand Down Expand Up @@ -174,7 +174,9 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
spec_name: Cow::Borrowed("web3-storage-parachain"),
impl_name: Cow::Borrowed("web3-storage-parachain"),
authoring_version: 1,
spec_version: 3,
// 3 on dev (relay-clock migration lineage); 4 for 2 s blocks / 3 cores
// (slot-based authoring, RelayParentOffset = 1).
spec_version: 4,
impl_version: 0,
apis: RUNTIME_API_VERSIONS,
transaction_version: 1,
Expand Down Expand Up @@ -344,7 +346,7 @@ impl cumulus_pallet_parachain_system::Config for Runtime {
type CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases;
type ConsensusHook = ConsensusHook;
type WeightInfo = weights::cumulus_pallet_parachain_system::WeightInfo<Runtime>;
type RelayParentOffset = ConstU32<0>;
type RelayParentOffset = ConstU32<RELAY_PARENT_OFFSET>;
}

impl parachain_info::Config for Runtime {}
Expand Down Expand Up @@ -742,7 +744,7 @@ pallet_revive::impl_runtime_apis_plus_revive_traits!(

impl cumulus_primitives_core::RelayParentOffsetApi<Block> for Runtime {
fn relay_parent_offset() -> u32 {
0
RELAY_PARENT_OFFSET
}
}

Expand Down
13 changes: 7 additions & 6 deletions runtimes/web3-storage-paseo/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ pub use pallet_storage_provider;
use paseo_constants::{
consensus::{
async_backing::UNINCLUDED_SEGMENT_CAPACITY, BLOCK_PROCESSING_VELOCITY,
MAXIMUM_BLOCK_WEIGHT, RELAY_CHAIN_SLOT_DURATION_MILLIS, SLOT_DURATION,
MAXIMUM_BLOCK_WEIGHT, RELAY_CHAIN_SLOT_DURATION_MILLIS, RELAY_PARENT_OFFSET, SLOT_DURATION,
},
currency::{EXISTENTIAL_DEPOSIT, MICROUNIT},
system::{AVERAGE_ON_INITIALIZE_RATIO, NORMAL_DISPATCH_RATIO},
Expand Down Expand Up @@ -180,9 +180,10 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
// Encodes the runtime semver: major * 1_000_000 + minor * 1_000 + patch.
// 0.4.1 -> 4_001 on dev; 4_002 for the breaking Challenges storage reshape
// (Vec -> StorageDoubleMap); 4_003 for dropping the vestigial
// `ChallengerStatRecord::total_earnings` field. Must stay > the deployed
// value so the upgrade is accepted and migrations run.
spec_version: 4_003,
// `ChallengerStatRecord::total_earnings` field; 4_004 for 2 s blocks /
// 3 cores (slot-based authoring, RelayParentOffset = 1). Must stay > the
// deployed value so the upgrade is accepted and migrations run.
spec_version: 4_004,
impl_version: 0,
apis: RUNTIME_API_VERSIONS,
transaction_version: 2,
Expand Down Expand Up @@ -353,7 +354,7 @@ impl cumulus_pallet_parachain_system::Config for Runtime {
type CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases;
type ConsensusHook = ConsensusHook;
type WeightInfo = weights::cumulus_pallet_parachain_system::WeightInfo<Runtime>;
type RelayParentOffset = ConstU32<0>;
type RelayParentOffset = ConstU32<RELAY_PARENT_OFFSET>;
}

impl parachain_info::Config for Runtime {}
Expand Down Expand Up @@ -751,7 +752,7 @@ pallet_revive::impl_runtime_apis_plus_revive_traits!(

impl cumulus_primitives_core::RelayParentOffsetApi<Block> for Runtime {
fn relay_parent_offset() -> u32 {
0
RELAY_PARENT_OFFSET
}
}

Expand Down
32 changes: 25 additions & 7 deletions runtimes/web3-storage-paseo/src/paseo_constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,30 +18,48 @@ pub use paseo_runtime_constants::system_parachain;
pub mod consensus {
use frame_support::weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight};

/// How many parachain blocks are processed by the relay chain per parent. Limits the
/// number of blocks authored per slot.
pub const BLOCK_PROCESSING_VELOCITY: u32 = 1;
/// Relay chain slot duration, in milliseconds.
/// How many parachain blocks are processed by the relay chain per parent. With 3 cores
/// assigned to the parachain and 2 s parachain blocks against a 6 s relay slot, each relay
/// slot includes 3 parachain candidates.
pub const BLOCK_PROCESSING_VELOCITY: u32 = 3;
/// Relay chain slot duration, in milliseconds. The relay chain is independent of the
/// parachain block time and continues to produce a slot every 6 s.
pub const RELAY_CHAIN_SLOT_DURATION_MILLIS: u32 = 6000;

/// Average expected block time targeted by the parachain. Picked up by `pallet_timestamp` and
/// `pallet_aura`.
pub const MILLISECS_PER_BLOCK: u64 = 6000;
pub const MILLISECS_PER_BLOCK: u64 = 2000;

/// Slot duration equals block time for this runtime.
pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK;

/// 2 seconds of compute with a 6 second average block.
/// Required by slot-based authoring with elastic scaling.
pub const RELAY_PARENT_OFFSET: u32 = 1;

/// 2 seconds of compute per parachain block (one relay-chain core).
pub const MAXIMUM_BLOCK_WEIGHT: Weight = Weight::from_parts(
WEIGHT_REF_TIME_PER_SECOND.saturating_mul(2),
cumulus_primitives_core::relay_chain::MAX_POV_SIZE as u64,
);

/// Parameters enabling async backing functionality.
pub mod async_backing {
use super::{BLOCK_PROCESSING_VELOCITY, RELAY_PARENT_OFFSET};

/// Maximum number of blocks simultaneously accepted by the Runtime, not yet included
/// into the relay chain.
pub const UNINCLUDED_SEGMENT_CAPACITY: u32 = 3;
///
/// Sized in relay slots, then converted to parachain blocks. A candidate takes ~2 relay
/// slots to be backed and included under async backing; `RELAY_PARENT_OFFSET` adds the
/// extra relay parents the offset lets us build against, and `+ 1` is the in-flight slot.
/// Multiplying by `BLOCK_PROCESSING_VELOCITY` turns relay slots into parachain blocks.
/// With offset 1 and velocity 3 this is `(3 + 1) * 3 = 12`.
pub const UNINCLUDED_SEGMENT_CAPACITY: u32 =
(RELAY_SLOTS_OF_CAPACITY + RELAY_PARENT_OFFSET) * BLOCK_PROCESSING_VELOCITY;

/// Relay slots of unincluded data to buffer, before accounting for the relay-parent
/// offset: 2 slots for the backing+inclusion pipeline plus 1 in-flight slot.
const RELAY_SLOTS_OF_CAPACITY: u32 = 3;
}
}

Expand Down
4 changes: 2 additions & 2 deletions scripts/runtimes-matrix.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"integration_tests": true,
"try_runtime": {
"spec_name_check": "--disable-spec-name-check",
"extra_flags": "--blocktime 6000 --disable-spec-version-check"
"extra_flags": "--blocktime 2000 --disable-spec-version-check"
},
"benchmarks_templates": {
"pallet_xcm_benchmarks::generic": "templates/xcm-bench-template.hbs",
Expand All @@ -32,7 +32,7 @@
"integration_tests": true,
"try_runtime": {
"spec_name_check": "--disable-spec-name-check",
"extra_flags": "--blocktime 6000 --disable-spec-version-check"
"extra_flags": "--blocktime 2000 --disable-spec-version-check"
},
"benchmarks_templates": {
"pallet_xcm_benchmarks::generic": "templates/xcm-bench-template.hbs",
Expand Down
32 changes: 31 additions & 1 deletion zombienet.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ default_command = "{{PROJECT_ROOT}}/.bin/polkadot"
chain = "westend-local"
default_args = ["-lruntime=info"]

[relaychain.genesis.runtimeGenesis.patch.configuration.config.scheduler_params]
num_cores = 3

[relaychain.genesis.runtimeGenesis.patch.configuration.config.async_backing_params]
allowed_ancestry_len = 6
max_candidate_depth = 6

[[relaychain.nodes]]
name = "alice"
validator = true
Expand All @@ -18,14 +25,37 @@ name = "bob"
validator = true
rpc_port = 9901

[[relaychain.nodes]]
name = "charlie"
validator = true

[[relaychain.nodes]]
name = "dave"
validator = true

[[relaychain.nodes]]
name = "eve"
validator = true

[[relaychain.nodes]]
name = "ferdie"
validator = true

[[parachains]]
id = 4000
cumulus_based = true
num_cores = 3
chain_spec_command = "{{PROJECT_ROOT}}/scripts/build-chain-spec.sh"

[[parachains.collators]]
name = "alice"
validator = true
command = "{{PROJECT_ROOT}}/.bin/polkadot-omni-node"
rpc_port = 2222
args = ["--collator", "-lruntime=info"]
args = ["--authoring=slot-based", "--collator", "-lruntime=info"]

[[parachains.collators]]
name = "bob"
validator = true
command = "{{PROJECT_ROOT}}/.bin/polkadot-omni-node"
args = ["--authoring=slot-based", "--collator", "-lruntime=info"]
Loading
Loading