diff --git a/README.md b/README.md index 8ec6fca..76e47ad 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ ### Requirements -You need these binaries available in your `PATH`: +You need these binaries available in your `PATH`, or configured by path (see [Use a specific doppelganger build](#use-a-specific-doppelganger-build) and [Set the command / image of the spawned network](#set-the-command--image-of-the-spawned-network)): - [Doppelganger binaries](https://github.com/paritytech/doppelganger-wrapper) (doppelganger, doppelganger-parachain, workers) — v0.2.3 or newer. The bite syncs state without range proofs (a chain whose staking lives on Asset Hub cannot be synced with them: sync freezes silently at ~37%); older builds ignore that setting and freeze on such chains. - `polkadot` and `polkadot-parachain` for the spawned network — polkadot v1.22.1 or newer: the spawn relies on `ZOMBIE_DISPUTE_CANDIDATE_LIFETIME_AFTER_FINALIZATION` ([polkadot-sdk#12247](https://github.com/paritytech/polkadot-sdk/pull/12247)); on older binaries the fork produces blocks but never finalizes. @@ -146,7 +146,32 @@ They land as `default_command` / `default_image` in the generated `config.toml`, - `command` takes effect immediately, including with `zombie-bite spawn`, and is the way to point at a local build instead of relying on `PATH`. It cannot contain whitespace; extra args still go through `ZOMBIE_BITE_RC_EXTRA_ARGS` / `ZOMBIE_BITE_AH_EXTRA_ARGS`. - `image` is only honored by providers that use images (docker/k8s). `zombie-bite spawn` uses the native provider, which ignores it, so `image` is a pass-through for feeding the generated `config.toml` to zombienet elsewhere. -There are no cli flags for these, they are config-file only. Neither key affects the `bite` step itself, which always runs the `doppelganger` / `doppelganger-parachain` binaries. See [examples/with-images.toml](./examples/with-images.toml). +There are no cli flags for these, they are config-file only. Neither key affects the `bite` step itself, which runs the `doppelganger` / `doppelganger-parachain` binaries (see below). See [examples/with-images.toml](./examples/with-images.toml). + +#### Use a specific doppelganger build + +The `bite` step syncs the live chains with `doppelganger` (relay) and `doppelganger-parachain` (parachains), and uses the same binaries to build their chain-specs. By default both are resolved from `PATH`. To run a specific build instead, e.g. a downloaded [doppelganger-wrapper release](https://github.com/paritytech/doppelganger-wrapper/releases), point at it from the config file: + +```toml +[doppelganger] +relay = "/opt/doppelganger/v0.2.3/doppelganger" +parachain = "/opt/doppelganger/v0.2.3/doppelganger-parachain" +``` + +or with the matching flags, which win over the config file key by key: + +```sh +zombie-bite bite -r polkadot -p asset-hub \ + --doppelganger /opt/doppelganger/v0.2.3/doppelganger \ + --doppelganger-parachain /opt/doppelganger/v0.2.3/doppelganger-parachain +``` + +- Both are optional and independent: an unset one keeps the `PATH` default. +- A value with a `/` in it must be an executable file, checked before any sync starts. A bare name is looked up on `PATH`, like the default. As with `command`, no whitespace: it's a binary, not a command line. +- Keep the release's `polkadot-execute-worker` / `polkadot-prepare-worker` in the same directory as the binaries: doppelganger looks for its workers next to itself. +- This only affects the `bite` step. The spawned network runs whatever `command` says (above). + +See [examples/doppelganger.toml](./examples/doppelganger.toml). #### Forking a relay that is not a public network `-r` also takes `custom%%%`, for a relay zombie-bite has no built-in knowledge of: diff --git a/crates/zombie-bite-core/README.md b/crates/zombie-bite-core/README.md index b48fad5..0585708 100644 --- a/crates/zombie-bite-core/README.md +++ b/crates/zombie-bite-core/README.md @@ -18,9 +18,12 @@ The flow mirrors the cli subcommands: 4. **pack** / **unpack** (`bundle`): move the artifacts to another machine as a single file. -The same external binaries as the cli are needed in `PATH` (doppelganger, +The same external binaries as the cli are needed (doppelganger, `polkadot`, `polkadot-parachain`), see the [repository README](https://github.com/pepoviola/zombie-bite#requirements). +They are looked up on `PATH` unless configured: `BiteOptions::doppelganger` +(a `DoppelgangerSetup`) sets the doppelganger binaries the bite runs, and +`SpawnSetup` sets the command / image of the spawned network. ## Example diff --git a/crates/zombie-bite-core/src/config.rs b/crates/zombie-bite-core/src/config.rs index 1002080..5f6e4cc 100644 --- a/crates/zombie-bite-core/src/config.rs +++ b/crates/zombie-bite-core/src/config.rs @@ -265,6 +265,77 @@ pub struct BiteOptions { pub upgrades: Upgrades, pub cores: CoresOverride, pub keep_messaging_state: bool, + /// Which `doppelganger` binaries sync the live chains and build their + /// chain-specs. + pub doppelganger: DoppelgangerSetup, +} + +/// The `doppelganger` binaries the *bite* step runs, as opposed to +/// [`SpawnSetup`], which is what the spawned network runs. +/// +/// Unset fields fall back to the bare `doppelganger` / `doppelganger-parachain` +/// names, resolved from `PATH`. +#[derive(Debug, Default, Clone, PartialEq)] +pub struct DoppelgangerSetup { + /// Syncs the relay chain and builds its chain-spec. + pub relay: Option, + /// Syncs the parachains and builds their chain-specs. + pub para: Option, +} + +impl DoppelgangerSetup { + pub fn relay_command(&self) -> String { + self.relay + .clone() + .unwrap_or_else(|| Context::Relaychain.doppelganger_cmd()) + } + + pub fn para_command(&self) -> String { + self.para + .clone() + .unwrap_or_else(|| Context::Parachain.doppelganger_cmd()) + } + + /// Validate with zombienet's own `Command`, and check that a value naming + /// a file (anything with a `/` in it) is one that can be run. Either + /// mistake would otherwise only surface when the first sync node fails to + /// start. + pub fn validate(&self) -> Result<(), anyhow::Error> { + for (label, command) in [("relay", &self.relay), ("parachain", &self.para)] { + let Some(command) = command else { + continue; + }; + + Command::try_from(command.as_str()) + .map_err(|e| anyhow::anyhow!("invalid `doppelganger` command for {label}: {e}"))?; + + if command.contains('/') { + ensure_executable(command).map_err(|e| { + anyhow::anyhow!("invalid `doppelganger` command for {label}: {e}") + })?; + } + } + + Ok(()) + } +} + +fn ensure_executable(path: &str) -> Result<(), anyhow::Error> { + let metadata = + std::fs::metadata(path).map_err(|e| anyhow::anyhow!("can't read '{path}': {e}"))?; + if !metadata.is_file() { + anyhow::bail!("'{path}' is not a file"); + } + + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + if metadata.permissions().mode() & 0o111 == 0 { + anyhow::bail!("'{path}' is not executable"); + } + } + + Ok(()) } pub(crate) fn get_assigned_cores( @@ -894,6 +965,17 @@ pub struct ZombieBiteConfig { /// Hostname or IP to advertise the spawned nodes under in the published /// chain-specs. pub publish_bootnodes: Option, + /// The `doppelganger` binaries the bite step runs. + pub doppelganger: Option, +} + +/// `[doppelganger]` table of the config file, see [`DoppelgangerSetup`]. +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Default)] +pub struct DoppelgangerConfig { + /// Defaults to `doppelganger`. + pub relay: Option, + /// Defaults to `doppelganger-parachain`. + pub parachain: Option, } #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] @@ -1018,6 +1100,16 @@ impl ZombieBiteConfig { Ok(paras) } + /// The `[doppelganger]` table as a [`DoppelgangerSetup`], empty when the + /// table is absent. + pub fn get_doppelganger_setup(&self) -> DoppelgangerSetup { + let config = self.doppelganger.clone().unwrap_or_default(); + DoppelgangerSetup { + relay: config.relay, + para: config.parachain, + } + } + /// Collect the `command` / `image` of every chain, keyed by para id for /// the parachains. Disabled parachains are skipped, since `to_parachain` /// is what decides which ones make it into the network. @@ -1462,6 +1554,7 @@ mod test { apply_upgrade: None, keep_messaging_state: None, publish_bootnodes: None, + doppelganger: None, }; assert_eq!(config.get_parachains().unwrap().len(), 0); @@ -1526,6 +1619,7 @@ mod test { apply_upgrade: None, keep_messaging_state: None, publish_bootnodes: None, + doppelganger: None, }; let parachains = config.get_parachains().unwrap(); @@ -1858,6 +1952,7 @@ chain_spec = "/path/to/yap-3392-raw-chain-spec.json" apply_upgrade: None, keep_messaging_state: None, publish_bootnodes: None, + doppelganger: None, } } @@ -2036,4 +2131,142 @@ chain_spec = "/path/to/yap-3392-raw-chain-spec.json" assert_eq!(custom.sync_endpoint(), "wss://my-own-kusama.example.com"); assert_eq!(custom.rpc_endpoint(), "wss://my-own-kusama.example.com"); } + + /// A unique, empty scratch dir under the system temp dir. + fn scratch_dir(name: &str) -> std::path::PathBuf { + let dir = std::env::temp_dir().join(format!("zb-{name}-{}", std::process::id())); + let _ = std::fs::remove_dir_all(&dir); + std::fs::create_dir_all(&dir).unwrap(); + dir + } + + #[test] + fn doppelganger_setup_falls_back_to_builtin_defaults() { + let setup = DoppelgangerSetup::default(); + + assert_eq!(setup.relay_command(), "doppelganger"); + assert_eq!(setup.para_command(), "doppelganger-parachain"); + assert!(setup.validate().is_ok()); + } + + #[test] + fn doppelganger_setup_validate_checks_the_file() { + use std::os::unix::fs::PermissionsExt; + + let dir = scratch_dir("doppelganger-validate"); + let runnable = dir.join("doppelganger"); + std::fs::write(&runnable, "#!/bin/sh\n").unwrap(); + std::fs::set_permissions(&runnable, std::fs::Permissions::from_mode(0o755)).unwrap(); + let not_runnable = dir.join("doppelganger-parachain"); + std::fs::write(¬_runnable, "").unwrap(); + std::fs::set_permissions(¬_runnable, std::fs::Permissions::from_mode(0o644)).unwrap(); + + let ok = DoppelgangerSetup { + relay: Some(runnable.to_string_lossy().into_owned()), + para: None, + }; + assert!(ok.validate().is_ok()); + assert_eq!(ok.relay_command(), runnable.to_string_lossy()); + + let not_executable = DoppelgangerSetup { + relay: None, + para: Some(not_runnable.to_string_lossy().into_owned()), + }; + let err = not_executable.validate().unwrap_err().to_string(); + assert!(err.contains("for parachain"), "{err}"); + assert!(err.contains("is not executable"), "{err}"); + + let missing = DoppelgangerSetup { + relay: Some(dir.join("nope").to_string_lossy().into_owned()), + para: None, + }; + let err = missing.validate().unwrap_err().to_string(); + assert!(err.contains("for relay"), "{err}"); + assert!(err.contains("can't read"), "{err}"); + + let a_dir = DoppelgangerSetup { + relay: Some(dir.to_string_lossy().into_owned()), + para: None, + }; + let err = a_dir.validate().unwrap_err().to_string(); + assert!(err.contains("is not a file"), "{err}"); + + // Same rule as `SpawnSetup`: no args smuggled into the command. + let with_args = DoppelgangerSetup { + relay: Some("doppelganger --foo".to_string()), + para: None, + }; + let err = with_args.validate().unwrap_err().to_string(); + assert!( + err.contains("invalid `doppelganger` command for relay"), + "{err}" + ); + + // A bare name is left to PATH, as the default is: nothing to check. + let bare = DoppelgangerSetup { + relay: Some("my-doppelganger".to_string()), + para: None, + }; + assert!(bare.validate().is_ok()); + + std::fs::remove_dir_all(&dir).unwrap(); + } + + #[test] + fn config_file_reads_the_doppelganger_table() { + let config: ZombieBiteConfig = toml::from_str( + r#" + [relaychain] + network = "polkadot" + + [doppelganger] + relay = "/opt/doppelganger/doppelganger" + parachain = "/opt/doppelganger/doppelganger-parachain" + "#, + ) + .unwrap(); + + let setup = config.get_doppelganger_setup(); + assert_eq!(setup.relay_command(), "/opt/doppelganger/doppelganger"); + assert_eq!( + setup.para_command(), + "/opt/doppelganger/doppelganger-parachain" + ); + } + + #[test] + fn example_doppelganger_toml_is_valid() { + let config = ZombieBiteConfig::from_file(concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../examples/doppelganger.toml" + )) + .unwrap(); + + // Not `validate()`: the example points at paths that won't exist here. + let setup = config.get_doppelganger_setup(); + assert_eq!( + setup.relay_command(), + "/opt/doppelganger/v0.2.3/doppelganger" + ); + assert_eq!( + setup.para_command(), + "/opt/doppelganger/v0.2.3/doppelganger-parachain" + ); + } + + #[test] + fn config_file_without_a_doppelganger_table_keeps_the_defaults() { + let config: ZombieBiteConfig = toml::from_str( + r#" + [relaychain] + network = "polkadot" + "#, + ) + .unwrap(); + + assert_eq!( + config.get_doppelganger_setup(), + DoppelgangerSetup::default() + ); + } } diff --git a/crates/zombie-bite-core/src/doppelganger.rs b/crates/zombie-bite-core/src/doppelganger.rs index 0b27f90..d62770d 100644 --- a/crates/zombie-bite-core/src/doppelganger.rs +++ b/crates/zombie-bite-core/src/doppelganger.rs @@ -38,8 +38,8 @@ use crate::utils::{ }; use crate::config::{ - get_assigned_cores, get_state_pruning_config, BiteOptions, Context, Parachain, Relaychain, - SpawnSetup, Step, + get_assigned_cores, get_state_pruning_config, BiteOptions, Parachain, Relaychain, SpawnSetup, + Step, }; use crate::manifest::{self, ChainEntry, Manifest}; use crate::metadata::ChainMetadata; @@ -78,9 +78,15 @@ pub async fn bite( spawn_setup: &SpawnSetup, opts: &BiteOptions, ) -> Result<(), anyhow::Error> { + // Fail before any sync starts: a bad path would otherwise only show up + // when the first sync node can't be spawned. + opts.doppelganger.validate()?; + let doppelganger_relay = opts.doppelganger.relay_command(); + let doppelganger_para = opts.doppelganger.para_command(); + // Star the node and wait until finish (with temp dir managed by us) info!( - "🪞 Starting DoppelGanger process for {} and {:?}", + "🪞 Starting DoppelGanger process for {} and {:?} (relay: {doppelganger_relay}, paras: {doppelganger_para})", relay_chain.as_chain_string(), paras_to ); @@ -150,7 +156,7 @@ pub async fn bite( syncs.push( sync_para( ns.clone(), - "doppelganger-parachain", + &doppelganger_para, para, &relay_chain, relay_chain.sync_endpoint(), @@ -168,7 +174,6 @@ pub async fn bite( // loop over paras let mut para_artifacts = vec![]; let mut para_heads_env = vec![]; - let context_para = Context::Parachain; for (para_index, (_sync_node, sync_db_path, sync_chain, sync_head_path)) in res.into_iter().enumerate() { @@ -204,7 +209,7 @@ pub async fn bite( generate_chain_spec( ns.clone(), &chain_spec_path, - &context_para.doppelganger_cmd(), + &doppelganger_para, &sync_chain, ) .await @@ -290,7 +295,7 @@ pub async fn bite( let (sync_node, sync_db_path, sync_chain) = sync_relay_only( ns.clone(), - "doppelganger", + &doppelganger_relay, &relay_chain, para_heads_env, rc_default_overrides_path, @@ -306,12 +311,11 @@ pub async fn bite( // get the chain-spec (prod) and clean the bootnodes // relaychain - let context_relay = Context::Relaychain; let r_chain_spec_path = format!("{}/{}-spec.json", base_dir_str, sync_chain); generate_chain_spec( ns.clone(), &r_chain_spec_path, - &context_relay.doppelganger_cmd(), + &doppelganger_relay, &relay_chain.chain_arg(), ) .await @@ -1154,48 +1158,6 @@ async fn generate_chain_spec( Ok(()) } -async fn run_doppelganger_node(ns: DynNamespace, base_path: &Path) -> Result<(), String> { - let data_path = format!("{}/sync_db", base_path.to_string_lossy()); - let logs_path = format!("{}/sync.log", base_path.to_string_lossy()); - info!( - "⛓ Syncing using warp, this could take a while. You can follow the logs with: \n\t - tail -f {}", - &logs_path - ); - - let temp_node = ns - .spawn_node( - &SpawnNodeOptions::new("temp-doppelganger", "bash") - .args(vec!["-c", "while :; do sleep 60; done"]), - ) - .await - .unwrap(); - - let _stdout = temp_node - .run_command( - RunCommandOptions::new("bash") - .args(vec![ - "-c", - format!( - "doppelganger -l doppelganger=debug --chain kusama --sync warp -d {} > {} 2>&1", - data_path, logs_path - ) - .as_str(), - ]) - // Override rust log for sync - .env(vec![("RUST_LOG", "")]), - ) - .await - .unwrap() - .unwrap(); - - temp_node.destroy().await.unwrap(); - - info!("✅ Synced"); - - Ok(()) -} - fn get_epoch_ms() -> u128 { SystemTime::now() .duration_since(UNIX_EPOCH) diff --git a/crates/zombie-bite-core/src/resolve.rs b/crates/zombie-bite-core/src/resolve.rs index 6790c70..5b3b1cb 100644 --- a/crates/zombie-bite-core/src/resolve.rs +++ b/crates/zombie-bite-core/src/resolve.rs @@ -11,7 +11,8 @@ use std::{ use tracing::{trace, warn}; use crate::config::{ - BiteOptions, CoresOverride, Parachain, Relaychain, SpawnSetup, Upgrades, ZombieBiteConfig, + BiteOptions, CoresOverride, DoppelgangerSetup, Parachain, Relaychain, SpawnSetup, Upgrades, + ZombieBiteConfig, }; const KNOWN_RELAYS: [&str; 4] = ["polkadot", "kusama", "paseo", "westend"]; @@ -75,6 +76,10 @@ pub struct BiteOverrides { pub para_cores: Vec, /// Host to advertise the spawned nodes under as bootNodes. pub publish_bootnodes: Option, + /// `doppelganger` binary to sync the relay chain with. + pub doppelganger: Option, + /// `doppelganger-parachain` binary to sync the parachains with. + pub doppelganger_parachain: Option, } /// Values that take precedence over the config file when resolving a spawn. @@ -153,6 +158,8 @@ pub fn resolve_bite_config( keep_messaging_state, para_cores, publish_bootnodes, + doppelganger, + doppelganger_parachain, } = overrides; // Load config file if provided @@ -306,6 +313,20 @@ pub fn resolve_bite_config( SpawnSetup::default() }; spawn_setup.validate()?; + + // Doppelganger binaries: each cli flag wins over its config-file key. + let mut doppelganger_setup = if let Some(ref config) = config_file { + config.get_doppelganger_setup() + } else { + DoppelgangerSetup::default() + }; + if doppelganger.is_some() { + doppelganger_setup.relay = doppelganger; + } + if doppelganger_parachain.is_some() { + doppelganger_setup.para = doppelganger_parachain; + } + doppelganger_setup.validate()?; // Per-para cores: CLI entries win over the config file's `cores`. let mut cores: CoresOverride = CoresOverride::new(); if let Some(ref config) = config_file { @@ -362,6 +383,7 @@ pub fn resolve_bite_config( upgrades, cores, keep_messaging_state: resolved_keep_messaging, + doppelganger: doppelganger_setup, }, }; resolved.validate()?; @@ -583,4 +605,94 @@ mod test { assert_eq!(rc.as_chain_string(), "previewnet"); assert!(rc.is_custom()); } + + fn write_config(name: &str, contents: &str) -> String { + let path = std::env::temp_dir().join(format!("zb-{name}-{}.toml", std::process::id())); + std::fs::write(&path, contents).unwrap(); + path.to_string_lossy().into_owned() + } + + #[test] + fn doppelganger_defaults_to_path_lookup() { + let resolved = resolve_bite_config( + None, + BiteOverrides { + base_path: Some(std::env::temp_dir().to_string_lossy().into_owned()), + ..Default::default() + }, + ) + .unwrap(); + + assert_eq!(resolved.opts.doppelganger, DoppelgangerSetup::default()); + } + + #[test] + fn doppelganger_flags_win_over_the_config_file() { + // Bare names, so validation has no file to look for. + let config = write_config( + "doppelganger-precedence", + r#" + [relaychain] + network = "polkadot" + + [doppelganger] + relay = "doppelganger-from-config" + parachain = "doppelganger-parachain-from-config" + "#, + ); + + let from_config = resolve_bite_config( + Some(config.clone()), + BiteOverrides { + base_path: Some(std::env::temp_dir().to_string_lossy().into_owned()), + ..Default::default() + }, + ) + .unwrap(); + assert_eq!( + from_config.opts.doppelganger.relay_command(), + "doppelganger-from-config" + ); + assert_eq!( + from_config.opts.doppelganger.para_command(), + "doppelganger-parachain-from-config" + ); + + // Each flag overrides its own key only. + let with_flag = resolve_bite_config( + Some(config.clone()), + BiteOverrides { + base_path: Some(std::env::temp_dir().to_string_lossy().into_owned()), + doppelganger: Some("doppelganger-from-flag".to_string()), + ..Default::default() + }, + ) + .unwrap(); + assert_eq!( + with_flag.opts.doppelganger.relay_command(), + "doppelganger-from-flag" + ); + assert_eq!( + with_flag.opts.doppelganger.para_command(), + "doppelganger-parachain-from-config" + ); + + std::fs::remove_file(config).unwrap(); + } + + #[test] + fn a_doppelganger_path_that_does_not_exist_fails_resolution() { + let err = resolve_bite_config( + None, + BiteOverrides { + base_path: Some(std::env::temp_dir().to_string_lossy().into_owned()), + doppelganger_parachain: Some("/definitely/not/here".to_string()), + ..Default::default() + }, + ) + .unwrap_err() + .to_string(); + + assert!(err.contains("for parachain"), "{err}"); + } } diff --git a/crates/zombie-bite/src/cli.rs b/crates/zombie-bite/src/cli.rs index 4a2db79..5b70d1a 100644 --- a/crates/zombie-bite/src/cli.rs +++ b/crates/zombie-bite/src/cli.rs @@ -8,6 +8,9 @@ pub struct Args { } #[derive(Subcommand, Debug, Clone)] +// Parsed once from argv, so the size gap between `Bite` and the other +// subcommands costs nothing worth boxing clap fields over. +#[allow(clippy::large_enum_variant)] pub enum Commands { /// Bite the running network using 'doppelganger' binaries, and generate the artifacts for spawning. Bite { @@ -80,6 +83,16 @@ pub enum Commands { /// Db to use #[arg(long, value_parser = clap::builder::PossibleValuesParser::new(["rocksdb", "paritydb"]), default_value="rocksdb", verbatim_doc_comment)] database: String, + /// `doppelganger` binary to sync the relay chain with. + /// If not specified, will use the `[doppelganger]` table of the config, + /// and if not there, `doppelganger` from PATH. + #[arg(long, verbatim_doc_comment)] + doppelganger: Option, + /// `doppelganger-parachain` binary to sync the parachains with. + /// If not specified, will use the `[doppelganger]` table of the config, + /// and if not there, `doppelganger-parachain` from PATH. + #[arg(long, verbatim_doc_comment)] + doppelganger_parachain: Option, }, /// Spawn a new instance of the network from the bite step. Spawn { diff --git a/crates/zombie-bite/src/main.rs b/crates/zombie-bite/src/main.rs index 0dcdd31..770a99a 100644 --- a/crates/zombie-bite/src/main.rs +++ b/crates/zombie-bite/src/main.rs @@ -53,6 +53,8 @@ async fn main() -> Result<(), anyhow::Error> { keep_messaging_state, para_cores, publish_bootnodes, + doppelganger, + doppelganger_parachain, } => { if with_monitor && !and_spawn { bail!("--with-monitor can only be used with --and-spawn"); @@ -74,6 +76,8 @@ async fn main() -> Result<(), anyhow::Error> { keep_messaging_state, para_cores, publish_bootnodes, + doppelganger, + doppelganger_parachain, }, )?; diff --git a/examples/doppelganger.toml b/examples/doppelganger.toml new file mode 100644 index 0000000..5c50de1 --- /dev/null +++ b/examples/doppelganger.toml @@ -0,0 +1,21 @@ +# Zombie Bite Configuration - Specific doppelganger build +# `[doppelganger]` sets the binaries the `bite` step syncs the live chains +# with, and builds their chain-specs with. Both keys are optional: an unset one +# falls back to `doppelganger` / `doppelganger-parachain` from PATH. +# +# A path must be an executable file. Keep the release's +# `polkadot-execute-worker` / `polkadot-prepare-worker` next to it: doppelganger +# looks for its workers in its own directory. + +base_path = "/tmp/polkadot-bite" + +[relaychain] +network = "polkadot" + +[[parachains]] +type = "asset-hub" +enabled = true + +[doppelganger] +relay = "/opt/doppelganger/v0.2.3/doppelganger" +parachain = "/opt/doppelganger/v0.2.3/doppelganger-parachain"