Skip to content

Commit cdc9c0e

Browse files
re-giusalindima
andauthored
Create a ChainSpec Wrapper to manage Anvil genesis config (paritytech#293)
* draft chainspec wrapper * inject Anvil Node Config into Substrate CLI * Set chain_id from config.json * Add extra fields * inject storage items into ChainSpec * implement ChainSpec trait for wrapper * fix clippy * CR reorg * set System::Number as u32 * nit * nit * Add genesis timestamp to time manager of mining node * add custom genesis block number support in RPC * fmt * fmt * clippy + comments * CR nits + reorg * Add genesis integration tests (chain ID missing) * comment nit Co-authored-by: Alin Dima <[email protected]> * add chain id test * fix time manager creation * changed genesis milliseconds mismatch + clippy for unused import * undo clippy change * merge fix - missing import * fix rpc client creation * clippy nit * fix * CR fixes * fix types * nit * Use genesis block number to compute genesis hash * improve error handling * fix metadata retrieval * use the impersonation executor * use latest available metadata + error handling * add Chain Id RPC * nit * fmt + clippy * add `InternalError` variant * make test less flaky * fix test with chain id RPC * Use chain id RPC in genesis tests * fix deny license --------- Co-authored-by: Alin Dima <[email protected]> Co-authored-by: alindima <[email protected]>
1 parent 9267395 commit cdc9c0e

File tree

16 files changed

+796
-125
lines changed

16 files changed

+796
-125
lines changed

Cargo.lock

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/anvil-polkadot/Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ path = "bin/main.rs"
1919

2020
[dependencies]
2121
# foundry internal
22+
codec = { version = "3.7.5", default-features = true, package = "parity-scale-codec" }
2223
substrate-runtime = { path = "substrate-runtime" }
2324
secp256k1 = { version = "0.28.0", default-features = false }
2425
libsecp256k1 = { version = "0.7.0", default-features = false }
@@ -37,13 +38,16 @@ polkadot-sdk = { git = "https://github.com/paritytech/polkadot-sdk.git", branch
3738
"sc-executor-common",
3839
"sc-executor-wasmtime",
3940
"sc-keystore",
41+
"sc-network",
4042
"sc-network-types",
4143
"sc-rpc",
4244
"sc-rpc-api",
4345
"sc-rpc-server",
4446
"sc-rpc-spec-v2",
47+
"sc-runtime-utilities",
4548
"sc-service",
4649
"sc-state-db",
50+
"sc-telemetry",
4751
"sc-tracing",
4852
"sc-transaction-pool",
4953
"sc-transaction-pool-api",
@@ -131,10 +135,10 @@ clap = { version = "4", features = [
131135
clap_complete = { version = "4" }
132136
chrono.workspace = true
133137
clap_complete_fig = "4"
134-
parity-scale-codec = "3.7.5"
135138
subxt = "0.43.0"
136139
subxt-signer = "0.43.0"
137140
tokio-stream = "0.1.17"
141+
jsonrpsee = "0.24.9"
138142
sqlx = "0.8.6"
139143

140144
[dev-dependencies]

crates/anvil-polkadot/src/api_server/error.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ pub enum Error {
1313
InvalidParams(String),
1414
#[error("Revive call failed: {0}")]
1515
ReviveRpc(#[from] EthRpcError),
16+
#[error("Internal error: {0}")]
17+
InternalError(String),
1618
}
1719
impl From<subxt::Error> for Error {
1820
fn from(err: subxt::Error) -> Self {
@@ -72,6 +74,9 @@ impl<T: Serialize> ToRpcResponseResult for Result<T> {
7274
Error::ReviveRpc(client_error) => {
7375
RpcError::internal_error_with(format!("{client_error}")).into()
7476
}
77+
Error::InternalError(error_message) => {
78+
RpcError::internal_error_with(error_message).into()
79+
}
7580
},
7681
}
7782
}

crates/anvil-polkadot/src/api_server/server.rs

Lines changed: 89 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ use crate::{
99
logging::LoggingManager,
1010
macros::node_info,
1111
substrate_node::{
12-
impersonation::ImpersonationManager, in_mem_rpc::InMemoryRpcClient,
13-
mining_engine::MiningEngine, service::Service,
12+
impersonation::ImpersonationManager,
13+
in_mem_rpc::InMemoryRpcClient,
14+
mining_engine::MiningEngine,
15+
service::{Backend, Service},
1416
},
1517
};
1618
use alloy_eips::{BlockId, BlockNumberOrTag};
@@ -19,6 +21,7 @@ use alloy_rpc_types::{TransactionRequest, anvil::MineOptions};
1921
use alloy_serde::WithOtherFields;
2022
use anvil_core::eth::{EthRequest, Params as MineParams};
2123
use anvil_rpc::response::ResponseResult;
24+
use codec::Decode;
2225
use futures::{StreamExt, channel::mpsc};
2326
use polkadot_sdk::{
2427
pallet_revive::evm::{Account, Block, Bytes, ReceiptInfo, TransactionSigned},
@@ -27,12 +30,16 @@ use polkadot_sdk::{
2730
client::{Client as EthRpcClient, ClientError, SubscriptionType},
2831
subxt_client::{self, SrcChainConfig},
2932
},
33+
parachains_common::Hash,
34+
sc_client_api::{Backend as _, HeaderBackend, StateBackend, TrieCacheContext},
35+
sp_api::{Metadata, ProvideRuntimeApi},
3036
sp_core::{self, keccak_256},
3137
};
3238
use sqlx::sqlite::SqlitePoolOptions;
3339
use std::{sync::Arc, time::Duration};
3440
use subxt::{
35-
OnlineClient, backend::rpc::RpcClient, config::substrate::H256,
41+
Metadata as SubxtMetadata, OnlineClient, backend::rpc::RpcClient,
42+
client::RuntimeVersion as SubxtRuntimeVersion, config::substrate::H256,
3643
ext::subxt_rpcs::LegacyRpcMethods, utils::H160,
3744
};
3845

@@ -43,6 +50,7 @@ pub struct Wallet {
4350
pub struct ApiServer {
4451
req_receiver: mpsc::Receiver<ApiRequest>,
4552
logging_manager: LoggingManager,
53+
backend: Arc<Backend>,
4654
mining_engine: Arc<MiningEngine>,
4755
eth_rpc_client: EthRpcClient,
4856
wallet: Wallet,
@@ -61,6 +69,7 @@ impl ApiServer {
6169
Ok(Self {
6270
req_receiver,
6371
logging_manager,
72+
backend: substrate_service.backend.clone(),
6473
mining_engine: substrate_service.mining_engine.clone(),
6574
eth_rpc_client,
6675
impersonation_manager,
@@ -257,12 +266,14 @@ impl ApiServer {
257266
// Eth RPCs
258267
fn eth_chain_id(&self) -> Result<U64> {
259268
node_info!("eth_chainId");
260-
Ok(U256::from(self.eth_rpc_client.chain_id()).to::<U64>())
269+
let latest_block_hash = self.backend.blockchain().info().best_hash;
270+
Ok(U256::from(self.chain_id(latest_block_hash)).to::<U64>())
261271
}
262272

263273
fn network_id(&self) -> Result<u64> {
264274
node_info!("eth_networkId");
265-
Ok(self.eth_rpc_client.chain_id())
275+
let latest_block_hash = self.backend.blockchain().info().best_hash;
276+
Ok(self.chain_id(latest_block_hash))
266277
}
267278

268279
fn net_listening(&self) -> Result<bool> {
@@ -446,11 +457,83 @@ impl ApiServer {
446457
self.impersonation_manager.stop_impersonating(addr);
447458
Ok(())
448459
}
460+
461+
fn chain_id(&self, at: Hash) -> u64 {
462+
let chain_id_key: [u8; 16] = [
463+
149u8, 39u8, 54u8, 105u8, 39u8, 71u8, 142u8, 113u8, 13u8, 63u8, 127u8, 183u8, 124u8,
464+
109u8, 31u8, 137u8,
465+
];
466+
if let Ok(state_at) = self.backend.state_at(at, TrieCacheContext::Trusted)
467+
&& let Ok(Some(encoded_chain_id)) = state_at.storage(chain_id_key.as_slice())
468+
&& let Ok(chain_id) = u64::decode(&mut &encoded_chain_id[..])
469+
{
470+
return chain_id;
471+
}
472+
473+
// if the chain id is not found, use the default chain id
474+
self.eth_rpc_client.chain_id()
475+
}
449476
}
450477

451478
async fn create_revive_rpc_client(substrate_service: &Service) -> Result<EthRpcClient> {
452479
let rpc_client = RpcClient::new(InMemoryRpcClient(substrate_service.rpc_handlers.clone()));
453-
let api = OnlineClient::<SrcChainConfig>::from_rpc_client(rpc_client.clone()).await?;
480+
481+
let genesis_block_number = substrate_service.genesis_block_number.try_into().map_err(|_| {
482+
Error::InternalError(format!(
483+
"Genesis block number {} is too large for u32 (max: {})",
484+
substrate_service.genesis_block_number,
485+
u32::MAX
486+
))
487+
})?;
488+
489+
let Some(genesis_hash) = substrate_service.client.hash(genesis_block_number).ok().flatten()
490+
else {
491+
return Err(Error::InternalError(format!(
492+
"Genesis hash not found for genesis block number {}",
493+
substrate_service.genesis_block_number
494+
)));
495+
};
496+
497+
let Ok(runtime_version) = substrate_service.client.runtime_version_at(genesis_hash) else {
498+
return Err(Error::InternalError(
499+
"Runtime version not found for given genesis hash".to_string(),
500+
));
501+
};
502+
503+
let subxt_runtime_version = SubxtRuntimeVersion {
504+
spec_version: runtime_version.spec_version,
505+
transaction_version: runtime_version.transaction_version,
506+
};
507+
508+
let Ok(supported_metadata_versions) =
509+
substrate_service.client.runtime_api().metadata_versions(genesis_hash)
510+
else {
511+
return Err(Error::InternalError("Unable to fetch metadata versions".to_string()));
512+
};
513+
let Some(latest_metadata_version) = supported_metadata_versions.into_iter().max() else {
514+
return Err(Error::InternalError("No stable metadata versions supported".to_string()));
515+
};
516+
let opaque_metadata = substrate_service
517+
.client
518+
.runtime_api()
519+
.metadata_at_version(genesis_hash, latest_metadata_version)
520+
.map_err(|_| {
521+
Error::InternalError("Failed to get runtime API for genesis hash".to_string())
522+
})?
523+
.ok_or_else(|| {
524+
Error::InternalError(format!(
525+
"Metadata not found for version {latest_metadata_version} at genesis hash"
526+
))
527+
})?;
528+
let subxt_metadata = SubxtMetadata::decode(&mut (*opaque_metadata).as_slice())
529+
.map_err(|_| Error::InternalError("Unable to decode metadata".to_string()))?;
530+
531+
let api = OnlineClient::<SrcChainConfig>::from_rpc_client_with(
532+
genesis_hash,
533+
subxt_runtime_version,
534+
subxt_metadata,
535+
rpc_client.clone(),
536+
)?;
454537
let rpc = LegacyRpcMethods::<SrcChainConfig>::new(rpc_client.clone());
455538

456539
let block_provider = SubxtBlockInfoProvider::new(api.clone(), rpc.clone()).await?;

crates/anvil-polkadot/src/lib.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::{
66
api_server::ApiHandle,
77
config::AnvilNodeConfig,
88
logging::{LoggingManager, NodeLogLayer},
9-
substrate_node::service::Service,
9+
substrate_node::{genesis::GenesisConfig, service::Service},
1010
};
1111
use clap::{CommandFactory, Parser};
1212
use eyre::Result;
@@ -83,10 +83,12 @@ pub fn run_command(args: Anvil) -> Result<()> {
8383
}
8484
return Ok(());
8585
}
86-
let substrate_client = opts::SubstrateCli {};
8786

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

89+
let substrate_client =
90+
opts::SubstrateCli { genesis_config: GenesisConfig::from(&anvil_config) };
91+
9092
let tokio_runtime = build_runtime()?;
9193

9294
let signals = tokio_runtime.block_on(async { sc_cli::Signals::capture() })?;

crates/anvil-polkadot/src/opts.rs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
use crate::{cmd::NodeArgs, substrate_node::chain_spec};
1+
use crate::{
2+
cmd::NodeArgs,
3+
substrate_node::{chain_spec, genesis::GenesisConfig},
4+
};
25
use clap::{Parser, Subcommand};
36
use foundry_cli::opts::GlobalArgs;
47
use foundry_common::version::{LONG_VERSION, SHORT_VERSION};
@@ -31,7 +34,10 @@ pub enum AnvilSubcommand {
3134
GenerateFigSpec,
3235
}
3336

34-
pub struct SubstrateCli;
37+
pub struct SubstrateCli {
38+
// Used to inject the anvil config into the chain spec
39+
pub genesis_config: GenesisConfig,
40+
}
3541

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

66-
fn load_spec(&self, id: &str) -> std::result::Result<Box<dyn sc_service::ChainSpec>, String> {
67-
Ok(match id {
68-
"dev" | "" => Box::new(chain_spec::development_chain_spec()?),
69-
path => {
70-
Box::new(chain_spec::ChainSpec::from_json_file(std::path::PathBuf::from(path))?)
71-
}
72-
})
72+
fn load_spec(&self, _: &str) -> std::result::Result<Box<dyn sc_service::ChainSpec>, String> {
73+
Ok(Box::new(chain_spec::development_chain_spec(self.genesis_config.clone())?))
7374
}
7475
}

0 commit comments

Comments
 (0)