Skip to content
Closed
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
15 changes: 15 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ pub enum Commands {
/// Db to use
#[arg(long, value_parser = clap::builder::PossibleValuesParser::new(["rocksdb", "paritydb"]), default_value="rocksdb", verbatim_doc_comment)]
database: String,
/// Number of cores to assign per parachain after spawn (only used with --and-spawn)
#[arg(long, default_value_t = 1, verbatim_doc_comment)]
cores: u32,
},
/// Spawn a new instance of the network from the bite step.
Spawn {
Expand Down Expand Up @@ -135,6 +138,7 @@ pub struct ResolvedBiteConfig {
pub parachains: Vec<Parachain>,
pub base_path: PathBuf,
pub and_spawn: bool,
pub cores: u32,
}

#[derive(Debug)]
Expand All @@ -153,6 +157,7 @@ pub fn resolve_bite_config(
base_path: Option<String>,
rc_sync_url: Option<String>,
and_spawn: bool,
cores: u32,
) -> Result<ResolvedBiteConfig, anyhow::Error> {
// Load config file if provided
let config_file = if let Some(path) = config_path {
Expand Down Expand Up @@ -275,11 +280,21 @@ pub fn resolve_bite_config(
and_spawn
};

// Resolve cores (CLI overrides config file)
let resolved_cores = if cores > 1 {
cores
} else if let Some(ref config) = config_file {
config.cores.unwrap_or(cores)
} else {
cores
};

Ok(ResolvedBiteConfig {
relaychain,
parachains: resolved_parachains,
base_path: resolved_base_path,
and_spawn: resolved_and_spawn,
cores: resolved_cores,
})
}

Expand Down
6 changes: 5 additions & 1 deletion src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -517,7 +517,8 @@ pub fn generate_network_config(
let col_builder = c.with_name(&collator_name)
.with_args(vec![
("-l", "aura=debug,runtime=trace,cumulus-consensus=trace,consensus::common=trace,parachain::collation-generation=trace,parachain::collator-protocol=trace,parachain=debug,basic-authorship=trace").into(),
"--force-authoring".into()
"--force-authoring".into(),
"--authoring=slot-based".into(),
]);
if let Ok(port) = env::var("ZOMBIE_BITE_AH_PORT") {
let rpc_port = port.parse().expect("env var ZOMBIE_BITE_AH_PORT must be a valid u16");
Expand Down Expand Up @@ -547,6 +548,7 @@ pub struct ZombieBiteConfig {
pub base_path: Option<String>,
pub and_spawn: Option<bool>,
pub with_monitor: Option<bool>,
pub cores: Option<u32>,
}

#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
Expand Down Expand Up @@ -1035,6 +1037,7 @@ mod test {
base_path: None,
and_spawn: None,
with_monitor: None,
cores: None,
};

assert_eq!(config.get_parachains().len(), 0);
Expand Down Expand Up @@ -1084,6 +1087,7 @@ mod test {
base_path: None,
and_spawn: None,
with_monitor: None,
cores: None,
};

let parachains = config.get_parachains();
Expand Down
18 changes: 13 additions & 5 deletions src/doppelganger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ pub async fn doppelganger_inner(
relay_chain: Relaychain,
paras_to: Vec<Parachain>,
database: &str,
cores_per_para: u32,
) -> Result<(), anyhow::Error> {
// Star the node and wait until finish (with temp dir managed by us)
info!(
Expand Down Expand Up @@ -206,7 +207,8 @@ pub async fn doppelganger_inner(
}

let rc_default_overrides_path =
generate_default_overrides_for_rc(&base_dir_str, &relay_chain, &paras_to).await;
generate_default_overrides_for_rc(&base_dir_str, &relay_chain, &paras_to, cores_per_para)
.await;
let rc_info_path = format!("{base_dir_str}/rc_info.txt");
// RELAYCHAIN sync

Expand Down Expand Up @@ -287,6 +289,7 @@ pub async fn doppelganger_inner(
para_artifacts,
Some(global_base_dir.clone()),
database,
cores_per_para,
)
.await
.map_err(|e| anyhow!(e.to_string()))?;
Expand Down Expand Up @@ -552,6 +555,7 @@ async fn generate_config(
paras: Vec<ChainArtifact>,
global_base_dir: Option<PathBuf>,
database: &str,
cores_per_para: u32,
) -> Result<NetworkConfig, String> {
let leaked_rust_log = env::var("RUST_LOG_RC").unwrap_or_else(|_| {
String::from(
Expand Down Expand Up @@ -613,8 +617,9 @@ async fn generate_config(
get_random_port().await
};

// Alice + Bob + number of parachains
let num_validators = (2 + paras.len()).min(7);
// Need enough validators to back candidates on all cores (1 per core minimum)
// Capped at 7 (max predefined validator keys)
let num_validators = std::cmp::max(2 + paras.len(), cores_per_para as usize + 1).min(7);

// config a new network with dynamic validators
let mut config = NetworkConfigBuilder::new().with_relaychain(|r| {
Expand All @@ -625,6 +630,7 @@ async fn generate_config(
"--no-hardware-benchmarks".into(),
("--state-pruning", get_state_pruning_config().as_str()).into(),
("--database", database).into(),
"--unsafe-rpc-external".into(),
];

if let Ok(extra_args) = env::var("ZOMBIE_BITE_RC_EXTRA_ARGS") {
Expand Down Expand Up @@ -711,7 +717,7 @@ async fn generate_config(
let mut para_default_args = vec![
(
"--relay-chain-rpc-urls",
format!("ws://127.0.0.1:{rpc_alice_port}").as_str(),
format!("ws://0.0.0.0:{rpc_alice_port}").as_str(),
)
.into(),
("-l", para_leaked_rust_log.as_str()).into(),
Expand All @@ -721,6 +727,8 @@ async fn generate_config(
"--no-hardware-benchmarks".into(),
("--state-pruning", get_state_pruning_config().as_str()).into(),
("--database", database).into(),
"--authoring=slot-based".into(),
"--unsafe-rpc-external".into(),
];

if let Ok(extra_args) = env::var("ZOMBIE_BITE_AH_EXTRA_ARGS") {
Expand Down Expand Up @@ -994,7 +1002,7 @@ mod test {
para_id: Some(1000),
};

let network_config = generate_config(relay, vec![ah], None, "rocksdb")
let network_config = generate_config(relay, vec![ah], None, "rocksdb", 1)
.await
.unwrap();

Expand Down
3 changes: 3 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ async fn main() -> Result<(), anyhow::Error> {
and_spawn,
with_monitor,
database,
cores,
} => {
if with_monitor && !and_spawn {
error!("--with-monitor can only be used with --and-spawn");
Expand All @@ -174,6 +175,7 @@ async fn main() -> Result<(), anyhow::Error> {
base_path,
rc_sync_url,
and_spawn,
cores,
)?;

debug!("{:?}", resolved_config.relaychain);
Expand All @@ -182,6 +184,7 @@ async fn main() -> Result<(), anyhow::Error> {
resolved_config.relaychain,
resolved_config.parachains,
&database,
resolved_config.cores,
)
.await
.expect("bite should work");
Expand Down
100 changes: 77 additions & 23 deletions src/overrides.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,19 @@ fn generate_next_keys_injects(
}

/// Generate the storage overrides for relay chain validators
/// Returns (overrides, injects) tuple. Overrides replace existing keys,
/// injects add keys that may not exist in the original state.
pub fn generate_rc_overrides(
validator_keys: &[&crate::utils::ValidatorKeys],
paras: &[&Parachain],
) -> serde_json::Value {
cores_per_para: u32,
) -> (serde_json::Value, serde_json::Value) {
let num_validators = validator_keys.len();
let num_cores: u32 = paras
let num_paras: u32 = paras
.len()
.try_into()
.expect("The number of paras needs to fit into a u32.");
let num_cores: u32 = num_paras * cores_per_para;

// Build stash list for validators (concatenated hex)
let stash_list: String = validator_keys
Expand Down Expand Up @@ -120,8 +124,6 @@ pub fn generate_rc_overrides(
"cd710b30bd2eab0352ddcc26417aa1940b76934f4cc08dee01012d059e1b83ee": paras_parachains,
// paraScheduler validatorGroup (dynamic groups based on validator count)
"94eadf0156a8ad5156507773d0471e4a16973e1142f5bd30d9464076794007db": format!("{}{}", validator_groups_count_hex, validator_groups),
// paraScheduler claimQueue (empty, will auto-fill)
"94eadf0156a8ad5156507773d0471e4a49f6c9aa90c04982c05388649310f22f": "040000000000",
// paraShared activeValidatorIndices (dynamic)
"b341e3a63e58a188839b242d17f8c9f82586833f834350b4d435d5fd269ecc8b": format!("{}{}", validator_count_hex, validator_indices),
// paraShared activeValidatorKeys (dynamic)
Expand All @@ -130,11 +132,8 @@ pub fn generate_rc_overrides(
"2099d7f109d6e535fb000bba623fd4409f99a2ce711f3a31b2fc05604c93f179": format!("{}{}", validator_count_hex, authority_discovery_keys),
// authorityDiscovery nextKeys (dynamic)
"2099d7f109d6e535fb000bba623fd4404c014e6bf8b8c2c011e7290b85696bb3": format!("{}{}", validator_count_hex, authority_discovery_keys),
// Configuration activeConfig, {x} cores and node_features [1101]
"06de3d8a54d27e44a9d5ce189618f22db4b49d95320d9021994c850f25b8e385": format!("0000300000500000aaaa020000001000fbff0000100000000a000000403800005802000003000000020000000000500000c800008000000000e8764817000000000000000000000000e87648170000000000000000000000e80300000090010080000000009001000c01002000000600c4090000000000000601983a000000000000403800000006000000580200000300000019000000000000000200000002000000020000001400000001000000100b010000001400000004000000010500000001000000{}00000000f401000080b2e60e80c3c90180b2e60e00000000000000000000000005000000", array_bytes::bytes2hex("", num_cores.encode())),
// TODO: is this needed?
// paraScheduler availabilityCores (1 core, free)
"94eadf0156a8ad5156507773d0471e4ab8ebad86f546c7e0b135a4212aace339": "0400",
// Configuration activeConfig (from real Kusama, with max_validators_per_core=None, minimum_backing_votes=1)
"06de3d8a54d27e44a9d5ce189618f22db4b49d95320d9021994c850f25b8e385": format!("0000300000500000aaaa0a0000004000fbff0000800000000a000000100e00005802000006000000020000000000a00000c800001e000000005039278c0400000000000000000000005039278c040000000000000000000019000000009001001e000000009001000c01002000000600c4090000000000000601983a0000000000008070000001bc0200000600000058020000030000002b010000000000001e00000006000000020000001400000001000000100b060000000a0000000a0000000005000000{num_cores}00000000f401000080b2e60e80c3c90100f2052a01000000000000000000000005000000", num_cores = array_bytes::bytes2hex("", num_cores.encode())),
// Sudo Key (Alice)
"5c0d1176a568c1f92944340dbfed9e9c530ebca703c85910e7164cb7d1c9e47b": "d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d",
});
Expand All @@ -152,8 +151,26 @@ pub fn generate_rc_overrides(
"",
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");

// CoreDescriptors, ClaimQueue, and AvailabilityCores go into injects
// because CoreDescriptors is a StorageMap and new core keys may not exist
// in the original forked state (overrides only replace EXISTING keys).
let mut injects = json!({});

// Build ClaimQueue and AvailabilityCores dynamically based on paras and cores
// ClaimQueue: BTreeMap<CoreIndex, VecDeque<Assignment>>
// Assignment::Bulk(ParaId) = variant 1 + para_id (u32 LE)
let mut claim_queue_hex = String::new();
// BTreeMap length (compact encoded)
claim_queue_hex.push_str(&array_bytes::bytes2hex("", codec::Compact(num_cores).encode()));

// AvailabilityCores: Vec<CoreState>
// CoreState::Free = variant 0
let mut availability_cores_hex = String::new();
availability_cores_hex.push_str(&array_bytes::bytes2hex("", codec::Compact(num_cores).encode()));

let mut core_index: u32 = 0;
for (_, para) in paras.iter().enumerate() {
let para_id = ParaId(para.id());

let para_twox64 = array_bytes::bytes2hex("", subhasher::twox64(para_id.encode()));
Expand All @@ -169,37 +186,73 @@ pub fn generate_rc_overrides(
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);
// CoretimeAssignmentProvider CoreDescriptors — assign `cores_per_para` cores to this para
// These MUST be injects because the StorageMap keys for new cores don't exist
// in the original chain state (overrides silently skip missing keys).
for _ in 0..cores_per_para {
let core_descriptor_idx_key = format!(
"{core_descriptor_prefix}{}",
array_bytes::bytes2hex("", subhasher::twox256(core_index.to_le_bytes()))
);
let core_descriptor = format!("00010402{}00e100e100000000e1", para_hex);
injects[core_descriptor_idx_key] = json!(core_descriptor);

// ClaimQueue entry: CoreIndex(core_index) => VecDeque with `lookahead` entries
// Each entry is Assignment::Bulk(para_id) = variant 1 + para_id (u32 LE)
let core_idx_hex = array_bytes::bytes2hex("", core_index.to_le_bytes());
let assignment_hex = format!("01{}", para_hex); // Assignment::Bulk = variant 1
// lookahead=5: match real Kusama config so collators at relay_parent_offset=1 find entries
let lookahead: u32 = 5;
let vec_len_hex = array_bytes::bytes2hex("", codec::Compact(lookahead).encode());
let repeated_assignments = assignment_hex.repeat(lookahead as usize);
claim_queue_hex.push_str(&format!("{core_idx_hex}{vec_len_hex}{repeated_assignments}"));

// AvailabilityCores entry: CoreState::Free = 0x00
availability_cores_hex.push_str("00");

core_index += 1;
}
}

overrides
// paraScheduler ClaimQueue (inject to guarantee it's applied)
injects["94eadf0156a8ad5156507773d0471e4a49f6c9aa90c04982c05388649310f22f"] =
json!(claim_queue_hex);

// paraScheduler AvailabilityCores (inject to guarantee it's applied)
injects["94eadf0156a8ad5156507773d0471e4ab8ebad86f546c7e0b135a4212aace339"] =
json!(availability_cores_hex);

(overrides, injects)
}

pub async fn generate_default_overrides_for_rc(
base_dir: &str,
relay: &Relaychain,
paras: &Vec<Parachain>,
cores_per_para: u32,
) -> PathBuf {
// Alice + Bob + number of parachains
let num_validators = (2 + paras.len()).min(7);
// Need enough validators to back candidates on all cores (1 validator per core minimum)
// Capped at 7 (max predefined validator keys available)
let num_validators = std::cmp::max(2 + paras.len(), cores_per_para as usize + 1).min(7);
let validator_keys = get_validator_keys(num_validators);

let next_keys_injects = generate_next_keys_injects(&validator_keys);

// Generate the rc overrides with parachains
let paras_refs: Vec<&Parachain> = paras.iter().collect();
let mut overrides = generate_rc_overrides(&validator_keys, &paras_refs);
let (mut overrides, rc_injects) = generate_rc_overrides(&validator_keys, &paras_refs, cores_per_para);

// Keys to inject (mostly storage maps that are not present in the current state)
// <Pallet> < Item>
let mut injects = next_keys_injects;

// Merge CoreDescriptor/ClaimQueue/AvailabilityCores injects
if let Value::Object(map) = rc_injects {
for (k, v) in map {
injects[k] = v;
}
}

// RcMigrator Manager (set //Alice by default)
injects["2185d18cb42ae97242af0e70e6ad689012fcd13ee43ae32cc87f798eb5ed3295"] =
json!("d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d");
Expand Down Expand Up @@ -361,6 +414,7 @@ mod test {
"/tmp",
&crate::config::Relaychain::new("polakdot"),
&paras,
1,
)
.await;
}
Expand Down Expand Up @@ -408,7 +462,7 @@ mod test {
let validator_keys = get_validator_keys(2);
let para = crate::config::Parachain::new("asset-hub");
let paras = vec![&para];
let overrides = generate_rc_overrides(&validator_keys, &paras);
let (overrides, _injects) = generate_rc_overrides(&validator_keys, &paras, 1);

// Validator Validators
assert_eq!(
Expand Down