Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Commit

Permalink
try-runtime::fast-forward
Browse files Browse the repository at this point in the history
  • Loading branch information
pmikolajczyk41 committed Dec 15, 2022
1 parent cd2fdcf commit 63ea95b
Show file tree
Hide file tree
Showing 6 changed files with 351 additions and 6 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

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

28 changes: 27 additions & 1 deletion bin/node-template/node/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ use frame_benchmarking_cli::{BenchmarkCmd, ExtrinsicFactory, SUBSTRATE_REFERENCE
use node_template_runtime::{Block, EXISTENTIAL_DEPOSIT};
use sc_cli::{ChainSpec, RuntimeVersion, SubstrateCli};
use sc_service::PartialComponents;
use sp_consensus_aura::{Slot, SlotDuration, AURA_ENGINE_ID};
use sp_core::Encode;
use sp_inherents::InherentData;
use sp_keyring::Sr25519Keyring;
use sp_runtime::{Digest, DigestItem};
use sp_timestamp::TimestampInherentData;

impl SubstrateCli for Cli {
fn impl_name() -> String {
Expand Down Expand Up @@ -184,11 +189,32 @@ pub fn run() -> sc_cli::Result<()> {
let task_manager =
sc_service::TaskManager::new(config.tokio_handle.clone(), registry)
.map_err(|e| sc_cli::Error::Service(sc_service::Error::Prometheus(e)))?;

let info_provider = |_, maybe_prev_info: Option<(InherentData, Digest)>| async {
const BLOCKTIME_MILLIS: u64 = 2 * 3_000;

let timestamp_idp = match maybe_prev_info {
Some((inherent_data, _)) => sp_timestamp::InherentDataProvider::new(
inherent_data.timestamp_inherent_data().unwrap().unwrap() +
BLOCKTIME_MILLIS,
),
None => sp_timestamp::InherentDataProvider::from_system_time(),
};

let slot = Slot::from_timestamp(
*timestamp_idp,
SlotDuration::from_millis(BLOCKTIME_MILLIS),
);
let digest = vec![DigestItem::PreRuntime(AURA_ENGINE_ID, slot.encode())];

Ok((timestamp_idp, digest))
};

Ok((
cmd.run::<Block, ExtendedHostFunctions<
sp_io::SubstrateHostFunctions,
<ExecutorDispatch as NativeExecutionDispatch>::ExtendHostFunctions,
>>(),
>, _>(Some(info_provider)),
task_manager,
))
})
Expand Down
2 changes: 2 additions & 0 deletions utils/frame/try-runtime/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ sc-executor = { version = "0.10.0-dev", path = "../../../../client/executor" }
sc-service = { version = "0.10.0-dev", default-features = false, path = "../../../../client/service" }
sp-core = { version = "7.0.0", path = "../../../../primitives/core" }
sp-externalities = { version = "0.13.0", path = "../../../../primitives/externalities" }
sp-inherents = { path = "../../../../primitives/inherents" }
sp-io = { version = "7.0.0", path = "../../../../primitives/io" }
sp-keystore = { version = "0.13.0", path = "../../../../primitives/keystore" }
sp-runtime = { version = "7.0.0", path = "../../../../primitives/runtime" }
Expand All @@ -31,6 +32,7 @@ sp-weights = { version = "4.0.0", path = "../../../../primitives/weights" }
frame-try-runtime = { optional = true, path = "../../../../frame/try-runtime" }
substrate-rpc-client = { path = "../../rpc/client" }

async-trait = "0.1.57"
parity-scale-codec = "3.0.0"
hex = "0.4.3"
clap = { version = "4.0.9", features = ["derive"] }
Expand Down
292 changes: 292 additions & 0 deletions utils/frame/try-runtime/cli/src/commands/fast_forward.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,292 @@
use crate::{
build_executor, full_extensions, rpc_err_handler, state_machine_call, BlockT, LiveState,
SharedParams, State,
};
use parity_scale_codec::{Decode, Encode};
use sc_cli::Result;
use sc_executor::{sp_wasm_interface::HostFunctions, WasmExecutor};
use serde::de::DeserializeOwned;
use sp_core::H256;
use sp_inherents::{InherentData, InherentDataProvider};
use sp_io::TestExternalities;
use sp_runtime::{
traits::{Header, NumberFor, One},
Digest, DigestItem,
};
use std::{fmt::Debug, str::FromStr};
use substrate_rpc_client::{ws_client, ChainApi};

/// Configurations of the [`Command::FastForward`].
#[derive(Debug, Clone, clap::Parser)]
pub struct FastForwardCmd {
/// How many blocks should be processed. If `None`, then blocks will be produced and processed
/// in a loop.
#[arg(long)]
n_blocks: Option<u64>,

/// The state type to use.
#[command(subcommand)]
state: State,

/// The ws uri from which to fetch the block.
///
/// If `state` is `Live`, this is ignored. Otherwise, it must not be empty.
// todo: make use of clap's arg groups
#[arg(long, value_parser = crate::parse::url)]
block_ws_uri: Option<String>,

/// Which try-state targets to execute when running this command.
///
/// Expected values:
/// - `all`
/// - `none`
/// - A comma separated list of pallets, as per pallet names in `construct_runtime!()` (e.g.
/// `Staking, System`).
/// - `rr-[x]` where `[x]` is a number. Then, the given number of pallets are checked in a
/// round-robin fashion.
#[arg(long, default_value = "all")]
try_state: frame_try_runtime::TryStateSelect,
}

impl FastForwardCmd {
fn block_ws_uri(&self) -> &str {
match self.state {
State::Live(LiveState { ref uri, .. }) => &uri,
_ => self
.block_ws_uri
.as_ref()
.expect("Either `--block-uri` must be provided, or state must be `live`"),
}
}
}

/// Something that can create inherent data providers and pre-runtime digest.
///
/// It is possible for the caller to provide custom arguments to the callee by setting the
/// `ExtraArgs` generic parameter.
///
/// This module already provides some convenience implementation of this trait for closures. So, it
/// should not be required to implement
#[async_trait::async_trait]
pub trait BlockBuildingInfoProvider<Block: BlockT, ExtraArgs = ()> {
type InherentDataProviders: InherentDataProvider;

async fn get_inherent_providers_and_pre_digest(
&self,
parent_hash: Block::Hash,
extra_args: ExtraArgs,
) -> Result<(Self::InherentDataProviders, Vec<DigestItem>)>;
}

#[async_trait::async_trait]
impl<F, Block, IDP, ExtraArgs, Fut> BlockBuildingInfoProvider<Block, ExtraArgs> for F
where
Block: BlockT,
F: Fn(Block::Hash, ExtraArgs) -> Fut + Sync + Send,
Fut: std::future::Future<Output = Result<(IDP, Vec<DigestItem>)>> + Send + 'static,
IDP: InherentDataProvider + 'static,
ExtraArgs: Send + 'static,
{
type InherentDataProviders = IDP;

async fn get_inherent_providers_and_pre_digest(
&self,
parent: Block::Hash,
extra_args: ExtraArgs,
) -> Result<(Self::InherentDataProviders, Vec<DigestItem>)> {
(*self)(parent, extra_args).await
}
}

/// Read the block number corresponding to `hash` with an RPC call to `ws_uri`.
async fn get_block_number<Block: BlockT>(
hash: Block::Hash,
ws_uri: &str,
) -> Result<NumberFor<Block>>
where
Block::Header: DeserializeOwned,
{
let rpc = ws_client(ws_uri).await?;
Ok(ChainApi::<(), Block::Hash, Block::Header, ()>::header(&rpc, Some(hash))
.await
.map_err(rpc_err_handler)
.and_then(|maybe_header| maybe_header.ok_or("header_not_found").map(|h| *h.number()))?)
}

/// Call `method` with `data` and return the result. `externalities` will not change.
async fn dry_run<T: Decode, Block: BlockT, HostFns: HostFunctions>(
externalities: &TestExternalities,
executor: &WasmExecutor<HostFns>,
method: &'static str,
data: &[u8],
) -> Result<T> {
let (_, result) = state_machine_call::<Block, HostFns>(
externalities,
executor,
method,
data,
full_extensions(),
)?;

Ok(<T>::decode(&mut &*result)?)
}

/// Call `method` with `data` and actually save storage changes to `externalities`.
async fn run<Block: BlockT, HostFns: HostFunctions>(
externalities: &mut TestExternalities,
executor: &WasmExecutor<HostFns>,
method: &'static str,
data: &[u8],
) -> Result<()> {
let (mut changes, _) = state_machine_call::<Block, HostFns>(
externalities,
executor,
method,
data,
full_extensions(),
)?;

let storage_changes = changes
.drain_storage_changes(
&externalities.backend,
&mut Default::default(),
externalities.state_version,
)
.unwrap();

externalities
.backend
.apply_transaction(storage_changes.transaction_storage_root, storage_changes.transaction);

Ok(())
}

/// Produce next empty block.
async fn next_empty_block<
Block: BlockT,
HostFns: HostFunctions,
BBIP: BlockBuildingInfoProvider<Block, Option<(InherentData, Digest)>>,
>(
externalities: &mut TestExternalities,
executor: &WasmExecutor<HostFns>,
parent_height: NumberFor<Block>,
parent_hash: Block::Hash,
block_building_info_provider: &Option<BBIP>,
previous_block_building_info: Option<(InherentData, Digest)>,
) -> Result<(Block, Option<(InherentData, Digest)>)> {
let (maybe_inherent_data, pre_digest) = match &block_building_info_provider {
None => (None, Default::default()),
Some(bbip) => {
let (inherent_data_provider, pre_digest) = bbip
.get_inherent_providers_and_pre_digest(parent_hash, previous_block_building_info)
.await?;
let inherent_data = inherent_data_provider
.create_inherent_data()
.await
.map_err(|e| sc_cli::Error::Input(format!("I don't know how to convert {e:?}")))?;

(Some(inherent_data), Digest { logs: pre_digest })
},
};

let header = Block::Header::new(
parent_height + One::one(),
Default::default(),
Default::default(),
parent_hash,
pre_digest.clone(),
);
let mut extrinsics = <Vec<Block::Extrinsic>>::new();

run::<Block, _>(externalities, executor, "Core_initialize_block", &header.encode()).await?;

if let Some(ref inherent_data) = maybe_inherent_data {
extrinsics = dry_run::<Vec<Block::Extrinsic>, Block, _>(
externalities,
executor,
"BlockBuilder_inherent_extrinsics",
&inherent_data.encode(),
)
.await?;
}

for xt in &extrinsics {
run::<Block, _>(externalities, executor, "BlockBuilder_apply_extrinsic", &xt.encode())
.await?;
}

let header = dry_run::<Block::Header, Block, _>(
externalities,
executor,
"BlockBuilder_finalize_block",
&[0u8; 0],
)
.await?;

run::<Block, _>(externalities, executor, "BlockBuilder_finalize_block", &[0u8; 0]).await?;

Ok((Block::new(header, extrinsics), (maybe_inherent_data.map(|id| (id, pre_digest)))))
}

pub(crate) async fn fast_forward<Block, HostFns, BBIP>(
shared: SharedParams,
command: FastForwardCmd,
block_building_info_provider: Option<BBIP>,
) -> Result<()>
where
Block: BlockT<Hash = H256> + DeserializeOwned,
Block::Hash: FromStr,
Block::Header: DeserializeOwned,
<Block::Hash as FromStr>::Err: Debug,
NumberFor<Block>: FromStr,
<NumberFor<Block> as FromStr>::Err: Debug,
HostFns: HostFunctions,
BBIP: BlockBuildingInfoProvider<Block, Option<(InherentData, Digest)>>,
{
let executor = build_executor::<HostFns>(&shared);
let ext = command.state.into_ext::<Block, HostFns>(&shared, &executor, None).await?;

let mut last_block_hash = ext.block_hash;
let mut last_block_number =
get_block_number::<Block>(last_block_hash, command.block_ws_uri()).await?;
let mut prev_block_building_info = None;

let mut ext = ext.inner_ext;

for _ in 1..=command.n_blocks.unwrap_or(u64::MAX) {
// We are saving state before we overwrite it while producing new block.
let backend = ext.as_backend();

log::info!("Producing new empty block at height {:?}", last_block_number + One::one());

let (next_block, new_block_building_info) = next_empty_block::<Block, HostFns, BBIP>(
&mut ext,
&executor,
last_block_number,
last_block_hash,
&block_building_info_provider,
prev_block_building_info,
)
.await?;

log::info!("Produced a new block: {:?}", next_block.header());

// And now we restore previous state.
ext.backend = backend;

let state_root_check = true;
let signature_check = true;
let payload =
(next_block.clone(), state_root_check, signature_check, command.try_state.clone())
.encode();
run::<Block, _>(&mut ext, &executor, "TryRuntime_execute_block", &payload).await?;

log::info!("Executed the new block");

prev_block_building_info = new_block_building_info;
last_block_hash = next_block.hash();
last_block_number += One::one();
}

Ok(())
}
1 change: 1 addition & 0 deletions utils/frame/try-runtime/cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

pub mod create_snapshot;
pub mod execute_block;
pub mod fast_forward;
pub mod follow_chain;
pub mod offchain_worker;
pub mod on_runtime_upgrade;
Loading

0 comments on commit 63ea95b

Please sign in to comment.