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
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@ zombie-bite spawn -d /tmp/base_path --apply-upgrade

- `--para-cores <para_id>=<cores>` overrides how many cores a parachain gets (defaults mirror the live networks, e.g. asset-hub takes 3 for elastic scaling). The relay's validator count follows the total.
- `--keep-messaging-state` keeps the inherited HRMP/DMP state instead of clearing it. Only correct when the relay's parachains are exactly the ones being bitten, so both snapshots agree on channel heads; on a shared relay the mismatch makes cumulus panic with `HRMP head mismatch`.
- `--publish-bootnodes` writes the spawned network's own node addresses into the published chain-specs, so the artifacts are dialable from another machine (specs otherwise ship with `bootNodes: []`).

#### Bundle manifest

A bite writes a `manifest.json` next to `ready.json` recording, per chain, the bite block, source RPC, spec and snapshot files with sizes, any carried upgrade, and the `doppelganger` versions that produced the snapshots. A later `spawn` warns when the local binaries differ, because a snapshot from a newer node fails to restore in ways that otherwise look like corruption.

#### Overrides are checked against the runtime

Expand Down
14 changes: 14 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ pub enum Commands {
/// Can be set multiple times, once per para.
#[arg(long = "para-cores", verbatim_doc_comment)]
para_cores: Vec<String>,
/// Write the spawned network's own node addresses into the published
/// chain-specs, so the artifacts are dialable from outside the box.
#[arg(long, default_value_t = false, verbatim_doc_comment)]
publish_bootnodes: bool,
/// 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>,
Expand Down Expand Up @@ -102,6 +106,10 @@ pub enum Commands {
/// and wait until it enacts.
#[arg(long, default_value_t = false, verbatim_doc_comment)]
apply_upgrade: bool,
/// Write the spawned network's own node addresses into the published
/// chain-specs, so the artifacts are dialable from outside the box.
#[arg(long, default_value_t = false, verbatim_doc_comment)]
publish_bootnodes: bool,
},
/// [Helper] Generate artifacts to be used by the next step (only 'spawn' and 'post' allowed)
GenerateArtifacts {
Expand Down Expand Up @@ -166,6 +174,7 @@ pub struct ResolvedBiteConfig {
pub base_path: PathBuf,
pub and_spawn: bool,
pub apply_upgrade: bool,
pub publish_bootnodes: bool,
pub opts: BiteOptions,
}

Expand All @@ -174,6 +183,7 @@ pub struct ResolvedSpawnConfig {
pub base_path: PathBuf,
pub with_monitor: bool,
pub apply_upgrade: bool,
pub publish_bootnodes: bool,
}

#[allow(clippy::too_many_arguments)]
Expand All @@ -191,6 +201,7 @@ pub fn resolve_bite_config(
apply_upgrade: bool,
keep_messaging_state: bool,
para_cores: Vec<String>,
publish_bootnodes: bool,
) -> Result<ResolvedBiteConfig, anyhow::Error> {
// Load config file if provided
let config_file = if let Some(path) = config_path {
Expand Down Expand Up @@ -364,6 +375,7 @@ pub fn resolve_bite_config(
base_path: resolved_base_path,
and_spawn: resolved_and_spawn,
apply_upgrade: resolved_apply_upgrade,
publish_bootnodes,
opts: BiteOptions {
upgrades,
cores,
Expand All @@ -377,6 +389,7 @@ pub fn resolve_spawn_config(
base_path: Option<String>,
with_monitor: bool,
apply_upgrade: bool,
publish_bootnodes: bool,
) -> Result<ResolvedSpawnConfig, anyhow::Error> {
// Load config file if provided
let config_file = if let Some(path) = config_path {
Expand Down Expand Up @@ -413,6 +426,7 @@ pub fn resolve_spawn_config(
base_path: resolved_base_path,
with_monitor: resolved_with_monitor,
apply_upgrade: resolved_apply_upgrade,
publish_bootnodes,
})
}

Expand Down
138 changes: 136 additions & 2 deletions src/doppelganger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ use crate::utils::{
use crate::config::{
get_assigned_cores, get_state_pruning_config, BiteOptions, Context, Parachain, Relaychain, Step,
};
use crate::manifest::{self, ChainEntry, Manifest};
use crate::metadata::ChainMetadata;
use crate::overrides::{generate_default_overrides_for_para, generate_default_overrides_for_rc};
use crate::sync::{sync_para, sync_relay_only};
Expand Down Expand Up @@ -329,8 +330,8 @@ pub async fn doppelganger_inner(
};

let config = generate_config(
relay_artifacts,
para_artifacts,
relay_artifacts.clone(),
para_artifacts.clone(),
Some(global_base_dir.clone()),
database,
req_cores,
Expand Down Expand Up @@ -441,11 +442,80 @@ pub async fn doppelganger_inner(
)
.await;

let manifest = build_manifest(
&relay_chain,
&paras_to,
&ready_content,
&relay_artifacts,
&para_artifacts,
)
.await;
manifest.write(&global_base_dir).await?;

clean_up_dir_for_step(global_base_dir, Step::Bite, &relay_chain, &paras_to).await?;

Ok(())
}

async fn build_manifest(
relay_chain: &Relaychain,
paras_to: &[Parachain],
ready: &serde_json::Value,
relay_artifacts: &ChainArtifact,
para_artifacts: &[ChainArtifact],
) -> Manifest {
let file_name = |path: &str| {
Path::new(path)
.file_name()
.map(|n| n.to_string_lossy().to_string())
};

let relay = ChainEntry {
chain: relay_chain.as_chain_string(),
para_id: None,
bite_block: ready["rc_start_block"].as_u64(),
source_rpc: ready["rc_source_rpc"].as_str().map(str::to_string),
spec_file: file_name(&relay_artifacts.spec_path),
snapshot_file: file_name(&relay_artifacts.snap_path),
snapshot_bytes: manifest::file_size(&relay_artifacts.snap_path).await,
upgrade_file: ready["rc_upgrade_wasm"].as_str().map(str::to_string),
upgrade_hash: ready["rc_upgrade_hash"].as_str().map(str::to_string),
};

let mut parachains = vec![];
for (index, para) in paras_to.iter().enumerate() {
let id = para.id();
let artifact = para_artifacts.get(index);
parachains.push(ChainEntry {
chain: para.as_chain_string(&relay_chain.as_chain_string()),
para_id: Some(id),
bite_block: ready[format!("para_{id}_start_block")].as_u64(),
source_rpc: ready[format!("para_{id}_source_rpc")]
.as_str()
.map(str::to_string),
spec_file: artifact.and_then(|a| file_name(&a.spec_path)),
snapshot_file: artifact.and_then(|a| file_name(&a.snap_path)),
snapshot_bytes: match artifact {
Some(a) => manifest::file_size(&a.snap_path).await,
None => None,
},
upgrade_file: ready[format!("para_{id}_upgrade_wasm")]
.as_str()
.map(str::to_string),
upgrade_hash: ready[format!("para_{id}_upgrade_hash")]
.as_str()
.map(str::to_string),
});
}

Manifest {
created_at: manifest::now_unix(),
relay,
parachains,
binaries: manifest::binary_versions().await,
}
}

async fn copy_upgrade_blob(from: &str, base_dir: &str, blob_name: &str) -> (String, String) {
let wasm = fs::read(from)
.await
Expand Down Expand Up @@ -947,6 +1017,70 @@ async fn validate_parachain_specs(
Ok(())
}

/// Write the spawned network's own node addresses into the chain-specs the
/// artifacts are published from.
///
/// `generate_chain_spec` clears `bootNodes`, which is safe but leaves a
/// published spec unusable off the box: the peer wiring only exists in the
/// spawned nodes' arguments. Filling in the running nodes' multiaddrs makes the
/// artifacts dialable without every consumer patching the specs itself.
pub async fn publish_bootnodes(
network: &Network<LocalFileSystem>,
base_path: &Path,
step: Step,
) -> Result<(), anyhow::Error> {
let spec_dir = format!("{}/{}", base_path.to_string_lossy(), step.dir_from());

let relay_nodes: Vec<String> = network
.relaychain()
.nodes()
.iter()
.map(|node| node.multiaddr().to_string())
.collect();
write_bootnodes(
&format!("{spec_dir}/{}-spec.json", network.relaychain().chain()),
&relay_nodes,
)
.await?;

for para in network.parachains() {
let Some(chain_id) = para.chain_id() else {
warn!(
"para {}: no chain id, can't tell which spec to write bootnodes into",
para.para_id()
);
continue;
};
let nodes: Vec<String> = para
.collators()
.iter()
.map(|node| node.multiaddr().to_string())
.collect();
write_bootnodes(&format!("{spec_dir}/{chain_id}-spec.json"), &nodes).await?;
}

Ok(())
}

async fn write_bootnodes(spec_path: &str, nodes: &[String]) -> Result<(), anyhow::Error> {
if nodes.is_empty() {
warn!("{spec_path}: no running nodes to use as bootnodes");
return Ok(());
}
let Ok(content) = fs::read_to_string(spec_path).await else {
warn!("{spec_path}: chain-spec not found, skipping bootnodes");
return Ok(());
};
let mut spec: serde_json::Value = serde_json::from_str(&content)?;
spec["bootNodes"] = json!(nodes);
fs::write(spec_path, serde_json::to_string_pretty(&spec)?).await?;
info!(
"{spec_path}: bootNodes set to the spawned network ({})",
nodes.len()
);
Ok(())
}

async fn generate_snap(data_path: &str, snap_path: &str) -> Result<(), anyhow::Error> {
info!("\n📝 Generating snapshot file {snap_path} with data_path {data_path}...");

Expand Down
33 changes: 31 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use zombienet_sdk::{LocalFileSystem, Network, NetworkNode};
mod cli;
mod config;
mod doppelganger;
mod manifest;
mod metadata;
mod monit;
mod overrides;
Expand Down Expand Up @@ -168,6 +169,7 @@ async fn main() -> Result<(), anyhow::Error> {
apply_upgrade,
keep_messaging_state,
para_cores,
publish_bootnodes,
} => {
if with_monitor && !and_spawn {
bail!("--with-monitor can only be used with --and-spawn");
Expand All @@ -187,6 +189,7 @@ async fn main() -> Result<(), anyhow::Error> {
apply_upgrade,
keep_messaging_state,
para_cores,
publish_bootnodes,
)?;

if resolved_config.apply_upgrade && !resolved_config.and_spawn {
Expand Down Expand Up @@ -225,6 +228,15 @@ async fn main() -> Result<(), anyhow::Error> {

verify::verify_fork(&network, resolved_config.base_path.as_path()).await?;

if resolved_config.publish_bootnodes {
doppelganger::publish_bootnodes(
&network,
resolved_config.base_path.as_path(),
step,
)
.await?;
}

if resolved_config.apply_upgrade {
upgrade::apply_from_ready(&network, resolved_config.base_path.as_path())
.await?;
Expand All @@ -242,9 +254,15 @@ async fn main() -> Result<(), anyhow::Error> {
with_monitor,
step,
apply_upgrade,
publish_bootnodes,
} => {
let resolved_config =
resolve_spawn_config(config, base_path, with_monitor, apply_upgrade)?;
let resolved_config = resolve_spawn_config(
config,
base_path,
with_monitor,
apply_upgrade,
publish_bootnodes,
)?;
let step: Step = step.into();
let base_path_str = resolved_config.base_path.to_string_lossy();

Expand All @@ -260,6 +278,8 @@ async fn main() -> Result<(), anyhow::Error> {

resolve_if_dir_exist(&resolved_config.base_path, step).await;

manifest::warn_on_binary_mismatch(resolved_config.base_path.as_path()).await;

let network =
doppelganger::spawn(step, resolved_config.base_path.as_path(), None, None)
.await
Expand All @@ -269,6 +289,15 @@ async fn main() -> Result<(), anyhow::Error> {

verify::verify_fork(&network, resolved_config.base_path.as_path()).await?;

if resolved_config.publish_bootnodes {
doppelganger::publish_bootnodes(
&network,
resolved_config.base_path.as_path(),
step,
)
.await?;
}

if resolved_config.apply_upgrade {
upgrade::apply_from_ready(&network, resolved_config.base_path.as_path()).await?;
}
Expand Down
Loading