-
Notifications
You must be signed in to change notification settings - Fork 6
Create a ChainSpec Wrapper to manage Anvil genesis config #293
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
EDIT: actually, I don't see it's producing blocks, what I was seeing was probably the anvil config printed as string, not the genesis block storage. So I would say it's not working yet |
After Alex's block production PR, we no longer start producing blocks by default. You either need to configure it via RPC or CLI. A simple way is to use the |
} | ||
|
||
#[cfg(test)] | ||
mod tests { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these tests are not really useful
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed in 2ff0438 to test for key-value storage translation, can they be useful now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should be using the hardcoded variants as defined in this module. After all, this is what we use in reality
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's also do this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in e1abde2
I tried customising the genesis block number but without success. When querying the numer from polkadot.js, I get Anyway, setting it in the genesis state is not going to be enough. The block builder uses the block number from the header of the genesis block (not from the runtime state). I think we need to implement another |
And for timestamp, we also need to modify the timestamp of the mining engine (similar to what the setTime RPC is doing), so that the next blocks will be based on this timestamp (otherwise they'll just jump right back to the present moment on the next block). CC: @AlexandruCihodaru |
Yeh, looks like block 1 references current time. I don't think we're able to tell what timestamp does block 0 reference, with polkadotjs (no extrinsic shows up in it, like in block 1 case, and the header doesn't seem to show this info either). Would be nice. Not sure how subscan shows it for the production networks. |
I am thinking we should set the genesis block number in the genesis block header too. Not sure though how doable it is. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, nice work! Would be great to have also the chain id tested like Alin mentioned offline (which IIUC should be easily doable).
Co-authored-by: Alin Dima <[email protected]>
let time_manager = | ||
Arc::new(TimeManager::new_with_milliseconds(sp_timestamp::Timestamp::current().into())); | ||
let time_manager = Arc::new(TimeManager::new_with_milliseconds( | ||
sp_timestamp::Timestamp::from(anvil_config.get_genesis_timestamp()).into(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
get_genesis_timestamp will return the current time as seconds, but we create the TimeManager based on milliseconds.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had to use --block-time 3
for 3 seconds block interval. The CLI and the behavior during testing corresponds. Somewhere a seconds to milliseconds conversion must be done I think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is done, all parameters to RPCs are converted to milliseconds by the TimeManager methods.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! Changed in 90da646
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
& fixed accordingly in 7221e82
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! Nice work!
Approving, modulo Alex's comment and let's make test_genesis less flaky in the CI
impl<'a> From<&'a AnvilNodeConfig> for GenesisConfig { | ||
fn from(anvil_config: &'a AnvilNodeConfig) -> Self { | ||
Self { | ||
chain_id: anvil_config.get_chain_id(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should also update standard_rpc::test_get_chain_id
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, we should properly implement the get_chain_id RPC to read the state instead of returning the hardcoded constant
let genesis_hash = node.block_hash_by_number(genesis_block_number).await.unwrap(); | ||
let actual_genesis_timestamp = node.get_decoded_timestamp(Some(genesis_hash)).await; | ||
// Anvil genesis timestamp is in seconds, while Substrate timestamp is in milliseconds. | ||
assert_eq!(actual_genesis_timestamp, genesis_timestamp * 1000); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: use checked_mul
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed in fcf200b
|
||
#[tokio::test(flavor = "multi_thread")] | ||
async fn test_genesis() { | ||
let genesis_block_number: u32 = 1000; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When creating the OnlineClient in create_revive_rpc_client
let api = OnlineClient::<SrcChainConfig>::from_rpc_client(rpc_client.clone()).await?; |
https://github.com/paritytech/subxt/blob/884dfe6c69753584660f53352da636f9d0706631/rpcs/src/methods/legacy.rs#L89-L94
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is there a problem with this? Did you run into any issues?
If not, then that's fine. We shouldn't need to call this for our use case
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if we set the genesis number to 1000 as in the test, we will not be able to create the OnlineClient because it calls the get_genesis from subxt that I linked above, which assumes that the genesis number is zero
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah I see. I think there's an easy workaround. Instead of calling OnlineClient::<SrcChainConfig>::from_rpc_client
we should call from_rpc_client_with()
and pass the genesis hash as returned by our local backend
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed in fcf200b
// Anvil genesis timestamp is in seconds, while Substrate timestamp is in milliseconds. | ||
timestamp: anvil_config.get_genesis_timestamp() * 1000, | ||
alloc: anvil_config.genesis.as_ref().map(|g| g.alloc.clone()), | ||
number: anvil_config.get_genesis_number() as u32, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could fail 🤷
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added expect
in e1abde2
Self { | ||
chain_id: anvil_config.get_chain_id(), | ||
// Anvil genesis timestamp is in seconds, while Substrate timestamp is in milliseconds. | ||
timestamp: anvil_config.get_genesis_timestamp() * 1000, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe use checked_mul here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed in e1abde2
.client | ||
.storage(genesis_hash, &StorageKey(CODE_KEY.to_vec())) | ||
.expect("Runtime code not found for given genesis hash") | ||
.unwrap(); |
There was a problem hiding this comment.
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
let runtime_version = substrate_service | ||
.client | ||
.runtime_version_at(genesis_hash) | ||
.expect("Runtime version not found for given genesis hash"); |
There was a problem hiding this comment.
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
assert_eq!(current_chain_id, chain_id); | ||
|
||
// Manually mine two blocks and force the timestamp to be increasing with 1 second each time. | ||
unwrap_response::<()>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The timestamp of the second block will include everything that happens from when the TimeManager is created until the inherent is created, so maybe take a larger tolerance in the assert
let time_manager = | ||
Arc::new(TimeManager::new_with_milliseconds(sp_timestamp::Timestamp::current().into())); | ||
let time_manager = Arc::new(TimeManager::new_with_milliseconds( | ||
sp_timestamp::Timestamp::from(1000 * anvil_config.get_genesis_timestamp()).into(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: checked_mul
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed in e1abde2
let api = OnlineClient::<SrcChainConfig>::from_rpc_client_with( | ||
genesis_hash, | ||
subxt_runtime_version, | ||
subxt_metadata, | ||
rpc_client.clone(), | ||
)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: move the creation of the OnlineClient in a separate method
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it belongs here, we're after all only creating this for the purposes of the revive rpc client
// 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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will probably work in most cases but it's a bit racy.
The genesis hash here may be different if the node is configured to mine blocks very frequently, it could get to mine a block before we retrieve the first block's hash here.
The clean solution would be to retrieve the block hash of the genesis block number (which we know).
Let's do this instead
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, the genesis_hash set in from_rpc_client_with
will probably need to be equal to the zeroed hash (which is what the client returns).
AFAIK the extrinsic payload contains the genesis hash as well (being constructed by subxt), so the runtime will reject the transaction if the hash is different.
Let's add a transaction to the integration test and see what happens and work from there
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is why I see tests failing in CI with:
called `Result::unwrap()` on an `Err` value: "Expected success but got error: RpcError { code: InternalError, message: \"Client error: RPC error: RPC error: client error: ErrorObject { code: ServerError(1010), message: \\\"Invalid Transaction\\\", data: Some(RawValue(\\\"Transaction call is not expected\\\")) }\", data: None }"
use anvil_polkadot::config::{AnvilNodeConfig, SubstrateNodeConfig}; | ||
|
||
#[tokio::test(flavor = "multi_thread")] | ||
async fn test_genesis() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should also add a test for configuring via json file, but this can come later, in the next PR if you want
Support genesis configuration customization for
anvil-polkadot
.This is implemented through a wrapper struct around
ChainSpec
, calledDevelopmentChainSpec
, which re-implementsassimilate_storage
for theBuildStorage
trait to override genesis storage for customizable items: chain id, block number, and timestamp.For reference, also see #260 (comment)
Notice that there are still some missing genesis storage items to override: alloc (accounts with code/balances), coinbase (block author), gas settings.