Skip to content
Open
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
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

You need these binaries available in your `PATH`:

- [Doppelganger binaries](https://github.com/paritytech/doppelganger-wrapper) (doppelganger, doppelganger-parachain, workers)
- [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.

### Logical steps: Bite, Spawn, Post

Expand Down Expand Up @@ -103,6 +103,20 @@ zombie-bite bite -r kusama --rc-upgrade ./kusama_runtime.wasm --and-spawn --appl
zombie-bite spawn -d /tmp/base_path --apply-upgrade
```

#### Forking a relay that is not a public network

`-r` also takes `custom%<name>%<rpc_endpoint>%<chain_spec_path>`, for a relay zombie-bite has no built-in knowledge of:

```sh
zombie-bite bite -d /tmp/base_path \
-r custom%previewnet%wss://previewnet.example.com%/path/to/previewnet.json \
-p custom%2000%wss://para.example.com%/path/to/para.json
```

The name is what the artifacts are named after, the endpoint is what the bite reads state and metadata from, and the chain-spec is what the node is started with. There is no built-in host config for such a relay, so the endpoint has to be reachable — `Configuration::ActiveConfig` is read from it.

A relay name that is not one of `polkadot`, `kusama`, `paseo` or `westend` is treated as a custom relay rather than silently falling back to polkadot.

#### Cores and messaging state

- `--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.
Expand Down
83 changes: 80 additions & 3 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ use crate::config::{
BiteOptions, CoresOverride, Parachain, Relaychain, Upgrades, ZombieBiteConfig,
};

const KNOWN_RELAYS: [&str; 4] = ["polkadot", "kusama", "paseo", "westend"];

#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
pub struct Args {
Expand All @@ -29,7 +31,10 @@ pub enum Commands {
/// The network will be using for bite
/// If not specified, will use the value from config.
/// If not in config, defaults to polkadot.
#[arg(short = 'r', long = "rc", value_parser = clap::builder::PossibleValuesParser::new(["polkadot", "kusama", "paseo", "westend"]))]
/// The network to bite: polkadot, kusama, paseo or westend.
/// For a relay that is not a public network use:
/// custom%<name>%<rpc_endpoint>%<chain_spec_path>
#[arg(short = 'r', long = "rc", verbatim_doc_comment)]
Comment on lines +35 to +37

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are dropping the help msj here with the possible values. I think we should include it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

restored, lists the four networks plus the custom format

relay: Option<String>,
/// If provided we will override the runtime as part of the process of 'bite'
/// The resulting network will be running with this runtime.
Expand Down Expand Up @@ -243,8 +248,16 @@ pub fn resolve_bite_config(
"polkadot".to_string()
};

let relaychain = if relay_runtime.is_some() || rc_sync_url.is_some() || relay_bite_at.is_some()
{
let relaychain = if relay_network.starts_with("custom%") {
resolve_custom_relaychain(&relay_network, relay_runtime.clone(), relay_bite_at)?
} else if !KNOWN_RELAYS.contains(&relay_network.as_str()) {
// Anything else is a typo, not a chain to bite: a custom relay has to
// come with its endpoint and chain-spec.
bail!(
"unknown relay '{relay_network}'; use one of {} or custom%<name>%<rpc_endpoint>%<chain_spec_path>",
KNOWN_RELAYS.join(", ")
);
} else if relay_runtime.is_some() || rc_sync_url.is_some() || relay_bite_at.is_some() {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But here we support more strings that may not start with custom%, I think we need to ensure is a valid rc.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

now bails on anything that isn't a known network or custom%. Kept the permissive mapping only in Relaychain::new for the helper subcommands that only get a name back

// CLI args provided, use them
Relaychain::new_with_values(&relay_network, relay_runtime, rc_sync_url, relay_bite_at)
} else if let Some(ref config) = config_file {
Expand Down Expand Up @@ -470,6 +483,29 @@ pub fn resolve_spawn_config(
})
}

/// custom%<name>%<rpc_endpoint>%<chain_spec_path>
fn resolve_custom_relaychain(
s: &str,
maybe_override: Option<String>,
maybe_bite_at: Option<u32>,
) -> Result<Relaychain, anyhow::Error> {
let parts: Vec<&str> = s.splitn(4, '%').collect();
if parts.len() != 4 {
bail!("custom relay must be custom%<name>%<rpc_endpoint>%<chain_spec_path>, got '{s}'");
}
let (name, rpc, chain_spec) = (parts[1], parts[2], parts[3]);
if name.is_empty() || rpc.is_empty() || chain_spec.is_empty() {
bail!("custom relay needs a name, an rpc endpoint and a chain-spec path, got '{s}'");
}
Ok(Relaychain::new_custom(
name,
chain_spec,
rpc,
maybe_override,
maybe_bite_at,
))
}

fn resolve_custom_parachain(s: &str) -> Parachain {
let parts: Vec<&str> = s.splitn(5, '%').collect();
trace!("custom parts: {parts:?}");
Expand Down Expand Up @@ -562,4 +598,45 @@ mod test {
let s = "custom%3392%wss://kusama-yap-3392.example.com:1234%/path/to/chain-spec.json%abc";
let _para = resolve_custom_parachain(s);
}
#[test]
fn custom_relay_works() {
let rc = resolve_custom_relaychain(
"custom%previewnet%wss://previewnet.example.com%/path/to/previewnet.json",
None,
Some(42),
)
.unwrap();

assert_eq!(rc.as_chain_string(), "previewnet");
assert_eq!(rc.chain_spec(), Some("/path/to/previewnet.json"));
// a custom relay is passed to the node as a spec path, not a name
assert_eq!(rc.chain_arg(), "/path/to/previewnet.json");
assert_eq!(rc.rpc_endpoint(), "wss://previewnet.example.com");
assert_eq!(rc.sync_endpoint(), "wss://previewnet.example.com");
assert_eq!(rc.at_block(), Some(42));
assert!(rc.is_custom());
}

#[test]
fn custom_relay_needs_every_part() {
for bad in [
"custom%previewnet%wss://previewnet.example.com",
"custom%previewnet%%/path/to/spec.json",
"custom%%wss://x%/path/to/spec.json",
] {
assert!(
resolve_custom_relaychain(bad, None, None).is_err(),
"should reject '{bad}'"
);
}
}

#[test]
fn unknown_relay_name_keeps_its_name() {
// helper subcommands only get the name back as a string, and the
// artifacts are named after it
let rc = Relaychain::new("previewnet");
assert_eq!(rc.as_chain_string(), "previewnet");
assert!(rc.is_custom());
}
}
147 changes: 124 additions & 23 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,16 @@ pub enum Relaychain {
maybe_sync_url: MaybeSyncUrl,
maybe_bite_at: MaybeByteAt,
},
/// A relay chain that is not one of the public networks: its name is used
/// for the artifact file names, and the chain-spec and endpoint have to be
/// supplied because there is nothing to look them up from.
Custom {
name: String,
chain_spec: MaybeChainSpec,
maybe_override: MaybeWasmOverridePath,
maybe_sync_url: MaybeSyncUrl,
maybe_bite_at: MaybeByteAt,
},
}

impl Relaychain {
Expand All @@ -254,11 +264,36 @@ impl Relaychain {
maybe_sync_url: None,
maybe_bite_at: None,
},
_ => Self::Polkadot {
"polkadot" => Self::Polkadot {
maybe_override: None,
maybe_sync_url: None,
maybe_bite_at: None,
},
// Keeps a custom relay's artifact names working in the helper
// subcommands, which only get the name back as a string.
other => Self::Custom {
name: other.to_string(),
chain_spec: None,
maybe_override: None,
maybe_sync_url: None,
maybe_bite_at: None,
},
}
}

pub fn new_custom(
name: impl Into<String>,
chain_spec: impl Into<String>,
rpc: impl Into<String>,
maybe_override: MaybeWasmOverridePath,
maybe_bite_at: MaybeByteAt,
) -> Self {
Self::Custom {
name: name.into(),
chain_spec: Some(chain_spec.into()),
maybe_override,
maybe_sync_url: Some(rpc.into()),
maybe_bite_at,
}
}

Expand All @@ -284,7 +319,14 @@ impl Relaychain {
maybe_sync_url,
maybe_bite_at,
},
_ => Self::Polkadot {
"polkadot" => Self::Polkadot {
maybe_override,
maybe_sync_url,
maybe_bite_at,
},
other => Self::Custom {
name: other.to_string(),
chain_spec: None,
maybe_override,
maybe_sync_url,
maybe_bite_at,
Expand All @@ -293,12 +335,7 @@ impl Relaychain {
}

pub fn as_local_chain_string(&self) -> String {
String::from(match self {
Relaychain::Polkadot { .. } => "polkadot-local",
Relaychain::Kusama { .. } => "kusama-local",
Relaychain::Paseo { .. } => "paseo-local",
Relaychain::Westend { .. } => "westend-local",
})
format!("{}-local", self.as_chain_string())
}

pub fn as_chain_string(&self) -> String {
Expand All @@ -307,26 +344,67 @@ impl Relaychain {
Relaychain::Kusama { .. } => "kusama",
Relaychain::Paseo { .. } => "paseo",
Relaychain::Westend { .. } => "westend",
Relaychain::Custom { name, .. } => name,
})
}

// TODO: make this endpoints configurables
pub fn sync_endpoint(&self) -> String {
String::from(match self {
/// Chain-spec of a custom relay; the public networks are known to the node
/// by name.
pub fn chain_spec(&self) -> Option<&str> {
match self {
Relaychain::Custom { chain_spec, .. } => chain_spec.as_deref(),
_ => None,
}
}

/// Value for the node's `--chain`: a spec path for a custom relay, the
/// network name otherwise.
pub fn chain_arg(&self) -> String {
self.chain_spec()
.map(str::to_string)
.unwrap_or_else(|| self.as_chain_string())
}

pub fn is_custom(&self) -> bool {
matches!(self, Relaychain::Custom { .. })
}

/// Endpoint supplied with `--rc-sync-url` / the config's `sync_url`, used
/// instead of the public default. Both the parachain sync and the reads the
/// bite does against the source go through it: the reason to pass it is that
/// the public endpoint is unusable (rate limited, down, or not reachable
/// from where the bite runs).
pub fn sync_url(&self) -> Option<&str> {
match self {
Relaychain::Polkadot { maybe_sync_url, .. }
| Relaychain::Kusama { maybe_sync_url, .. }
| Relaychain::Paseo { maybe_sync_url, .. }
| Relaychain::Westend { maybe_sync_url, .. }
| Relaychain::Custom { maybe_sync_url, .. } => maybe_sync_url.as_deref(),
}
}

fn default_endpoint(&self) -> &'static str {
match self {
Relaychain::Polkadot { .. } => "wss://rpc.polkadot.io",
Relaychain::Kusama { .. } => "wss://kusama-rpc.polkadot.io",
Relaychain::Paseo { .. } => "wss://paseo-rpc.dwellir.com",
Relaychain::Westend { .. } => "wss://westend-rpc.n.dwellir.com",
})
// A custom relay has no public endpoint to fall back to.
Relaychain::Custom { .. } => "",
}
}

pub fn sync_endpoint(&self) -> String {
self.sync_url()
.unwrap_or_else(|| self.default_endpoint())
.to_string()
}

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::Westend { .. } => "wss://westend-rpc.n.dwellir.com",
})
self.sync_url()
.unwrap_or_else(|| self.default_endpoint())
.to_string()
}

pub fn context(&self) -> Context {
Expand All @@ -338,7 +416,8 @@ impl Relaychain {
Relaychain::Kusama { maybe_override, .. }
| Relaychain::Polkadot { maybe_override, .. }
| Relaychain::Westend { maybe_override, .. }
| Relaychain::Paseo { maybe_override, .. } => maybe_override.as_deref(),
| Relaychain::Paseo { maybe_override, .. }
| Relaychain::Custom { maybe_override, .. } => maybe_override.as_deref(),
}
}

Expand All @@ -347,6 +426,9 @@ impl Relaychain {
Relaychain::Paseo { .. } => 600,
Relaychain::Kusama { .. } => 600,
Relaychain::Westend { .. } => 600,
// TODO: read it from the chain instead of assuming a testnet-sized
// epoch for a custom relay.
Relaychain::Custom { .. } => 600,
_ => 2400,
}
}
Expand All @@ -356,7 +438,8 @@ impl Relaychain {
Relaychain::Kusama { maybe_bite_at, .. }
| Relaychain::Polkadot { maybe_bite_at, .. }
| Relaychain::Westend { maybe_bite_at, .. }
| Relaychain::Paseo { maybe_bite_at, .. } => *maybe_bite_at,
| Relaychain::Paseo { maybe_bite_at, .. }
| Relaychain::Custom { maybe_bite_at, .. } => *maybe_bite_at,
}
}
}
Expand Down Expand Up @@ -599,7 +682,7 @@ pub fn generate_network_config(
Relaychain::Polkadot { .. } | Relaychain::Kusama { .. } | Relaychain::Westend { .. } => {
CMD_TPL
}
Relaychain::Paseo { .. } => DEFAULT_CHAIN_SPEC_TPL_COMMAND,
Relaychain::Paseo { .. } | Relaychain::Custom { .. } => DEFAULT_CHAIN_SPEC_TPL_COMMAND,
};

