Skip to content
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
6bc93a2
draft chainspec wrapper
re-gius Sep 9, 2025
38abb13
inject Anvil Node Config into Substrate CLI
re-gius Sep 10, 2025
9b33fb1
Set chain_id from config.json
re-gius Sep 11, 2025
30717d7
Add extra fields
re-gius Sep 15, 2025
66ee492
Merge branch 'master' into re-gius/chainspec-wrapper
re-gius Sep 15, 2025
85527e4
inject storage items into ChainSpec
re-gius Sep 16, 2025
b4420d0
implement ChainSpec trait for wrapper
re-gius Sep 17, 2025
b592f9a
Merge branch 'master' into re-gius/chainspec-wrapper
re-gius Sep 17, 2025
f807562
fix clippy
re-gius Sep 18, 2025
2ff0438
CR reorg
re-gius Sep 18, 2025
2302d04
Merge branch 'master' into re-gius/chainspec-wrapper
re-gius Sep 18, 2025
56b192f
Merge branch 'master' into re-gius/chainspec-wrapper
re-gius Sep 19, 2025
26e0fe7
set System::Number as u32
re-gius Sep 19, 2025
7caa8a5
nit
re-gius Sep 22, 2025
d513064
nit
re-gius Sep 22, 2025
5e80c89
Add genesis timestamp to time manager of mining node
re-gius Sep 22, 2025
7bafd06
Merge branch 'master' into re-gius/chainspec-wrapper
re-gius Sep 23, 2025
9d1a9d3
add custom genesis block number support in RPC
re-gius Sep 25, 2025
ce424ee
Merge branch 'master' into re-gius/chainspec-wrapper
re-gius Sep 25, 2025
16d17dc
fmt
re-gius Sep 25, 2025
0897f31
fmt
re-gius Sep 25, 2025
43e35d4
clippy + comments
re-gius Sep 26, 2025
329a139
Merge branch 'master' into re-gius/chainspec-wrapper
re-gius Sep 26, 2025
ce9de23
CR nits + reorg
re-gius Sep 26, 2025
6e67186
Add genesis integration tests (chain ID missing)
re-gius Sep 26, 2025
e2b2072
comment nit
re-gius Sep 29, 2025
3ee3857
add chain id test
re-gius Sep 29, 2025
3bd0395
Merge branch 'master' into re-gius/chainspec-wrapper
re-gius Sep 29, 2025
90da646
fix time manager creation
re-gius Sep 29, 2025
7221e82
changed genesis milliseconds mismatch + clippy for unused import
re-gius Sep 29, 2025
867d680
undo clippy change
re-gius Sep 29, 2025
327b973
Merge branch 'master' into re-gius/chainspec-wrapper
re-gius Sep 29, 2025
7680f00
merge fix - missing import
re-gius Sep 29, 2025
fcf200b
fix rpc client creation
re-gius Oct 1, 2025
5e42322
clippy nit
re-gius Oct 1, 2025
c51999b
fix
re-gius Oct 1, 2025
f8ba53b
Merge branch 'master' into re-gius/chainspec-wrapper
re-gius Oct 1, 2025
560962e
Merge branch 'master' into re-gius/chainspec-wrapper
re-gius Oct 7, 2025
e1abde2
CR fixes
re-gius Oct 8, 2025
c79e41b
fix types
re-gius Oct 8, 2025
c36ee17
nit
re-gius Oct 8, 2025
10b6b83
Use genesis block number to compute genesis hash
re-gius Oct 8, 2025
c5c7f47
improve error handling
re-gius Oct 8, 2025
4730f8e
fix metadata retrieval
alindima Oct 9, 2025
b3f7914
use the impersonation executor
alindima Oct 9, 2025
6a94b1a
use latest available metadata + error handling
re-gius Oct 9, 2025
8fa9e97
add Chain Id RPC
re-gius Oct 10, 2025
8a9689a
nit
re-gius Oct 10, 2025
11493c5
Merge branch 'master' into re-gius/chainspec-wrapper
re-gius Oct 10, 2025
5ae3b58
fmt + clippy
re-gius Oct 10, 2025
8915d00
add `InternalError` variant
re-gius Oct 10, 2025
e7cdd52
make test less flaky
re-gius Oct 10, 2025
986fd16
fix test with chain id RPC
re-gius Oct 10, 2025
a8ae12c
Use chain id RPC in genesis tests
re-gius Oct 10, 2025
88009ff
fix deny license
re-gius Oct 10, 2025
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
19 changes: 19 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion crates/anvil-polkadot/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ path = "bin/main.rs"

