Skip to content

Commit 054953a

Browse files
authored
fix genesis customisation (#391)
* fix timestamp and block number genesis customisation
1 parent 338548c commit 054953a

File tree

8 files changed

+512
-347
lines changed

8 files changed

+512
-347
lines changed

Cargo.lock

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

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

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -612,28 +612,37 @@ impl ApiServer {
612612
async fn estimate_gas(
613613
&self,
614614
request: WithOtherFields<TransactionRequest>,
615-
block: Option<alloy_rpc_types::BlockId>,
615+
block: Option<BlockId>,
616616
) -> Result<sp_core::U256> {
617617
node_info!("eth_estimateGas");
618618

619619
let hash = self.get_block_hash_for_tag(block).await?;
620620
let runtime_api = self.eth_rpc_client.runtime_api(hash);
621-
let dry_run =
622-
runtime_api.dry_run(convert_to_generic_transaction(request.into_inner())).await?;
621+
let dry_run = runtime_api
622+
.dry_run(
623+
convert_to_generic_transaction(request.into_inner()),
624+
ReviveBlockId::from(block).inner(),
625+
)
626+
.await?;
623627
Ok(dry_run.eth_gas)
624628
}
625629

626630
async fn call(
627631
&self,
628632
request: WithOtherFields<TransactionRequest>,
629-
block: Option<alloy_rpc_types::BlockId>,
633+
block: Option<BlockId>,
630634
) -> Result<Bytes> {
631635
node_info!("eth_call");
632636

633637
let hash = self.get_block_hash_for_tag(block).await?;
638+
634639
let runtime_api = self.eth_rpc_client.runtime_api(hash);
635-
let dry_run =
636-
runtime_api.dry_run(convert_to_generic_transaction(request.into_inner())).await?;
640+
let dry_run = runtime_api
641+
.dry_run(
642+
convert_to_generic_transaction(request.into_inner()),
643+
ReviveBlockId::from(block).inner(),
644+
)
645+
.await?;
637646

638647
Ok(dry_run.data.into())
639648
}

crates/anvil-polkadot/src/config.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
use crate::{
2-
api_server::revive_conversions::ReviveAddress,
3-
substrate_node::chain_spec::keypairs_from_private_keys,
4-
};
1+
use crate::api_server::revive_conversions::ReviveAddress;
52
use alloy_genesis::Genesis;
63
use alloy_primitives::{Address, U256, hex, map::HashMap, utils::Unit};
74
use alloy_signer::Signer;
@@ -926,3 +923,15 @@ impl AccountGenerator {
926923
Ok(wallets)
927924
}
928925
}
926+
927+
fn keypairs_from_private_keys(
928+
accounts: &[PrivateKeySigner],
929+
) -> Result<Vec<Keypair>, subxt_signer::eth::Error> {
930+
accounts
931+
.iter()
932+
.map(|signer| {
933+
let key = Keypair::from_secret_key(signer.credential().to_bytes().into())?;
934+
Ok(key)
935+
})
936+
.collect()
937+
}

crates/anvil-polkadot/src/substrate_node/chain_spec.rs

Lines changed: 136 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,35 @@
11
use crate::substrate_node::genesis::GenesisConfig;
2-
use alloy_signer_local::PrivateKeySigner;
2+
use codec::{Decode, Encode};
33
use polkadot_sdk::{
4-
sc_chain_spec::{ChainSpec, GetExtension},
4+
sc_chain_spec::{ChainSpec, GetExtension, json_patch},
5+
sc_executor,
56
sc_executor::HostFunctions,
67
sc_network::config::MultiaddrWithPeerId,
78
sc_service::{ChainType, GenericChainSpec, Properties},
89
sc_telemetry::TelemetryEndpoints,
9-
sp_core::storage::Storage,
10+
sp_core::{
11+
storage::Storage,
12+
traits::{CallContext, CodeExecutor, Externalities, FetchRuntimeCode, RuntimeCode},
13+
},
14+
sp_genesis_builder::Result as BuildResult,
15+
sp_io::{self, hashing::blake2_256},
1016
sp_runtime::BuildStorage,
17+
sp_state_machine::BasicExternalities,
1118
};
12-
use substrate_runtime::WASM_BINARY;
13-
use subxt_signer::eth::Keypair;
19+
use serde_json::Value;
20+
use std::borrow::Cow;
21+
22+
pub fn development_chain_spec(
23+
genesis_config: GenesisConfig,
24+
) -> Result<DevelopmentChainSpec, String> {
25+
let inner = GenericChainSpec::builder(&genesis_config.code, Default::default())
26+
.with_name("Development")
27+
.with_id("dev")
28+
.with_chain_type(ChainType::Development)
29+
.with_properties(props())
30+
.build();
31+
Ok(DevelopmentChainSpec { inner, genesis_config })
32+
}
1433

1534
/// This is a wrapper around the general Substrate ChainSpec type that allows manual changes to the
1635
/// genesis block.
@@ -26,8 +45,20 @@ where
2645
GenericChainSpec<E, EHF>: BuildStorage,
2746
{
2847
fn assimilate_storage(&self, storage: &mut Storage) -> Result<(), String> {
29-
self.inner.assimilate_storage(storage)?;
3048
storage.top.extend(self.genesis_config.as_storage_key_value());
49+
50+
// We need to initialise the storage used when calling into the runtime for the genesis
51+
// config, so that the customised items (like block number and timestamp) will be
52+
// seen even in the code that processes the genesis config patch.
53+
let temp_storage = storage.clone();
54+
55+
GenesisBuilderRuntimeCaller::<EHF>::new(&self.genesis_config.code[..])
56+
.get_storage_for_patch(
57+
self.genesis_config.runtime_genesis_config_patch(),
58+
temp_storage,
59+
)?
60+
.assimilate_storage(storage)?;
61+
3162
Ok(())
3263
}
3364
}
@@ -105,35 +136,108 @@ where
105136
fn props() -> Properties {
106137
let mut properties = Properties::new();
107138
properties.insert("tokenDecimals".to_string(), 12.into());
108-
properties.insert("tokenSymbol".to_string(), "MINI".into());
139+
properties.insert("tokenSymbol".to_string(), "DOT".into());
109140
properties
110141
}
111142

112-
pub fn development_chain_spec(
113-
genesis_config: GenesisConfig,
114-
) -> Result<DevelopmentChainSpec, String> {
115-
let inner = GenericChainSpec::builder(
116-
WASM_BINARY.expect("Development wasm not available"),
117-
Default::default(),
118-
)
119-
.with_name("Development")
120-
.with_id("dev")
121-
.with_chain_type(ChainType::Development)
122-
.with_genesis_config_patch(genesis_config.runtime_genesis_config_patch())
123-
.with_properties(props())
124-
.build();
125-
Ok(DevelopmentChainSpec { inner, genesis_config })
143+
// This mostly copies the upstream `GenesisConfigBuilderRuntimeCaller`, but with the ability of
144+
// injecting genesis state even before the genesis config builders in the runtime are run via
145+
// `GenesisBuilder_build_state`
146+
struct GenesisBuilderRuntimeCaller<'a, EHF = ()>
147+
where
148+
EHF: HostFunctions,
149+
{
150+
code: Cow<'a, [u8]>,
151+
code_hash: Vec<u8>,
152+
executor: sc_executor::WasmExecutor<(sp_io::SubstrateHostFunctions, EHF)>,
126153
}
127154

128-
pub fn keypairs_from_private_keys(
129-
accounts: &[PrivateKeySigner],
130-
) -> Result<Vec<Keypair>, subxt_signer::eth::Error> {
131-
accounts
132-
.iter()
133-
.map(|signer| {
134-
let key =
135-
subxt_signer::eth::Keypair::from_secret_key(signer.credential().to_bytes().into())?;
136-
Ok(key)
137-
})
138-
.collect()
155+
impl<'a, EHF> FetchRuntimeCode for GenesisBuilderRuntimeCaller<'a, EHF>
156+
where
157+
EHF: HostFunctions,
158+
{
159+
fn fetch_runtime_code(&self) -> Option<Cow<'_, [u8]>> {
160+
Some(self.code.as_ref().into())
161+
}
162+
}
163+
164+
impl<'a, EHF> GenesisBuilderRuntimeCaller<'a, EHF>
165+
where
166+
EHF: HostFunctions,
167+
{
168+
fn new(code: &'a [u8]) -> Self {
169+
GenesisBuilderRuntimeCaller {
170+
code: code.into(),
171+
code_hash: blake2_256(code).to_vec(),
172+
executor: sc_executor::WasmExecutor::<(sp_io::SubstrateHostFunctions, EHF)>::builder()
173+
.with_allow_missing_host_functions(true)
174+
.build(),
175+
}
176+
}
177+
178+
fn get_storage_for_patch(
179+
&self,
180+
patch: Value,
181+
genesis_storage: Storage,
182+
) -> core::result::Result<Storage, String> {
183+
let mut config = self.get_named_preset(None)?;
184+
json_patch::merge(&mut config, patch);
185+
self.get_storage_for_config(config, genesis_storage)
186+
}
187+
188+
fn call(
189+
&self,
190+
ext: &mut dyn Externalities,
191+
method: &str,
192+
data: &[u8],
193+
) -> sc_executor::error::Result<Vec<u8>> {
194+
self.executor
195+
.call(
196+
ext,
197+
&RuntimeCode { heap_pages: None, code_fetcher: self, hash: self.code_hash.clone() },
198+
method,
199+
data,
200+
CallContext::Offchain,
201+
)
202+
.0
203+
}
204+
205+
fn get_named_preset(&self, id: Option<&String>) -> core::result::Result<Value, String> {
206+
let mut t = BasicExternalities::new_empty();
207+
let call_result = self
208+
.call(&mut t, "GenesisBuilder_get_preset", &id.encode())
209+
.map_err(|e| format!("wasm call error {e}"))?;
210+
211+
let named_preset = Option::<Vec<u8>>::decode(&mut &call_result[..])
212+
.map_err(|e| format!("scale codec error: {e}"))?;
213+
214+
if let Some(named_preset) = named_preset {
215+
Ok(serde_json::from_slice(&named_preset[..]).expect("returned value is json. qed."))
216+
} else {
217+
Err(format!("The preset with name {id:?} is not available."))
218+
}
219+
}
220+
221+
fn get_storage_for_config(
222+
&self,
223+
config: Value,
224+
genesis_storage: Storage,
225+
) -> core::result::Result<Storage, String> {
226+
// This is the key difference compared to the upstream variant, we don't initialise the
227+
// storage as empty.
228+
let mut ext = BasicExternalities::new(genesis_storage);
229+
230+
let json_pretty_str = serde_json::to_string_pretty(&config)
231+
.map_err(|e| format!("json to string failed: {e}"))?;
232+
233+
let call_result = self
234+
.call(&mut ext, "GenesisBuilder_build_state", &json_pretty_str.encode())
235+
.map_err(|e| format!("wasm call error {e}"))?;
236+
237+
BuildResult::decode(&mut &call_result[..])
238+
.map_err(|e| format!("scale codec error: {e}"))?
239+
.map_err(|e| format!("{e} for blob:\n{json_pretty_str}"))?;
240+
241+
Ok(ext.into_storages())
242+
}
139243
}

crates/anvil-polkadot/src/substrate_node/genesis.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use polkadot_sdk::{
1313
sc_client_api::{BlockImportOperation, backend::Backend},
1414
sc_executor::RuntimeVersionOf,
1515
sp_blockchain,
16-
sp_core::{H160, storage::Storage},
16+
sp_core::{self, H160, storage::Storage},
1717
sp_runtime::{
1818
BuildStorage,
1919
traits::{Block as BlockT, Hash as HashT, HashingFor, Header as HeaderT},
@@ -22,6 +22,7 @@ use polkadot_sdk::{
2222
use serde::{Deserialize, Serialize};
2323
use serde_json::{Value, json};
2424
use std::{collections::BTreeMap, marker::PhantomData, sync::Arc};
25+
use substrate_runtime::WASM_BINARY;
2526
use subxt_signer::eth::Keypair;
2627

2728
/// Genesis settings
@@ -46,6 +47,8 @@ pub struct GenesisConfig {
4647
pub genesis_balance: U256,
4748
/// Coinbase address
4849
pub coinbase: Option<Address>,
50+
/// Substrate runtime code
51+
pub code: Vec<u8>,
4952
}
5053

5154
impl<'a> From<&'a AnvilNodeConfig> for GenesisConfig {
@@ -67,6 +70,7 @@ impl<'a> From<&'a AnvilNodeConfig> for GenesisConfig {
6770
genesis_accounts: anvil_config.genesis_accounts.clone(),
6871
genesis_balance: anvil_config.genesis_balance,
6972
coinbase: anvil_config.genesis.as_ref().map(|g| g.coinbase),
73+
code: WASM_BINARY.expect("Development wasm not available").to_vec(),
7074
}
7175
}
7276
}
@@ -94,8 +98,8 @@ impl GenesisConfig {
9498
(well_known_keys::TIMESTAMP.to_vec(), self.timestamp.encode()),
9599
(well_known_keys::BLOCK_NUMBER_KEY.to_vec(), self.number.encode()),
96100
(well_known_keys::AURA_AUTHORITIES.to_vec(), vec![aura_authority_id].encode()),
101+
(sp_core::storage::well_known_keys::CODE.to_vec(), self.code.clone()),
97102
];
98-
// TODO: add other fields
99103
storage
100104
}
101105

@@ -185,7 +189,7 @@ impl<Block: BlockT, B: Backend<Block>, E: RuntimeVersionOf>
185189
)
186190
}
187191

188-
pub fn new_with_storage(
192+
fn new_with_storage(
189193
genesis_number: u64,
190194
genesis_storage: Storage,
191195
commit_genesis_state: bool,

crates/anvil-polkadot/tests/it/genesis.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,16 @@ async fn test_genesis_params() {
3232

3333
// Check that block number, timestamp, and chain id are set correctly at genesis
3434
assert_eq!(node.best_block_number().await, genesis_block_number);
35+
assert_eq!(node.eth_best_block().await.number.as_u32(), genesis_block_number);
36+
3537
let genesis_hash = node.block_hash_by_number(genesis_block_number).await.unwrap();
3638
// Anvil genesis timestamp is in seconds, while Substrate timestamp is in milliseconds.
3739
let genesis_timestamp = anvil_genesis_timestamp.checked_mul(1000).unwrap();
3840
let actual_genesis_timestamp = node.get_decoded_timestamp(Some(genesis_hash)).await;
3941
assert_eq!(actual_genesis_timestamp, genesis_timestamp);
42+
let eth_genesis_timestamp = node.get_eth_timestamp(Some(genesis_hash)).await;
43+
assert_eq!(anvil_genesis_timestamp, eth_genesis_timestamp);
44+
4045
let current_chain_id_hex =
4146
unwrap_response::<String>(node.eth_rpc(EthRequest::EthChainId(())).await.unwrap()).unwrap();
4247
assert_eq!(current_chain_id_hex, to_hex_string(chain_id));
@@ -49,8 +54,14 @@ async fn test_genesis_params() {
4954

5055
let latest_block_number = node.best_block_number().await;
5156
assert_eq!(latest_block_number, genesis_block_number + 2);
57+
tokio::time::sleep(Duration::from_millis(400)).await;
58+
assert_eq!(node.eth_best_block().await.number.as_u32(), genesis_block_number + 2);
59+
5260
let hash2 = node.block_hash_by_number(genesis_block_number + 2).await.unwrap();
5361
let timestamp2 = node.get_decoded_timestamp(Some(hash2)).await;
62+
let eth_timestamp2 = node.get_eth_timestamp(Some(hash2)).await;
63+
assert_eq!(eth_timestamp2, timestamp2 / 1000);
64+
5465
assert_with_tolerance(
5566
timestamp2.saturating_sub(genesis_timestamp),
5667
2000,
@@ -285,6 +296,8 @@ async fn test_genesis_json() {
285296
"Genesis block number should match the one in genesis.json"
286297
);
287298

299+
assert_eq!(node.eth_best_block().await.number.as_u64(), expected_block_number);
300+
288301
// Test timestamp
289302
let genesis_hash = node.block_hash_by_number(genesis_block_number).await.unwrap();
290303
// Anvil genesis timestamp is in seconds, while Substrate timestamp is in milliseconds
@@ -295,6 +308,9 @@ async fn test_genesis_json() {
295308
"Genesis timestamp should match the one in genesis.json"
296309
);
297310

311+
let eth_genesis_timestamp = node.get_eth_timestamp(Some(genesis_hash)).await;
312+
assert_eq!(expected_timestamp, eth_genesis_timestamp);
313+
298314
// Test coinbase
299315
let coinbase =
300316
unwrap_response::<Address>(node.eth_rpc(EthRequest::EthCoinbase(())).await.unwrap())

0 commit comments

Comments
 (0)