Skip to content

Commit

Permalink
refactor(utils): Update gear-replay-cli tool (#3856)
Browse files Browse the repository at this point in the history
  • Loading branch information
ekovalev authored Apr 4, 2024
1 parent d130e12 commit 7d21c84
Show file tree
Hide file tree
Showing 13 changed files with 843 additions and 576 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

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

6 changes: 6 additions & 0 deletions utils/gear-replay-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ authors.workspace = true
edition.workspace = true
license.workspace = true

[[bin]]
name = "gear-replay-cli"
path = "src/main.rs"

[dependencies]
# Internal
gear-runtime-interface.workspace = true
Expand All @@ -14,6 +18,7 @@ service = { workspace = true, optional = true }

# Substrate Primitives
sp-core.workspace = true
sp-crypto-ec-utils = { workspace = true, features = ["bls12-381"] }
sp-externalities.workspace = true
sp-io.workspace = true
sp-keystore.workspace = true
Expand Down Expand Up @@ -46,6 +51,7 @@ std = [
"log/std",
"runtime-primitives/std",
"sp-core/std",
"sp-crypto-ec-utils/std",
"sp-io/std",
"sp-runtime/std",
"sp-state-machine/std",
Expand Down
192 changes: 68 additions & 124 deletions utils/gear-replay-cli/README.md

Large diffs are not rendered by default.

80 changes: 80 additions & 0 deletions utils/gear-replay-cli/src/cmd/create_snapshot.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// This file is part of Gear.

// Copyright (C) 2021-2024 Gear Technologies Inc.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

//! Create a chain state snapshot

use crate::{
shared_parameters::SharedParams,
state::{LiveState, State},
LOG_TARGET,
};
use clap::Parser;
use sp_runtime::{traits::Block as BlockT, DeserializeOwned};
use std::fmt::Debug;
use substrate_rpc_client::{ws_client, StateApi};

/// Create snapshot subcommand
#[derive(Clone, Debug, Parser)]
pub struct CreateSnapshotCmd<Block: BlockT> {
/// The source of the snapshot. Must be a remote node.
#[clap(flatten)]
pub from: LiveState<Block>,

/// The snapshot path to write to.
///
/// If not provided `<spec-name>-<spec-version>@<block-hash>.snap` will be used.
#[clap(index = 1)]
pub snapshot_path: Option<String>,
}

pub(crate) async fn run<Block>(
_shared: SharedParams,
command: CreateSnapshotCmd<Block>,
) -> sc_cli::Result<()>
where
Block: BlockT + DeserializeOwned,
Block::Header: DeserializeOwned,
{
let snapshot_path = command.snapshot_path;

let path = match snapshot_path {
Some(path) => path,
None => {
let rpc = ws_client(&command.from.uri).await.unwrap();
let remote_spec = StateApi::<Block::Hash>::runtime_version(&rpc, None)
.await
.unwrap();
let block_hash = match command.from.block {
Some(ref h) => format!("{}", h),
_ => "latext".to_owned(),
};
let path_str = format!(
"{}-{}@{}.snap",
remote_spec.spec_name.to_lowercase(),
remote_spec.spec_version,
block_hash,
);
log::info!(target: LOG_TARGET, "snapshot path not provided (-s), using '{path_str}'");
path_str
}
};

let _ = State::Live(command.from).to_ext(Some(path.into())).await?;

Ok(())
}
130 changes: 69 additions & 61 deletions utils/gear-replay-cli/src/cmd/gear_run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,110 +16,112 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

//! Replaying a block against the live chain state
//! Apply the `gear::run()` extrinsic on top of state changes introduced in a block

use crate::{parse, util::*, BlockHashOrNumber, SharedParams, LOG_TARGET};
use crate::{
build_executor, fetch_block, full_extensions,
shared_parameters::SharedParams,
state::{LiveState, SnapState, State},
state_machine_call, BlockHashOrNumber, LOG_TARGET,
};
use clap::Parser;
use codec::{Decode, Encode, Joiner};
#[cfg(feature = "always-wasm")]
use sc_executor::sp_wasm_interface::ExtendedHostFunctions;
#[cfg(all(not(feature = "always-wasm"), feature = "vara-native"))]
use service::VaraExecutorDispatch;
use sp_runtime::{
generic::SignedBlock,
traits::{Block as BlockT, Header as HeaderT, One},
ApplyExtrinsicResult, DeserializeOwned, Saturating,
traits::{Block as BlockT, Header as HeaderT},
ApplyExtrinsicResult, DeserializeOwned,
};
use std::{fmt::Debug, str::FromStr};
use substrate_rpc_client::{ws_client, ChainApi};
use std::fmt::Debug;
use substrate_rpc_client::ws_client;

/// GearRun subcommand
#[derive(Clone, Debug, Parser)]
pub struct GearRunCmd<Block: BlockT> {
/// The block hash or number as of which the state (including the runtime) is fetched.
/// If omitted, the latest finalized block is used.
/// The ws uri from which to fetch the block.
///
/// If `state` is `Live`, this can be ignored since the `uri` of the `LiveState` is used for
/// both fetching state and block.
#[arg(
short,
long,
value_parser = parse::block,
value_parser = crate::parse::url
)]
block: Option<BlockHashOrNumber<Block>>,
pub block_ws_uri: Option<String>,

/// The state type to use.
#[command(subcommand)]
pub state: State<Block>,
}