[dependencies]
# foundry internal
codec = { version = "3.7.5", default-features = true, package = "parity-scale-codec" }
substrate-runtime = { path = "substrate-runtime" }
polkadot-sdk = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master", default-features = false, features = [
"sc-allocator",
Expand All @@ -34,13 +35,16 @@ polkadot-sdk = { git = "https://github.com/paritytech/polkadot-sdk.git", branch
"sc-executor-common",
"sc-executor-wasmtime",
"sc-keystore",
"sc-network",
"sc-network-types",
"sc-rpc",
"sc-rpc-api",
"sc-rpc-server",
"sc-rpc-spec-v2",
"sc-runtime-utilities",
"sc-service",
"sc-state-db",
"sc-telemetry",
"sc-tracing",
"sc-transaction-pool",
"sc-transaction-pool-api",
Expand Down Expand Up @@ -128,10 +132,10 @@ clap = { version = "4", features = [
clap_complete = { version = "4" }
chrono.workspace = true
clap_complete_fig = "4"
parity-scale-codec = "3.7.5"
subxt = "0.43.0"
subxt-signer = "0.43.0"
tokio-stream = "0.1.17"
jsonrpsee = "0.24.9"
sqlx = "0.8.6"

[dev-dependencies]
Expand Down
47 changes: 44 additions & 3 deletions crates/anvil-polkadot/src/api_server/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use alloy_rpc_types::{TransactionRequest, anvil::MineOptions};
use alloy_serde::WithOtherFields;
use anvil_core::eth::{EthRequest, Params as MineParams};
use anvil_rpc::response::ResponseResult;
use codec::Decode;
use futures::{StreamExt, channel::mpsc};
use polkadot_sdk::{
pallet_revive::evm::{Account, Block, Bytes, ReceiptInfo},
Expand All @@ -26,12 +27,19 @@ use polkadot_sdk::{
client::{Client as EthRpcClient, ClientError, SubscriptionType},
subxt_client::{self, SrcChainConfig},
},
sp_core::{self, keccak_256},
sc_client_api::StorageProvider,
sc_executor::WasmExecutor,
sc_runtime_utilities::fetch_latest_metadata_from_code_blob,
sp_core::{self, keccak_256, storage::StorageKey},
sp_io::SubstrateHostFunctions,
sp_runtime::Cow,
sp_storage::well_known_keys::CODE as CODE_KEY,
};
use sqlx::sqlite::SqlitePoolOptions;
use std::{sync::Arc, time::Duration};
use subxt::{
OnlineClient, backend::rpc::RpcClient, config::substrate::H256,
Metadata as SubxtMetadata, OnlineClient, backend::rpc::RpcClient,
client::RuntimeVersion as SubxtRuntimeVersion, config::substrate::H256,
ext::subxt_rpcs::LegacyRpcMethods, utils::H160,
};

Expand Down Expand Up @@ -411,7 +419,40 @@ impl ApiServer {

async fn create_revive_rpc_client(substrate_service: &Service) -> Result<EthRpcClient> {
let rpc_client = RpcClient::new(InMemoryRpcClient(substrate_service.rpc_handlers.clone()));
let api = OnlineClient::<SrcChainConfig>::from_rpc_client(rpc_client.clone()).await?;

// Using best_hash because genesis hash is only set if genesis block number is equal to 0, but
// Anvil allows a custom genesis block number.
// https://github.com/paritytech/polkadot-sdk/blob/62c3a80f913a272c4f5dba2c91320056d39ec68e/substrate/client/api/src/in_mem.rs#L178
let genesis_hash = substrate_service.client.chain_info().best_hash;

let runtime_version = substrate_service
.client
.runtime_version_at(genesis_hash)
.expect("Runtime version not found for given genesis hash");

Choose a reason for hiding this comment

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

Let's propagate the error instead of expect

Copy link
Author

Choose a reason for hiding this comment

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

Fixed in c5c7f47

let subxt_runtime_version = SubxtRuntimeVersion {
spec_version: runtime_version.spec_version,
transaction_version: runtime_version.transaction_version,
};

let code_bytes = substrate_service
.client
.storage(genesis_hash, &StorageKey(CODE_KEY.to_vec()))
.expect("Runtime code not found for given genesis hash")
.unwrap();

Choose a reason for hiding this comment

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

Please do proper error handling

Copy link
Author

Choose a reason for hiding this comment

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

Fixed in c5c7f47

let opaque_metadata = fetch_latest_metadata_from_code_blob(
&WasmExecutor::<SubstrateHostFunctions>::builder().build(),
Cow::Borrowed(code_bytes.0.as_slice()),
)
.map_err(|_| Error::InvalidParams("Unable to fetch metadata".to_string()))?;
let subxt_metadata = SubxtMetadata::decode(&mut (*opaque_metadata).as_slice())
.map_err(|_| Error::InvalidParams("Unable to decode metadata".to_string()))?;

let api = OnlineClient::<SrcChainConfig>::from_rpc_client_with(
genesis_hash,
subxt_runtime_version,
subxt_metadata,
rpc_client.clone(),
)?;
let rpc = LegacyRpcMethods::<SrcChainConfig>::new(rpc_client.clone());

let block_provider = SubxtBlockInfoProvider::new(api.clone(), rpc.clone()).await?;
Expand Down
6 changes: 4 additions & 2 deletions crates/anvil-polkadot/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::{
api_server::ApiHandle,
config::AnvilNodeConfig,
logging::{LoggingManager, NodeLogLayer},
substrate_node::service::Service,
substrate_node::{genesis::GenesisConfig, service::Service},
};
use clap::{CommandFactory, Parser};
use eyre::Result;
Expand Down Expand Up @@ -83,10 +83,12 @@ pub fn run_command(args: Anvil) -> Result<()> {
}
return Ok(());
}
let substrate_client = opts::SubstrateCli {};

let (anvil_config, substrate_config) = args.node.into_node_config()?;

let substrate_client =
opts::SubstrateCli { genesis_config: GenesisConfig::from(&anvil_config) };

let tokio_runtime = build_runtime()?;

let signals = tokio_runtime.block_on(async { sc_cli::Signals::capture() })?;
Expand Down
19 changes: 10 additions & 9 deletions crates/anvil-polkadot/src/opts.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use crate::{cmd::NodeArgs, substrate_node::chain_spec};
use crate::{
cmd::NodeArgs,
substrate_node::{chain_spec, genesis::GenesisConfig},
};
use clap::{Parser, Subcommand};
use foundry_cli::opts::GlobalArgs;
use foundry_common::version::{LONG_VERSION, SHORT_VERSION};
Expand Down Expand Up @@ -31,7 +34,10 @@ pub enum AnvilSubcommand {
GenerateFigSpec,
}

pub struct SubstrateCli;
pub struct SubstrateCli {
// Used to inject the anvil config into the chain spec
pub genesis_config: GenesisConfig,
}

// Implementation of the SubstrateCli, which enables us to launch an in-process substrate node.
impl sc_cli::SubstrateCli for SubstrateCli {
Expand Down Expand Up @@ -63,12 +69,7 @@ impl sc_cli::SubstrateCli for SubstrateCli {
"anvil-polkadot".into()
}

fn load_spec(&self, id: &str) -> std::result::Result<Box<dyn sc_service::ChainSpec>, String> {
Ok(match id {
"dev" | "" => Box::new(chain_spec::development_chain_spec()?),
path => {
Box::new(chain_spec::ChainSpec::from_json_file(std::path::PathBuf::from(path))?)
}
})
fn load_spec(&self, _: &str) -> std::result::Result<Box<dyn sc_service::ChainSpec>, String> {
Ok(Box::new(chain_spec::development_chain_spec(self.genesis_config.clone())?))
}
}
122 changes: 111 additions & 11 deletions crates/anvil-polkadot/src/substrate_node/chain_spec.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,105 @@
use crate::substrate_node::genesis::GenesisConfig;
use polkadot_sdk::{
sc_service::{self, ChainType, Properties},
sc_chain_spec::{ChainSpec, GetExtension},
sc_executor::HostFunctions,
sc_network::config::MultiaddrWithPeerId,
sc_service::{ChainType, GenericChainSpec, Properties},
sc_telemetry::TelemetryEndpoints,
sp_core::storage::Storage,
sp_genesis_builder,
sp_runtime::BuildStorage,
};
use substrate_runtime::WASM_BINARY;

/// This is a specialization of the general Substrate ChainSpec type.
pub type ChainSpec = sc_service::GenericChainSpec;
/// This is a wrapper around the general Substrate ChainSpec type that allows manual changes to the
/// genesis block.
#[derive(Clone)]
pub struct DevelopmentChainSpec<E = Option<()>, EHF = ()> {
inner: GenericChainSpec<E, EHF>,
genesis_config: GenesisConfig,
}

impl<E, EHF> BuildStorage for DevelopmentChainSpec<E, EHF>
where
EHF: HostFunctions,
GenericChainSpec<E, EHF>: BuildStorage,
{
fn assimilate_storage(&self, storage: &mut Storage) -> Result<(), String> {
self.inner.assimilate_storage(storage)?;
storage.top.extend(self.genesis_config.as_storage_key_value());
Ok(())
}
}

impl<E, EHF> ChainSpec for DevelopmentChainSpec<E, EHF>
where
E: GetExtension + serde::Serialize + Clone + Send + Sync + 'static,
EHF: HostFunctions,
{
fn boot_nodes(&self) -> &[MultiaddrWithPeerId] {
self.inner.boot_nodes()
}

fn name(&self) -> &str {
self.inner.name()
}

fn id(&self) -> &str {
self.inner.id()
}

fn chain_type(&self) -> ChainType {
self.inner.chain_type()
}

fn telemetry_endpoints(&self) -> &Option<TelemetryEndpoints> {
self.inner.telemetry_endpoints()
}

fn protocol_id(&self) -> Option<&str> {
self.inner.protocol_id()
}

fn fork_id(&self) -> Option<&str> {
self.inner.fork_id()
}

fn properties(&self) -> Properties {
self.inner.properties()
}

fn add_boot_node(&mut self, addr: MultiaddrWithPeerId) {
self.inner.add_boot_node(addr)
}

fn extensions(&self) -> &dyn GetExtension {
self.inner.extensions() as &dyn GetExtension
}

fn extensions_mut(&mut self) -> &mut dyn GetExtension {
self.inner.extensions_mut() as &mut dyn GetExtension
}

fn as_json(&self, raw: bool) -> Result<String, String> {
self.inner.as_json(raw)
}

fn as_storage_builder(&self) -> &dyn BuildStorage {
self
}

fn cloned_box(&self) -> Box<dyn ChainSpec> {
Box::new(Self { inner: self.inner.clone(), genesis_config: self.genesis_config.clone() })
}

fn set_storage(&mut self, storage: Storage) {
self.inner.set_storage(storage);
}

fn code_substitutes(&self) -> std::collections::BTreeMap<String, Vec<u8>> {
self.inner.code_substitutes()
}
}

fn props() -> Properties {
let mut properties = Properties::new();
Expand All @@ -14,12 +108,18 @@ fn props() -> Properties {
properties
}

pub fn development_chain_spec() -> Result<ChainSpec, String> {
Ok(ChainSpec::builder(WASM_BINARY.expect("Development wasm not available"), Default::default())
.with_name("Development")
.with_id("dev")
.with_chain_type(ChainType::Development)
.with_genesis_config_preset_name(sp_genesis_builder::DEV_RUNTIME_PRESET)
.with_properties(props())
.build())
pub fn development_chain_spec(
genesis_config: GenesisConfig,
) -> Result<DevelopmentChainSpec, String> {
let inner = GenericChainSpec::builder(
WASM_BINARY.expect("Development wasm not available"),
Default::default(),
)
.with_name("Development")
.with_id("dev")
.with_chain_type(ChainType::Development)
.with_genesis_config_preset_name(sp_genesis_builder::DEV_RUNTIME_PRESET)
.with_properties(props())
.build();
Ok(DevelopmentChainSpec { inner, genesis_config })
}
Loading
Loading