mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-12 17:01:09 +00:00
Adds export-state subcommand (#5842)
* Export state cli * More work * Fix tests * Make it work * Fix compilation * Apply suggestions from code review
This commit is contained in:
@@ -16,11 +16,7 @@
|
||||
|
||||
//! Substrate chain configurations.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::{borrow::Cow, fs::File, path::PathBuf, sync::Arc, collections::HashMap};
|
||||
use serde::{Serialize, Deserialize};
|
||||
use sp_core::storage::{StorageKey, StorageData, ChildInfo, Storage, StorageChild};
|
||||
use sp_runtime::BuildStorage;
|
||||
@@ -33,14 +29,16 @@ enum GenesisSource<G> {
|
||||
File(PathBuf),
|
||||
Binary(Cow<'static, [u8]>),
|
||||
Factory(Arc<dyn Fn() -> G + Send + Sync>),
|
||||
Storage(Storage),
|
||||
}
|
||||
|
||||
impl<G> Clone for GenesisSource<G> {
|
||||
fn clone(&self) -> Self {
|
||||
match *self {
|
||||
GenesisSource::File(ref path) => GenesisSource::File(path.clone()),
|
||||
GenesisSource::Binary(ref d) => GenesisSource::Binary(d.clone()),
|
||||
GenesisSource::Factory(ref f) => GenesisSource::Factory(f.clone()),
|
||||
Self::File(ref path) => Self::File(path.clone()),
|
||||
Self::Binary(ref d) => Self::Binary(d.clone()),
|
||||
Self::Factory(ref f) => Self::Factory(f.clone()),
|
||||
Self::Storage(ref s) => Self::Storage(s.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,19 +51,40 @@ impl<G: RuntimeGenesis> GenesisSource<G> {
|
||||
}
|
||||
|
||||
match self {
|
||||
GenesisSource::File(path) => {
|
||||
Self::File(path) => {
|
||||
let file = File::open(path)
|
||||
.map_err(|e| format!("Error opening spec file: {}", e))?;
|
||||
let genesis: GenesisContainer<G> = json::from_reader(file)
|
||||
.map_err(|e| format!("Error parsing spec file: {}", e))?;
|
||||
Ok(genesis.genesis)
|
||||
},
|
||||
GenesisSource::Binary(buf) => {
|
||||
Self::Binary(buf) => {
|
||||
let genesis: GenesisContainer<G> = json::from_reader(buf.as_ref())
|
||||
.map_err(|e| format!("Error parsing embedded file: {}", e))?;
|
||||
Ok(genesis.genesis)
|
||||
},
|
||||
GenesisSource::Factory(f) => Ok(Genesis::Runtime(f())),
|
||||
Self::Factory(f) => Ok(Genesis::Runtime(f())),
|
||||
Self::Storage(storage) => {
|
||||
let top = storage.top
|
||||
.iter()
|
||||
.map(|(k, v)| (StorageKey(k.clone()), StorageData(v.clone())))
|
||||
.collect();
|
||||
|
||||
let children_default = storage.children_default
|
||||
.iter()
|
||||
.map(|(k, child)|
|
||||
(
|
||||
StorageKey(k.clone()),
|
||||
child.data
|
||||
.iter()
|
||||
.map(|(k, v)| (StorageKey(k.clone()), StorageData(v.clone())))
|
||||
.collect()
|
||||
)
|
||||
)
|
||||
.collect();
|
||||
|
||||
Ok(Genesis::Raw(RawGenesis { top, children_default }))
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -98,15 +117,15 @@ impl<G: RuntimeGenesis, E> BuildStorage for ChainSpec<G, E> {
|
||||
}
|
||||
}
|
||||
|
||||
type GenesisStorage = HashMap<StorageKey, StorageData>;
|
||||
pub type GenesisStorage = HashMap<StorageKey, StorageData>;
|
||||
|
||||
/// Raw storage content for genesis block.
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(deny_unknown_fields)]
|
||||
/// Storage content for genesis block.
|
||||
struct RawGenesis {
|
||||
top: GenesisStorage,
|
||||
children_default: HashMap<StorageKey, GenesisStorage>,
|
||||
pub struct RawGenesis {
|
||||
pub top: GenesisStorage,
|
||||
pub children_default: HashMap<StorageKey, GenesisStorage>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@@ -263,7 +282,7 @@ impl<G, E: serde::de::DeserializeOwned> ChainSpec<G, E> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<G: RuntimeGenesis, E: serde::Serialize + Clone> ChainSpec<G, E> {
|
||||
impl<G: RuntimeGenesis, E: serde::Serialize + Clone + 'static> ChainSpec<G, E> {
|
||||
/// Dump to json string.
|
||||
pub fn as_json(&self, raw: bool) -> Result<String, String> {
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@@ -303,8 +322,8 @@ impl<G: RuntimeGenesis, E: serde::Serialize + Clone> ChainSpec<G, E> {
|
||||
|
||||
impl<G, E> crate::ChainSpec for ChainSpec<G, E>
|
||||
where
|
||||
G: RuntimeGenesis,
|
||||
E: GetExtension + serde::Serialize + Clone + Send,
|
||||
G: RuntimeGenesis + 'static,
|
||||
E: GetExtension + serde::Serialize + Clone + Send + 'static,
|
||||
{
|
||||
fn boot_nodes(&self) -> &[MultiaddrWithPeerId] {
|
||||
ChainSpec::boot_nodes(self)
|
||||
@@ -349,6 +368,14 @@ where
|
||||
fn as_storage_builder(&self) -> &dyn BuildStorage {
|
||||
self
|
||||
}
|
||||
|
||||
fn cloned_box(&self) -> Box<dyn crate::ChainSpec> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn set_storage(&mut self, storage: Storage) {
|
||||
self.genesis = GenesisSource::Storage(storage);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -119,6 +119,7 @@ use serde::{Serialize, de::DeserializeOwned};
|
||||
use sp_runtime::BuildStorage;
|
||||
use sc_network::config::MultiaddrWithPeerId;
|
||||
use sc_telemetry::TelemetryEndpoints;
|
||||
use sp_core::storage::Storage;
|
||||
|
||||
/// A set of traits for the runtime genesis config.
|
||||
pub trait RuntimeGenesis: Serialize + DeserializeOwned + BuildStorage {}
|
||||
@@ -150,4 +151,10 @@ pub trait ChainSpec: BuildStorage + Send {
|
||||
fn as_json(&self, raw: bool) -> Result<String, String>;
|
||||
/// Return StorageBuilder for this spec.
|
||||
fn as_storage_builder(&self) -> &dyn BuildStorage;
|
||||
/// Returns a cloned `Box<dyn ChainSpec>`.
|
||||
fn cloned_box(&self) -> Box<dyn ChainSpec>;
|
||||
/// Set the storage that should be used by this chain spec.
|
||||
///
|
||||
/// This will be used as storage at genesis.
|
||||
fn set_storage(&mut self, storage: Storage);
|
||||
}
|
||||
|
||||
@@ -14,15 +14,12 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::error;
|
||||
use crate::params::ImportParams;
|
||||
use crate::params::SharedParams;
|
||||
use crate::CliConfiguration;
|
||||
use crate::{
|
||||
CliConfiguration, error, params::{ImportParams, SharedParams, BlockNumberOrHash},
|
||||
};
|
||||
use sc_service::{Configuration, ServiceBuilderCommand};
|
||||
use sp_runtime::generic::BlockId;
|
||||
use sp_runtime::traits::{Block as BlockT, Header as HeaderT};
|
||||
use std::fmt::Debug;
|
||||
use std::str::FromStr;
|
||||
use sp_runtime::traits::{Block as BlockT, NumberFor};
|
||||
use std::{fmt::Debug, str::FromStr};
|
||||
use structopt::StructOpt;
|
||||
|
||||
/// The `check-block` command used to validate blocks.
|
||||
@@ -30,7 +27,7 @@ use structopt::StructOpt;
|
||||
pub struct CheckBlockCmd {
|
||||
/// Block hash or number
|
||||
#[structopt(value_name = "HASH or NUMBER")]
|
||||
pub input: String,
|
||||
pub input: BlockNumberOrHash,
|
||||
|
||||
/// The default number of 64KB pages to ever allocate for Wasm execution.
|
||||
///
|
||||
@@ -57,29 +54,13 @@ impl CheckBlockCmd {
|
||||
where
|
||||
B: FnOnce(Configuration) -> Result<BC, sc_service::error::Error>,
|
||||
BC: ServiceBuilderCommand<Block = BB> + Unpin,
|
||||
BB: sp_runtime::traits::Block + Debug,
|
||||
<<<BB as BlockT>::Header as HeaderT>::Number as std::str::FromStr>::Err: std::fmt::Debug,
|
||||
<BB as BlockT>::Hash: std::str::FromStr,
|
||||
BB: BlockT + Debug,
|
||||
<NumberFor<BB> as FromStr>::Err: std::fmt::Debug,
|
||||
BB::Hash: FromStr,
|
||||
<BB::Hash as FromStr>::Err: std::fmt::Debug,
|
||||
{
|
||||
let input = if self.input.starts_with("0x") {
|
||||
&self.input[2..]
|
||||
} else {
|
||||
&self.input[..]
|
||||
};
|
||||
let block_id = match FromStr::from_str(input) {
|
||||
Ok(hash) => BlockId::hash(hash),
|
||||
Err(_) => match self.input.parse::<u32>() {
|
||||
Ok(n) => BlockId::number((n as u32).into()),
|
||||
Err(_) => {
|
||||
return Err(error::Error::Input(
|
||||
"Invalid hash or number specified".into(),
|
||||
))
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let start = std::time::Instant::now();
|
||||
builder(config)?.check_block(block_id).await?;
|
||||
builder(config)?.check_block(self.input.parse()?).await?;
|
||||
println!("Completed in {} ms.", start.elapsed().as_millis());
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
// Copyright 2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate 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.
|
||||
|
||||
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::{
|
||||
CliConfiguration, error, params::{PruningParams, SharedParams, BlockNumberOrHash},
|
||||
};
|
||||
use log::info;
|
||||
use sc_service::{Configuration, ServiceBuilderCommand};
|
||||
use sp_runtime::traits::{Block as BlockT, NumberFor};
|
||||
use std::{fmt::Debug, str::FromStr};
|
||||
use structopt::StructOpt;
|
||||
|
||||
/// The `export-state` command used to export the state of a given block into
|
||||
/// a chain spec.
|
||||
#[derive(Debug, StructOpt, Clone)]
|
||||
pub struct ExportStateCmd {
|
||||
/// Block hash or number.
|
||||
#[structopt(value_name = "HASH or NUMBER")]
|
||||
pub input: Option<BlockNumberOrHash>,
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[structopt(flatten)]
|
||||
pub shared_params: SharedParams,
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[structopt(flatten)]
|
||||
pub pruning_params: PruningParams,
|
||||
}
|
||||
|
||||
impl ExportStateCmd {
|
||||
/// Run the `export-state` command
|
||||
pub fn run<B, BC, BB>(
|
||||
&self,
|
||||
config: Configuration,
|
||||
builder: B,
|
||||
) -> error::Result<()>
|
||||
where
|
||||
B: FnOnce(Configuration) -> Result<BC, sc_service::error::Error>,
|
||||
BC: ServiceBuilderCommand<Block = BB> + Unpin,
|
||||
BB: BlockT + Debug,
|
||||
<NumberFor<BB> as FromStr>::Err: std::fmt::Debug,
|
||||
BB::Hash: FromStr,
|
||||
<BB::Hash as FromStr>::Err: std::fmt::Debug,
|
||||
{
|
||||
info!("Exporting raw state...");
|
||||
let mut input_spec = config.chain_spec.cloned_box();
|
||||
let block_id = self.input.clone().map(|b| b.parse()).transpose()?;
|
||||
let raw_state = builder(config)?.export_raw_state(block_id)?;
|
||||
input_spec.set_storage(raw_state);
|
||||
|
||||
info!("Generating new chain spec...");
|
||||
let json = sc_service::chain_ops::build_spec(&*input_spec, true)?;
|
||||
|
||||
print!("{}", json);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl CliConfiguration for ExportStateCmd {
|
||||
fn shared_params(&self) -> &SharedParams {
|
||||
&self.shared_params
|
||||
}
|
||||
|
||||
fn pruning_params(&self) -> Option<&PruningParams> {
|
||||
Some(&self.pruning_params)
|
||||
}
|
||||
}
|
||||
@@ -17,18 +17,20 @@
|
||||
mod build_spec_cmd;
|
||||
mod check_block_cmd;
|
||||
mod export_blocks_cmd;
|
||||
mod export_state_cmd;
|
||||
mod import_blocks_cmd;
|
||||
mod purge_chain_cmd;
|
||||
mod revert_cmd;
|
||||
mod runcmd;
|
||||
mod run_cmd;
|
||||
|
||||
pub use crate::commands::build_spec_cmd::BuildSpecCmd;
|
||||
pub use crate::commands::check_block_cmd::CheckBlockCmd;
|
||||
pub use crate::commands::export_blocks_cmd::ExportBlocksCmd;
|
||||
pub use crate::commands::import_blocks_cmd::ImportBlocksCmd;
|
||||
pub use crate::commands::purge_chain_cmd::PurgeChainCmd;
|
||||
pub use crate::commands::revert_cmd::RevertCmd;
|
||||
pub use crate::commands::runcmd::RunCmd;
|
||||
pub use self::build_spec_cmd::BuildSpecCmd;
|
||||
pub use self::check_block_cmd::CheckBlockCmd;
|
||||
pub use self::export_blocks_cmd::ExportBlocksCmd;
|
||||
pub use self::import_blocks_cmd::ImportBlocksCmd;
|
||||
pub use self::purge_chain_cmd::PurgeChainCmd;
|
||||
pub use self::revert_cmd::RevertCmd;
|
||||
pub use self::run_cmd::RunCmd;
|
||||
pub use self::export_state_cmd::ExportStateCmd;
|
||||
use std::fmt::Debug;
|
||||
use structopt::StructOpt;
|
||||
|
||||
@@ -56,6 +58,9 @@ pub enum Subcommand {
|
||||
|
||||
/// Remove the whole chain data.
|
||||
PurgeChain(PurgeChainCmd),
|
||||
|
||||
/// Export state as raw chain spec.
|
||||
ExportState(ExportStateCmd),
|
||||
}
|
||||
|
||||
// TODO: move to config.rs?
|
||||
@@ -339,7 +344,10 @@ macro_rules! substrate_cli_subcommands {
|
||||
}
|
||||
}
|
||||
|
||||
fn offchain_worker(&self, role: &::sc_service::Role) -> $crate::Result<::sc_service::config::OffchainWorkerConfig> {
|
||||
fn offchain_worker(
|
||||
&self,
|
||||
role: &::sc_service::Role,
|
||||
) -> $crate::Result<::sc_service::config::OffchainWorkerConfig> {
|
||||
match self {
|
||||
$($enum::$variant(cmd) => cmd.offchain_worker(role)),*
|
||||
}
|
||||
@@ -398,5 +406,6 @@ macro_rules! substrate_cli_subcommands {
|
||||
}
|
||||
|
||||
substrate_cli_subcommands!(
|
||||
Subcommand => BuildSpec, ExportBlocks, ImportBlocks, CheckBlock, Revert, PurgeChain
|
||||
Subcommand => BuildSpec, ExportBlocks, ImportBlocks, CheckBlock, Revert, PurgeChain, ExportState
|
||||
);
|
||||
|
||||
|
||||
@@ -24,8 +24,8 @@ mod pruning_params;
|
||||
mod shared_params;
|
||||
mod transaction_pool_params;
|
||||
|
||||
use std::fmt::Debug;
|
||||
use std::str::FromStr;
|
||||
use std::{fmt::Debug, str::FromStr};
|
||||
use sp_runtime::{generic::BlockId, traits::{Block as BlockT, NumberFor}};
|
||||
|
||||
pub use crate::params::database_params::*;
|
||||
pub use crate::params::import_params::*;
|
||||
@@ -45,10 +45,10 @@ impl FromStr for BlockNumber {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(block_number: &str) -> Result<Self, Self::Err> {
|
||||
if block_number.chars().any(|d| !d.is_digit(10)) {
|
||||
if let Some(pos) = block_number.chars().position(|d| !d.is_digit(10)) {
|
||||
Err(format!(
|
||||
"Invalid block number: {}, expected decimal formatted unsigned integer",
|
||||
block_number,
|
||||
"Expected block number, found illegal digit at position: {}",
|
||||
pos,
|
||||
))
|
||||
} else {
|
||||
Ok(Self(block_number.to_owned()))
|
||||
@@ -66,8 +66,89 @@ impl BlockNumber {
|
||||
N: FromStr,
|
||||
N::Err: std::fmt::Debug,
|
||||
{
|
||||
self.0
|
||||
.parse()
|
||||
.map_err(|e| format!("BlockNumber: {} parsing failed because of {:?}", self.0, e))
|
||||
FromStr::from_str(&self.0).map_err(|e| format!("Failed to parse block number: {:?}", e))
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper type that is either a `Hash` or the number of a `Block`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BlockNumberOrHash(String);
|
||||
|
||||
impl FromStr for BlockNumberOrHash {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(block_number: &str) -> Result<Self, Self::Err> {
|
||||
if block_number.starts_with("0x") {
|
||||
if let Some(pos) = &block_number[2..].chars().position(|c| !c.is_ascii_hexdigit()) {
|
||||
Err(format!(
|
||||
"Expected block hash, found illegal hex character at position: {}",
|
||||
2 + pos,
|
||||
))
|
||||
} else {
|
||||
Ok(Self(block_number.into()))
|
||||
}
|
||||
} else {
|
||||
BlockNumber::from_str(block_number).map(|v| Self(v.0))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockNumberOrHash {
|
||||
/// Parse the inner value as `BlockId`.
|
||||
pub fn parse<B: BlockT>(&self) -> Result<BlockId<B>, String>
|
||||
where
|
||||
B::Hash: FromStr,
|
||||
<B::Hash as FromStr>::Err: std::fmt::Debug,
|
||||
NumberFor<B>: FromStr,
|
||||
<NumberFor<B> as FromStr>::Err: std::fmt::Debug,
|
||||
{
|
||||
if self.0.starts_with("0x") {
|
||||
Ok(BlockId::Hash(
|
||||
FromStr::from_str(&self.0[2..])
|
||||
.map_err(|e| format!("Failed to parse block hash: {:?}", e))?
|
||||
))
|
||||
} else {
|
||||
BlockNumber(self.0.clone()).parse().map(BlockId::Number)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
type Header = sp_runtime::generic::Header<u32, sp_runtime::traits::BlakeTwo256>;
|
||||
type Block = sp_runtime::generic::Block<Header, sp_runtime::OpaqueExtrinsic>;
|
||||
|
||||
#[test]
|
||||
fn parse_block_number() {
|
||||
let block_number_or_hash = BlockNumberOrHash::from_str("1234").unwrap();
|
||||
let parsed = block_number_or_hash.parse::<Block>().unwrap();
|
||||
assert_eq!(BlockId::Number(1234), parsed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_block_hash() {
|
||||
let hash = sp_core::H256::default();
|
||||
let hash_str = format!("{:?}", hash);
|
||||
let block_number_or_hash = BlockNumberOrHash::from_str(&hash_str).unwrap();
|
||||
let parsed = block_number_or_hash.parse::<Block>().unwrap();
|
||||
assert_eq!(BlockId::Hash(hash), parsed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_block_hash_fails() {
|
||||
assert_eq!(
|
||||
"Expected block hash, found illegal hex character at position: 2",
|
||||
BlockNumberOrHash::from_str("0xHello").unwrap_err(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_block_number_fails() {
|
||||
assert_eq!(
|
||||
"Expected block number, found illegal digit at position: 3",
|
||||
BlockNumberOrHash::from_str("345Hello").unwrap_err(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,9 +26,7 @@ use log::info;
|
||||
use sc_service::{AbstractService, Configuration, Role, ServiceBuilderCommand, TaskType};
|
||||
use sp_runtime::traits::{Block as BlockT, Header as HeaderT};
|
||||
use sp_utils::metrics::{TOKIO_THREADS_ALIVE, TOKIO_THREADS_TOTAL};
|
||||
use std::fmt::Debug;
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::Arc;
|
||||
use std::{str::FromStr, fmt::Debug, marker::PhantomData, sync::Arc};
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
async fn main<F, E>(func: F) -> std::result::Result<(), Box<dyn std::error::Error>>
|
||||
@@ -183,8 +181,9 @@ impl<C: SubstrateCli> Runner<C> {
|
||||
B: FnOnce(Configuration) -> sc_service::error::Result<BC>,
|
||||
BC: ServiceBuilderCommand<Block = BB> + Unpin,
|
||||
BB: sp_runtime::traits::Block + Debug,
|
||||
<<<BB as BlockT>::Header as HeaderT>::Number as std::str::FromStr>::Err: Debug,
|
||||
<BB as BlockT>::Hash: std::str::FromStr,
|
||||
<<<BB as BlockT>::Header as HeaderT>::Number as FromStr>::Err: Debug,
|
||||
<BB as BlockT>::Hash: FromStr,
|
||||
<<BB as BlockT>::Hash as FromStr>::Err: Debug,
|
||||
{
|
||||
match subcommand {
|
||||
Subcommand::BuildSpec(cmd) => cmd.run(self.config),
|
||||
@@ -199,6 +198,7 @@ impl<C: SubstrateCli> Runner<C> {
|
||||
}
|
||||
Subcommand::Revert(cmd) => cmd.run(self.config, builder),
|
||||
Subcommand::PurgeChain(cmd) => cmd.run(self.config),
|
||||
Subcommand::ExportState(cmd) => cmd.run(self.config, builder),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ use futures::{
|
||||
Future, FutureExt, StreamExt,
|
||||
future::ready,
|
||||
};
|
||||
use sc_keystore::{Store as Keystore};
|
||||
use sc_keystore::Store as Keystore;
|
||||
use log::{info, warn, error};
|
||||
use sc_network::config::{Role, FinalityProofProvider, OnDemand, BoxFinalityProofRequestBuilder};
|
||||
use sc_network::{NetworkService, NetworkStateInfo};
|
||||
@@ -59,6 +59,7 @@ use sc_client_db::{Backend, DatabaseSettings};
|
||||
use sp_core::traits::CodeExecutor;
|
||||
use sp_runtime::BuildStorage;
|
||||
use sc_client_api::execution_extensions::ExecutionExtensions;
|
||||
use sp_core::storage::Storage;
|
||||
|
||||
pub type BackgroundTask = Pin<Box<dyn Future<Output=()> + Send>>;
|
||||
|
||||
@@ -771,6 +772,13 @@ pub trait ServiceBuilderCommand {
|
||||
self,
|
||||
block: BlockId<Self::Block>
|
||||
) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send>>;
|
||||
|
||||
/// Export the raw state at the given `block`. If `block` is `None`, the
|
||||
/// best block will be used.
|
||||
fn export_raw_state(
|
||||
&self,
|
||||
block: Option<BlockId<Self::Block>>,
|
||||
) -> Result<Storage, Error>;
|
||||
}
|
||||
|
||||
impl<TBl, TRtApi, TBackend, TExec, TSc, TImpQu, TExPool, TRpc>
|
||||
|
||||
@@ -33,13 +33,14 @@ use sp_consensus::{
|
||||
import_queue::{IncomingBlock, Link, BlockImportError, BlockImportResult, ImportQueue},
|
||||
};
|
||||
use sc_executor::{NativeExecutor, NativeExecutionDispatch};
|
||||
use sp_core::storage::{StorageKey, well_known_keys, ChildInfo, Storage, StorageChild, StorageMap};
|
||||
use sc_client_api::{StorageProvider, BlockBackend, UsageProvider};
|
||||
|
||||
use std::{io::{Read, Write, Seek}, pin::Pin};
|
||||
use sc_client_api::BlockBackend;
|
||||
use std::{io::{Read, Write, Seek}, pin::Pin, collections::HashMap};
|
||||
|
||||
/// Build a chain spec json
|
||||
pub fn build_spec(spec: &dyn ChainSpec, raw: bool) -> error::Result<String> {
|
||||
Ok(spec.as_json(raw)?)
|
||||
spec.as_json(raw).map_err(Into::into)
|
||||
}
|
||||
|
||||
impl<
|
||||
@@ -298,4 +299,45 @@ impl<
|
||||
Err(e) => Box::pin(future::err(format!("Error reading block: {:?}", e).into())),
|
||||
}
|
||||
}
|
||||
|
||||
fn export_raw_state(
|
||||
&self,
|
||||
block: Option<BlockId<Self::Block>>,
|
||||
) -> Result<Storage, Error> {
|
||||
let block = block.unwrap_or_else(
|
||||
|| BlockId::Hash(self.client.usage_info().chain.best_hash)
|
||||
);
|
||||
|
||||
let empty_key = StorageKey(Vec::new());
|
||||
let mut top_storage = self.client.storage_pairs(&block, &empty_key)?;
|
||||
let mut children_default = HashMap::new();
|
||||
|
||||
// Remove all default child storage roots from the top storage and collect the child storage
|
||||
// pairs.
|
||||
while let Some(pos) = top_storage
|
||||
.iter()
|
||||
.position(|(k, _)| k.0.starts_with(well_known_keys::DEFAULT_CHILD_STORAGE_KEY_PREFIX)) {
|
||||
let (key, _) = top_storage.swap_remove(pos);
|
||||
|
||||
let key = StorageKey(
|
||||
key.0[well_known_keys::DEFAULT_CHILD_STORAGE_KEY_PREFIX.len()..].to_vec(),
|
||||
);
|
||||
let child_info = ChildInfo::new_default(&key.0);
|
||||
|
||||
let keys = self.client.child_storage_keys(&block, &child_info, &empty_key)?;
|
||||
let mut pairs = StorageMap::new();
|
||||
keys.into_iter().try_for_each(|k| {
|
||||
if let Some(value) = self.client.child_storage(&block, &child_info, &k)? {
|
||||
pairs.insert(k.0, value.0);
|
||||
}
|
||||
|
||||
Ok::<_, Error>(())
|
||||
})?;
|
||||
|
||||
children_default.insert(key.0, StorageChild { child_info, data: pairs });
|
||||
}
|
||||
|
||||
let top = top_storage.into_iter().map(|(k, v)| (k.0, v.0)).collect();
|
||||
Ok(Storage { top, children_default })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1321,8 +1321,11 @@ impl<B, E, Block, RA> StorageProvider<Block, B> for Client<B, E, Block, RA> wher
|
||||
}
|
||||
|
||||
|
||||
fn storage(&self, id: &BlockId<Block>, key: &StorageKey) -> sp_blockchain::Result<Option<StorageData>>
|
||||
{
|
||||
fn storage(
|
||||
&self,
|
||||
id: &BlockId<Block>,
|
||||
key: &StorageKey,
|
||||
) -> sp_blockchain::Result<Option<StorageData>> {
|
||||
Ok(self.state_at(id)?
|
||||
.storage(&key.0).map_err(|e| sp_blockchain::Error::from_state(Box::new(e)))?
|
||||
.map(StorageData)
|
||||
@@ -1330,8 +1333,11 @@ impl<B, E, Block, RA> StorageProvider<Block, B> for Client<B, E, Block, RA> wher
|
||||
}
|
||||
|
||||
|
||||
fn storage_hash(&self, id: &BlockId<Block>, key: &StorageKey) -> sp_blockchain::Result<Option<Block::Hash>>
|
||||
{
|
||||
fn storage_hash(
|
||||
&self,
|
||||
id: &BlockId<Block>,
|
||||
key: &StorageKey,
|
||||
) -> sp_blockchain::Result<Option<Block::Hash>> {
|
||||
Ok(self.state_at(id)?
|
||||
.storage_hash(&key.0).map_err(|e| sp_blockchain::Error::from_state(Box::new(e)))?
|
||||
)
|
||||
|
||||
@@ -92,9 +92,9 @@ pub struct StorageData(
|
||||
#[cfg(feature = "std")]
|
||||
pub type StorageMap = std::collections::BTreeMap<Vec<u8>, Vec<u8>>;
|
||||
|
||||
/// Child trie storage data.
|
||||
#[cfg(feature = "std")]
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
/// Child trie storage data.
|
||||
pub struct StorageChild {
|
||||
/// Child data for storage.
|
||||
pub data: StorageMap,
|
||||
@@ -103,9 +103,9 @@ pub struct StorageChild {
|
||||
pub child_info: ChildInfo,
|
||||
}
|
||||
|
||||
/// Struct containing data needed for a storage.
|
||||
#[cfg(feature = "std")]
|
||||
#[derive(Default, Debug, Clone)]
|
||||
/// Struct containing data needed for a storage.
|
||||
pub struct Storage {
|
||||
/// Top trie storage data.
|
||||
pub top: StorageMap,
|
||||
@@ -148,6 +148,9 @@ pub mod well_known_keys {
|
||||
/// Prefix of child storage keys.
|
||||
pub const CHILD_STORAGE_KEY_PREFIX: &'static [u8] = b":child_storage:";
|
||||
|
||||
/// Prefix of the default child storage keys in the top trie.
|
||||
pub const DEFAULT_CHILD_STORAGE_KEY_PREFIX: &'static [u8] = b":child_storage:default:";
|
||||
|
||||
/// Whether a key is a child storage key.
|
||||
///
|
||||
/// This is convenience function which basically checks if the given `key` starts
|
||||
@@ -300,7 +303,7 @@ impl ChildType {
|
||||
/// is one.
|
||||
pub fn parent_prefix(&self) -> &'static [u8] {
|
||||
match self {
|
||||
&ChildType::ParentKeyId => DEFAULT_CHILD_TYPE_PARENT_PREFIX,
|
||||
&ChildType::ParentKeyId => well_known_keys::DEFAULT_CHILD_STORAGE_KEY_PREFIX,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -331,12 +334,10 @@ impl ChildTrieParentKeyId {
|
||||
}
|
||||
}
|
||||
|
||||
const DEFAULT_CHILD_TYPE_PARENT_PREFIX: &'static [u8] = b":child_storage:default:";
|
||||
|
||||
#[test]
|
||||
fn test_prefix_default_child_info() {
|
||||
let child_info = ChildInfo::new_default(b"any key");
|
||||
let prefix = child_info.child_type().parent_prefix();
|
||||
assert!(prefix.starts_with(well_known_keys::CHILD_STORAGE_KEY_PREFIX));
|
||||
assert!(prefix.starts_with(DEFAULT_CHILD_TYPE_PARENT_PREFIX));
|
||||
assert!(prefix.starts_with(well_known_keys::DEFAULT_CHILD_STORAGE_KEY_PREFIX));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user