// Calculate required validators based on parachain count
Expand Down Expand Up @@ -1129,9 +1212,12 @@ mod test {
let paseo = Relaychain::new("paseo");
assert_eq!(paseo.as_chain_string(), "paseo");

// Unknown defaults to polkadot
// An unknown name is a custom relay keeping its name, not a silent
// fallback to polkadot: the helper subcommands name artifacts after it,
// and a typo now fails instead of biting the wrong chain.
let unknown = Relaychain::new("unknown");
assert_eq!(unknown.as_chain_string(), "polkadot");
assert_eq!(unknown.as_chain_string(), "unknown");
assert!(unknown.is_custom());
}

#[test]
Expand Down Expand Up @@ -1559,4 +1645,19 @@ chain_spec = "/path/to/yap-3392-raw-chain-spec.json"
let para_config = parachains.first().unwrap();
assert_eq!(para_config.id(), 3392);
}
#[test]
fn sync_url_overrides_the_public_endpoint() {
let default = Relaychain::new("kusama");
assert_eq!(default.sync_endpoint(), "wss://kusama-rpc.polkadot.io");
assert_eq!(default.rpc_endpoint(), "wss://kusama-rpc.polkadot.io");

let custom = Relaychain::new_with_values(
"kusama",
None,
Some("wss://my-own-kusama.example.com".to_string()),
None,
);
assert_eq!(custom.sync_endpoint(), "wss://my-own-kusama.example.com");
assert_eq!(custom.rpc_endpoint(), "wss://my-own-kusama.example.com");
}
}
Loading
Loading