Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 9 additions & 4 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ pub enum Commands {
/// If provided we will _bite_ the live network at the supplied block hieght
#[arg(long = "rc-bite-at", verbatim_doc_comment)]
relay_bite_at: Option<u32>,
/// Parachains to include: asset-hub, coretime, people, bridge-hub, collectives (comma-separated)
/// Parachains to include: asset-hub, coretime, people, bridge-hub, collectives, bulletin (comma-separated)
#[arg(long, short = 'p', value_delimiter = ',', verbatim_doc_comment)]
parachains: Option<Vec<String>>,
/// Base path to use. if not provided we will check the env 'ZOMBIE_BITE_BASE_PATH' and if not present we will use `<cwd>_timestamp`
Expand Down Expand Up @@ -74,7 +74,7 @@ pub enum Commands {
/// [Helper] Generate artifacts to be used by the next step (only 'spawn' and 'post' allowed)
GenerateArtifacts {
/// The network will be using for bite (will try the network + ah)
#[arg(short = 'r', long = "rc", value_parser = clap::builder::PossibleValuesParser::new(["polkadot", "kusame", "paseo"]), default_value="polkadot")]
#[arg(short = 'r', long = "rc", value_parser = clap::builder::PossibleValuesParser::new(["polkadot", "kusama", "paseo"]), default_value="polkadot")]
relay: String,
/// Base path to use. if not provided we will check the env 'ZOMBIE_BITE_BASE_PATH' and if not present we will use `<cwd>_timestamp`
#[arg(long, short = 'd', verbatim_doc_comment)]
Expand All @@ -86,7 +86,7 @@ pub enum Commands {
/// [Helper] Clean up directory to only include the needed artifacts
CleanUpDir {
/// The network will be using for bite (will try the network + ah)
#[arg(short = 'r', long = "rc", value_parser = clap::builder::PossibleValuesParser::new(["polkadot", "kusame", "paseo"]), default_value="polkadot")]
#[arg(short = 'r', long = "rc", value_parser = clap::builder::PossibleValuesParser::new(["polkadot", "kusama", "paseo"]), default_value="polkadot")]
relay: String,
/// Base path to use. if not provided we will check the env 'ZOMBIE_BITE_BASE_PATH' and if not present we will use `<cwd>_timestamp`
#[arg(long, short = 'd', verbatim_doc_comment)]
Expand Down Expand Up @@ -215,10 +215,15 @@ pub fn resolve_bite_config(
maybe_bite_at: None,
maybe_rpc_endpoint: None,
}),
"bulletin" => Some(Parachain::Bulletin {
maybe_override: None,
maybe_bite_at: None,
maybe_rpc_endpoint: None,
}),
unknown => {
warn!(
"⚠️ Warning: Unknown parachain '{}' will be ignored.
Valid options are: asset-hub, coretime, people, bridge-hub, collectives",
Valid options are: asset-hub, coretime, people, bridge-hub, collectives, bulletin",
unknown
);
None
Expand Down
70 changes: 65 additions & 5 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,15 +214,15 @@ impl Relaychain {
String::from(match self {
Relaychain::Polkadot { .. } => "wss://rpc.polkadot.io",
Relaychain::Kusama { .. } => "wss://kusama-rpc.polkadot.io",
Relaychain::Paseo { .. } => "wss://paseo-rpc.dwellir.com",
Relaychain::Paseo { .. } => "wss://paseo-rpc.n.dwellir.com",
})
}

pub fn rpc_endpoint(&self) -> String {
String::from(match self {
Relaychain::Polkadot { .. } => "wss://rpc.polkadot.io",
Relaychain::Kusama { .. } => "wss://kusama-rpc.polkadot.io",
Relaychain::Paseo { .. } => "wss://paseo-rpc.dwellir.com",
Relaychain::Paseo { .. } => "wss://paseo-rpc.n.dwellir.com",
})
}

Expand Down Expand Up @@ -282,6 +282,11 @@ pub enum Parachain {
maybe_bite_at: MaybeByteAt,
maybe_rpc_endpoint: MaybeSyncUrl,
},
Bulletin {
maybe_override: MaybeWasmOverridePath,
maybe_bite_at: MaybeByteAt,
maybe_rpc_endpoint: MaybeSyncUrl,
},
}

impl Parachain {
Expand All @@ -302,6 +307,11 @@ impl Parachain {
maybe_bite_at: None,
maybe_rpc_endpoint: None,
},
"bulletin" => Parachain::Bulletin {
maybe_override: None,
maybe_bite_at: None,
maybe_rpc_endpoint: None,
},
_ => Parachain::AssetHub {
maybe_override: None,
maybe_bite_at: None,
Expand All @@ -317,6 +327,7 @@ impl Parachain {
Parachain::People { .. } => "people",
Parachain::BridgeHub { .. } => "bridge-hub",
Parachain::Collectives { .. } => "collectives",
Parachain::Bulletin { .. } => "bulletin",
};

format!("{para_part}-{relay_part}-local")
Expand All @@ -329,6 +340,7 @@ impl Parachain {
Parachain::People { .. } => "people",
Parachain::BridgeHub { .. } => "bridge-hub",
Parachain::Collectives { .. } => "collectives",
Parachain::Bulletin { .. } => "bulletin",
};

format!("{para_part}-{relay_part}")
Expand All @@ -345,6 +357,7 @@ impl Parachain {
Parachain::People { .. } => 1004,
Parachain::BridgeHub { .. } => 1002,
Parachain::Collectives { .. } => 1001,
Parachain::Bulletin { .. } => 5118,
}
}

Expand All @@ -354,7 +367,8 @@ impl Parachain {
| Parachain::Coretime { maybe_override, .. }
| Parachain::People { maybe_override, .. }
| Parachain::BridgeHub { maybe_override, .. }
| Parachain::Collectives { maybe_override, .. } => maybe_override.as_deref(),
| Parachain::Collectives { maybe_override, .. }
| Parachain::Bulletin { maybe_override, .. } => maybe_override.as_deref(),
}
}

Expand All @@ -364,7 +378,8 @@ impl Parachain {
| Parachain::Coretime { maybe_bite_at, .. }
| Parachain::People { maybe_bite_at, .. }
| Parachain::BridgeHub { maybe_bite_at, .. }
| Parachain::Collectives { maybe_bite_at, .. } => *maybe_bite_at,
| Parachain::Collectives { maybe_bite_at, .. }
| Parachain::Bulletin { maybe_bite_at, .. } => *maybe_bite_at,
}
}

Expand All @@ -384,6 +399,9 @@ impl Parachain {
}
| Parachain::Collectives {
maybe_rpc_endpoint, ..
}
| Parachain::Bulletin {
maybe_rpc_endpoint, ..
} => maybe_rpc_endpoint.as_deref(),
}
}
Expand Down Expand Up @@ -467,6 +485,7 @@ pub fn generate_network_config(
Parachain::People { .. } => ("people", para.id()),
Parachain::BridgeHub { .. } => ("bridge-hub", para.id()),
Parachain::Collectives { .. } => ("collectives", para.id()),
Parachain::Bulletin { .. } => ("bulletin", para.id()),
};
let chain = format!("{}-{}",chain_part, relay_chain);

Expand Down Expand Up @@ -559,6 +578,11 @@ impl ParachainConfig {
maybe_bite_at: self.bite_at,
maybe_rpc_endpoint: self.rpc_endpoint.clone(),
}),
"bulletin" => Some(Parachain::Bulletin {
maybe_override: self.runtime_override.clone(),
maybe_bite_at: self.bite_at,
maybe_rpc_endpoint: self.rpc_endpoint.clone(),
}),
_ => None,
}
} else {
Expand Down Expand Up @@ -710,7 +734,14 @@ mod test {

#[test]
fn all_parachain_types_supported() {
let types = vec!["asset-hub", "coretime", "people", "bridge-hub"];
let types = vec![
"asset-hub",
"coretime",
"people",
"bridge-hub",
"collectives",
"bulletin",
];

for parachain_type in types {
let config = ParachainConfig {
Expand Down Expand Up @@ -767,6 +798,15 @@ mod test {
.id(),
1002
);
assert_eq!(
Parachain::Bulletin {
maybe_override: None,
maybe_bite_at: None,
maybe_rpc_endpoint: None
}
.id(),
5118
);
}

#[test]
Expand Down Expand Up @@ -809,6 +849,16 @@ mod test {
.as_chain_string(relay),
"bridge-hub-polkadot"
);
let paseo = "paseo";
assert_eq!(
Parachain::Bulletin {
maybe_override: None,
maybe_bite_at: None,
maybe_rpc_endpoint: None
}
.as_chain_string(paseo),
"bulletin-paseo"
);
}

#[test]
Expand Down Expand Up @@ -851,6 +901,16 @@ mod test {
.as_local_chain_string(relay),
"bridge-hub-kusama-local"
);
let paseo = "paseo";
assert_eq!(
Parachain::Bulletin {
maybe_override: None,
maybe_bite_at: None,
maybe_rpc_endpoint: None
}
.as_local_chain_string(paseo),
"bulletin-paseo-local"
);
}

#[test]
Expand Down
67 changes: 54 additions & 13 deletions src/overrides.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,12 +148,7 @@ pub fn generate_rc_overrides(
"",
substorager::storage_value_key(&b"Hrmp"[..], b"HrmpIngressChannelsIndex"),
);
let core_descriptor_prefix = array_bytes::bytes2hex(
"",
substorager::storage_value_key(&b"CoretimeAssignmentProvider"[..], b"CoreDescriptors"),
);
for (index, para) in paras.iter().enumerate() {
let index: u32 = index.try_into().expect("Index should be valid u32");
for para in paras.iter() {
let para_id = ParaId(para.id());

let para_twox64 = array_bytes::bytes2hex("", subhasher::twox64(para_id.encode()));
Expand All @@ -168,15 +163,48 @@ pub fn generate_rc_overrides(
// HRMP hrmpIngressChannelsIndex (empty for each para)
let hrmp_channels_key = format!("{hrmp_hici_prefix}{para_key_part}");
overrides[hrmp_channels_key] = json!("00");
}

// CoretimeAssignmentProvider CoreDescriptors <idx>
let core_descriptor_idx_key = format!(
"{core_descriptor_prefix}{}",
array_bytes::bytes2hex("", subhasher::twox256(index.to_le_bytes()))
);
let core_descriptor = format!("00010402{}00e100e100010000e1", para_hex);
overrides[core_descriptor_idx_key] = json!(core_descriptor);
// ParaScheduler::CoreDescriptors is a StorageValue<Vec<(CoreIndex, CoreDescriptor)>>.
// The bitten state has an entry for every para registered on the live relay (50+ on paseo),
// but `Configuration::activeConfig.num_cores` is overridden above to `paras.len()` cores.
// We rewrite the value to one (core_index, Task(para_id, ...)) pair per para in our list,
// so each kept para gets its own dedicated core. Without this, core 0 stays assigned to
// whichever live para sat in the first slot of the bitten state, and our paras never
// get backable-candidate requests.
//
// SCALE layout per (CoreIndex, CoreDescriptor) entry, matching live paseo state:
// [4 bytes core_index_u32_le][CoreDescriptor]
// CoreDescriptor for an active Task assignment to ParaId X:
// 00 queue: None
// 01 current_work: Some
// 04 assignments: Vec compact-len 1
// 02 CoreAssignment::Task variant
// <4 bytes LE> ParaId
// 00e100e100010000e1 remaining WorkState bytes (mask + assignment state + pos/step)
let core_descriptors_key = array_bytes::bytes2hex(
"",
substorager::storage_value_key(&b"ParaScheduler"[..], b"CoreDescriptors"),
);
let num_paras: u32 = paras
.len()
.try_into()
.expect("The number of paras needs to fit into a u32.");
// Compact-encode the vec length; we only support up to 63 paras here (single-byte form).
assert!(
num_paras < 64,
"more than 63 parachains is not supported by the CoreDescriptors override"
);
let mut core_descriptors_value = format!("{:02x}", (num_paras as u8) << 2);
for (idx, para) in paras.iter().enumerate() {
let core_idx: u32 = idx.try_into().expect("Index should fit in u32");
let para_hex = array_bytes::bytes2hex("", ParaId(para.id()).encode());
let core_idx_hex = array_bytes::bytes2hex("", core_idx.to_le_bytes());
core_descriptors_value.push_str(&format!(
"{core_idx_hex}00010402{para_hex}00e100e100010000e1"
));
}
overrides[core_descriptors_key] = json!(core_descriptors_value);

overrides
}
Expand Down Expand Up @@ -311,6 +339,19 @@ pub async fn generate_default_overrides_for_para(
"15464cac3378d46f113cd5b7a4d71c84476f594316a7dfe49c1f352d95abdaf1": "01000000"
});

// Bulletin's pallet-bulletin-transaction-storage::on_finalize asserts that a per-block
// `apply_block_inherents` storage-proof inherent ran (via `ProofChecked`), or that the
// target block (n - RetentionPeriod) is zero / has no stored transactions. The generic
// doppelganger-parachain (omni-node) doesn't supply that inherent, so we sidestep the
// assert by overriding `RetentionPeriod` to u32::MAX — `n.saturating_sub(period)` then
// saturates to 0, the `is_zero()` branch short-circuits, and `on_finalize` passes.
// Storage key = twox128("TransactionStorage") + twox128("RetentionPeriod"); value is
// SCALE-encoded u32 LE (0xffffffff).
if let Parachain::Bulletin { .. } = para {
overrides["0e7b504e5df47062be129a8958a7a1278d69b77f53c8c31f3b84d472fdb7de2b"] =
json!("ffffffff");
}

if let Some(override_wasm) = para.wasm_overrides() {
let wasm_content = fs::read(override_wasm)
.await
Expand Down
26 changes: 18 additions & 8 deletions src/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ use zombienet_support::net::wait_ws_ready;

const PASEO_ASSET_HUB_SPEC_URL: &str =
"https://paseo-r2.zondax.ch/chain-specs/paseo-asset-hub.json";
const BULLETIN_PASEO_SPEC_URL: &str =
"https://raw.githubusercontent.com/paritytech/chainspecs/main/paseo/parachain/bulletin/raw.json";

fn maybe_hosted_spec_url(chain: &str) -> Option<&'static str> {
match chain {
"asset-hub-paseo" => Some(PASEO_ASSET_HUB_SPEC_URL),
"bulletin-paseo" => Some(BULLETIN_PASEO_SPEC_URL),
_ => None,
}
}

#[allow(clippy::too_many_arguments)]
pub async fn sync_relay_only(
Expand Down Expand Up @@ -139,17 +149,17 @@ pub async fn sync_para(

trace!("env: {env:?}");

let dest_for_paseo = format!("{}/asset-hub-paseo.json", ns.base_dir().to_string_lossy(),);
let chain_arg = if chain == "asset-hub-paseo" {
// get chain spec from https://paseo-r2.zondax.ch/chain-specs/paseo-asset-hub.json
let response = reqwest::get(PASEO_ASSET_HUB_SPEC_URL)
let hosted_spec_dest = format!("{}/{}.json", ns.base_dir().to_string_lossy(), &chain);
let chain_arg = if let Some(url) = maybe_hosted_spec_url(chain.as_str()) {
info!("📥 Downloading chain spec for {chain} from {url}");
let response = reqwest::get(url)
.await
.unwrap_or_else(|_| panic!("Create file {dest_for_paseo} should work"));
let mut file = std::fs::File::create(&dest_for_paseo)
.unwrap_or_else(|_| panic!("Create file {dest_for_paseo} should work"));
.unwrap_or_else(|_| panic!("Fetch chain spec from {url} should work"));
let mut file = std::fs::File::create(&hosted_spec_dest)
.unwrap_or_else(|_| panic!("Create file {hosted_spec_dest} should work"));
let mut content = Cursor::new(response.bytes().await.expect("Create cursor should works."));
std::io::copy(&mut content, &mut file).expect("Copy bytes should works.");
dest_for_paseo.as_str()
hosted_spec_dest.as_str()
} else {
chain.as_ref()
};
Expand Down
Loading