Skip to content
Merged
Show file tree
Hide file tree
Changes from 49 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" }
secp256k1 = { version = "0.28.0", default-features = false }
libsecp256k1 = { version = "0.7.0", default-features = false }
Expand All @@ -37,13 +38,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 @@ -131,10 +135,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
95 changes: 89 additions & 6 deletions crates/anvil-polkadot/src/api_server/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ use crate::{
logging::LoggingManager,
macros::node_info,
substrate_node::{
impersonation::ImpersonationManager, in_mem_rpc::InMemoryRpcClient,
mining_engine::MiningEngine, service::Service,
impersonation::ImpersonationManager,
in_mem_rpc::InMemoryRpcClient,
mining_engine::MiningEngine,
service::{Backend, Service},
},
};
use alloy_eips::{BlockId, BlockNumberOrTag};
Expand All @@ -19,6 +21,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, TransactionSigned},
Expand All @@ -27,12 +30,16 @@ use polkadot_sdk::{
client::{Client as EthRpcClient, ClientError, SubscriptionType},
subxt_client::{self, SrcChainConfig},
},
parachains_common::Hash,
sc_client_api::{Backend as _, HeaderBackend, StateBackend, TrieCacheContext},
sp_api::{Metadata, ProvideRuntimeApi},
sp_core::{self, keccak_256},
};
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 All @@ -43,6 +50,7 @@ pub struct Wallet {
pub struct ApiServer {
req_receiver: mpsc::Receiver<ApiRequest>,
logging_manager: LoggingManager,
backend: Arc<Backend>,
mining_engine: Arc<MiningEngine>,
eth_rpc_client: EthRpcClient,
wallet: Wallet,
Expand All @@ -61,6 +69,7 @@ impl ApiServer {
Ok(Self {
req_receiver,
logging_manager,
backend: substrate_service.backend.clone(),
mining_engine: substrate_service.mining_engine.clone(),
eth_rpc_client,
impersonation_manager,
Expand Down Expand Up @@ -257,12 +266,14 @@ impl ApiServer {
// Eth RPCs
fn eth_chain_id(&self) -> Result<U64> {
node_info!("eth_chainId");
Ok(U256::from(self.eth_rpc_client.chain_id()).to::<U64>())
let latest_block_hash = self.backend.blockchain().info().best_hash;
Ok(U256::from(self.chain_id(latest_block_hash)).to::<U64>())
}

fn network_id(&self) -> Result<u64> {
node_info!("eth_networkId");
Ok(self.eth_rpc_client.chain_id())
let latest_block_hash = self.backend.blockchain().info().best_hash;
Ok(self.chain_id(latest_block_hash))
Comment on lines +275 to +276

Choose a reason for hiding this comment

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

nit: use eth_chain_id()

Copy link
Author

Choose a reason for hiding this comment

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

This is not working right now when we override the chain id in genesis config.

}

fn net_listening(&self) -> Result<bool> {
Expand Down Expand Up @@ -446,11 +457,83 @@ impl ApiServer {
self.impersonation_manager.stop_impersonating(addr);
Ok(())
}

fn chain_id(&self, at: Hash) -> u64 {
let chain_id_key: [u8; 16] = [
149u8, 39u8, 54u8, 105u8, 39u8, 71u8, 142u8, 113u8, 13u8, 63u8, 127u8, 183u8, 124u8,
109u8, 31u8, 137u8,
];
if let Ok(state_at) = self.backend.state_at(at, TrieCacheContext::Trusted) {
if let Ok(Some(encoded_chain_id)) = state_at.storage(chain_id_key.as_slice()) {
if let Ok(chain_id) = u64::decode(&mut &encoded_chain_id[..]) {
return chain_id;
}
}
}
// if the chain id is not found, use the default chain id
self.eth_rpc_client.chain_id()
}
}

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?;

let genesis_block_number = substrate_service.genesis_block_number.try_into().map_err(|_| {
Error::InvalidParams(format!(
"Genesis block number {} is too large for u32 (max: {})",
substrate_service.genesis_block_number,
u32::MAX
))
})?;

let Some(genesis_hash) = substrate_service.client.hash(genesis_block_number).ok().flatten()
else {
return Err(Error::InvalidParams(format!(
"Genesis hash not found for genesis block number {}",
substrate_service.genesis_block_number
)));
};

let Ok(runtime_version) = substrate_service.client.runtime_version_at(genesis_hash) else {
return Err(Error::InvalidParams(
"Runtime version not found for given genesis hash".to_string(),
));
};

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

let Ok(supported_metadata_versions) =
substrate_service.client.runtime_api().metadata_versions(genesis_hash)
else {
return Err(Error::InvalidParams("Unable to fetch metadata versions".to_string()));

Choose a reason for hiding this comment

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

This and the subsequent errors feel more like an InternalError

Copy link
Author

Choose a reason for hiding this comment

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

Changed in 8915d00

};
let Some(latest_metadata_version) = supported_metadata_versions.into_iter().max() else {
return Err(Error::InvalidParams("No stable metadata versions supported".to_string()));
};
let opaque_metadata = substrate_service
.client
.runtime_api()
.metadata_at_version(genesis_hash, latest_metadata_version)
.map_err(|_| {
Error::InvalidParams("Failed to get runtime API for genesis hash".to_string())
})?
.ok_or_else(|| {
Error::InvalidParams(format!(
"Metadata not found for version {latest_metadata_version} at genesis hash"
))
})?;
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())?))
}
}
Loading
Loading