pub(crate) async fn gear_run<Block>(
impl<Block: BlockT> GearRunCmd<Block> {
fn block_ws_uri_and_hash(&self) -> (String, Option<BlockHashOrNumber<Block>>) {
match (&self.block_ws_uri, &self.state) {
(Some(block_ws_uri), State::Snap(SnapState { block, .. })) => {
(block_ws_uri.to_owned(), block.clone())
}
(Some(_block_ws_uri), State::Live(LiveState { uri, block, .. })) => {
log::warn!(target: LOG_TARGET, "--block-uri is ignored when fetching live state");
(uri.clone(), block.clone())
}
(None, State::Live(LiveState { uri, block, .. })) => (uri.clone(), block.clone()),
(None, State::Snap { .. }) => {
panic!("either `--block-uri` must be provided, or state must be `live`");
}
}
}
}

pub(crate) async fn run<Block>(
shared: SharedParams,
command: GearRunCmd<Block>,
) -> sc_cli::Result<()>
where
Block: BlockT + DeserializeOwned,
Block::Header: DeserializeOwned,
Block::Hash: FromStr,
<Block::Hash as FromStr>::Err: Debug,
{
let (block_ws_uri, execute_at) = command.block_ws_uri_and_hash();

// Initialize the RPC client.
// get the block number associated with this block.
let block_ws_uri = shared.uri.clone();
let rpc = ws_client(&block_ws_uri).await?;

let block_hash_or_num = match command.block.clone() {
Some(b) => b,
None => {
let height = ChainApi::<
(),
<Block as BlockT>::Hash,
<Block as BlockT>::Header,
SignedBlock<Block>,
>::finalized_head(&rpc)
.await
.map_err(rpc_err_handler)?;

log::info!(
target: LOG_TARGET,
"Block is not provided, setting it to the latest finalized head: {:?}",
height
);

BlockHashOrNumber::Hash(height)
let ext = match command.state {
State::Live(live_state) => {
let prev_block_live_state = live_state.prev_block_live_state().await?;
State::Live(prev_block_live_state).to_ext(None).await?
}
State::Snap(snap_state) => State::Snap(snap_state).to_ext(None).await?,
};

let (current_number, current_hash) = match block_hash_or_num {
BlockHashOrNumber::Number(n) => (n, block_number_to_hash::<Block>(&rpc, n).await?),
BlockHashOrNumber::Hash(hash) => (block_hash_to_number::<Block>(&rpc, hash).await?, hash),
let current_hash = match execute_at {
Some(b) => Some(b.as_hash(&rpc).await?),
_ => None,
};

// Get the state at the height corresponding to previous block.
let previous_hash =
block_number_to_hash::<Block>(&rpc, current_number.saturating_sub(One::one())).await?;
log::info!(
target: LOG_TARGET,
"Fetching state from {:?} at {:?}",
shared.uri,
previous_hash,
);
let ext =
build_externalities::<Block>(shared.uri.clone(), Some(previous_hash), vec![], true).await?;

log::info!(target: LOG_TARGET, "Fetching block {:?} ", current_hash);
log::info!(target: LOG_TARGET, "Fetching block {current_hash:?} ");
let block = fetch_block::<Block>(&rpc, current_hash).await?;

let (mut header, extrinsics) = block.deconstruct();

// A digest item gets added when the runtime is processing the block, so we need to pop
// the last one to be consistent with what a gossiped block would contain.
let (header, extrinsics) = block.deconstruct();
header.digest_mut().pop();

// Create executor, suitable for usage in conjunction with the preferred execution strategy.
#[cfg(all(not(feature = "always-wasm"), feature = "vara-native"))]
let executor = build_executor::<VaraExecutorDispatch>();
let executor = build_executor::<VaraExecutorDispatch>(&shared);
#[cfg(feature = "always-wasm")]
let executor = build_executor::<
ExtendedHostFunctions<
sp_io::SubstrateHostFunctions,
(
gear_runtime_interface::gear_ri::HostFunctions,
gear_runtime_interface::sandbox::HostFunctions,
sp_crypto_ec_utils::bls12_381::host_calls::HostFunctions,
),
>,
>();
>(&shared);

let (_changes, _enc_res) = state_machine_call::<Block, _>(
&ext,
Expand All @@ -134,15 +136,22 @@ where
header.number()
);

// Encoded `Gear::run` extrinsic: length byte 12 (3 << 2) + 104th pallet + 6th extrinsic
let gear_run_tx = vec![12_u8, 4, 104, 6];
let (_changes, gear_run_encoded) = state_machine_call::<Block, _>(
&ext,
&executor,
"GearApi_gear_run_extrinsic",
&None::<u64>.encode(),
full_extensions(),
)?;

// Drop the timestamp extrinsic which is always the first in the block
let extrinsics = extrinsics.into_iter().skip(1).collect::<Vec<_>>();

let is_gear_run = |x: &Vec<u8>| x.windows(3).any(|window| window == &gear_run_encoded[1..4]);

for extrinsic in extrinsics {
let tx_encoded = extrinsic.encode();
if tx_encoded != gear_run_tx {
if !is_gear_run(&tx_encoded) {
// Apply all extrinsics in the block except for the timestamp and gear::run
let _ = state_machine_call::<Block, _>(
&ext,
Expand All @@ -159,14 +168,13 @@ where
&ext,
&executor,
"BlockBuilder_apply_extrinsic",
&gear_run_tx,
&gear_run_encoded,
full_extensions(),
)?;
let r = ApplyExtrinsicResult::decode(&mut &enc_res[..]).unwrap();
log::info!(
target: LOG_TARGET,
"BlockBuilder_apply_extrinsic done with result {:?}",
r
"BlockBuilder_apply_extrinsic done with result {r:?}"
);

Ok(())
Expand Down
58 changes: 58 additions & 0 deletions utils/gear-replay-cli/src/cmd/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,63 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

use clap::{Parser, Subcommand};
use runtime_primitives::Block;
use sc_tracing::logging::LoggerBuilder;
use std::fmt::Debug;

use crate::shared_parameters::SharedParams;

pub mod create_snapshot;
pub mod gear_run;
pub mod replay_block;

#[derive(Debug, Parser)]
pub struct ReplayCli {
#[clap(flatten)]
pub shared: SharedParams,

/// Commands.
#[command(subcommand)]
pub command: Command,
}

impl ReplayCli {
fn log_filters(&self) -> sc_cli::Result<String> {
Ok(self.shared.log.join(","))
}

pub fn init_logger(&self) -> sc_cli::Result<()> {
let logger = LoggerBuilder::new(self.log_filters()?);
Ok(logger.init()?)
}

pub async fn run(&self) -> sc_cli::Result<()> {
self.command.run(&self.shared).await
}
}

/// Commands of `gear-replay` CLI
#[derive(Debug, Subcommand)]
pub enum Command {
ReplayBlock(replay_block::ReplayBlockCmd<Block>),
GearRun(gear_run::GearRunCmd<Block>),
/// Create a new snapshot file.
CreateSnapshot(create_snapshot::CreateSnapshotCmd<Block>),
}

impl Command {
pub async fn run(&self, shared: &SharedParams) -> sc_cli::Result<()> {
gear_runtime_interface::sandbox_init();

match &self {
Command::ReplayBlock(cmd) => {
replay_block::run::<Block>(shared.clone(), cmd.clone()).await
}
Command::GearRun(cmd) => gear_run::run::<Block>(shared.clone(), cmd.clone()).await,
Command::CreateSnapshot(cmd) => {
create_snapshot::run::<Block>(shared.clone(), cmd.clone()).await
}
}
}
}
Loading

0 comments on commit 7d21c84

Please sign in to comment.