Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ serde = { version = "1.0", features = [
], default-features = false }
serde_json = { version = "1.0", default-features = false, features = ["alloc"] }
serde_derive = { version = "1.0", default-features = false }
serde_with = { version = "3", default-features = false, features = ["macros"] }
hex = "0.4"
rustc-hex = "2.1"
hex-literal = "1.0"
Expand Down
4 changes: 4 additions & 0 deletions bins/conflux/src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::command::dump::DumpCommand;
use clap::{Args, Parser, Subcommand, ValueEnum};

/// Conflux client
Expand Down Expand Up @@ -267,6 +268,9 @@ pub enum Commands {
/// Manage accounts
#[command(subcommand_required = true, arg_required_else_help = true)]
Account(AccountSubcommands),
/// Dump eSpace account state at a given block number
#[command(subcommand_required = false, arg_required_else_help = false)]
Dump(DumpCommand),
/// RPC based subcommands to query blockchain information and send
/// transactions
#[command(subcommand_required = true, arg_required_else_help = true)]
Expand Down
154 changes: 154 additions & 0 deletions bins/conflux/src/command/dump.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
use cfx_types::Address;
use clap::{ArgMatches, Args};
use client::{
configuration::Configuration,
state_dump::{dump_whole_state, StateDumpConfig},
};
use parking_lot::{Condvar, Mutex};
use serde_json;
use std::{collections::HashMap, fs, path::Path, str::FromStr, sync::Arc};

#[derive(Args, Debug)]
pub struct DumpCommand {
/// Include accounts for which we don't have the address (missing preimage)
// #[arg(id = "incompletes", long = "incompletes")]
// incompletes: bool,
/// Print streaming JSON iteratively, delimited by newlines
// #[arg(id = "iterative", long = "iterative", default_value = "true")]
// iterative: bool,
/// Max number of elements (0 = no limit)
#[arg(
id = "limit",
long = "limit",
value_name = "NUM",
default_value = "0"
)]
limit: u64,
/// Target block number, if not specified, the latest block will be used
#[arg(id = "block", long = "block", value_name = "NUM")]
block: Option<u64>,
/// Exclude contract code (save db lookups)
#[arg(id = "nocode", long = "nocode")]
no_code: bool,
/// Exclude storage entries (save db lookups)
#[arg(id = "nostorage", long = "nostorage")]
no_storage: bool,
/// Start position address
#[arg(
id = "start",
long = "start",
value_name = "String",
default_value = "0x0000000000000000000000000000000000000000"
)]
start: String,
/// Path to the output folder (default: ./dump)
#[arg(id = "output", long = "output", value_name = "PATH")]
output: Option<String>,
/// Multi file mode
#[arg(id = "multifile", long = "multifile")]
multi_file: bool,
}

impl DumpCommand {
pub fn parse(matches: &ArgMatches) -> Result<Self, String> {
let output = matches.get_one::<String>("output").cloned();
Ok(Self {
block: matches.get_one::<u64>("block").cloned(),
// incompletes: matches.get_flag("incompletes"),
// iterative: matches.get_flag("iterative"),
limit: matches.get_one::<u64>("limit").cloned().unwrap_or(0),
no_code: matches.get_flag("nocode"),
no_storage: matches.get_flag("nostorage"),
start: matches.get_one::<String>("start").cloned().unwrap_or(
"0x0000000000000000000000000000000000000000".to_string(),
),
output,
multi_file: matches.get_flag("multifile"),
})
}

fn get_state_dump_config(&self) -> Result<StateDumpConfig, String> {
let address_str = self.start.strip_prefix("0x").unwrap_or(&self.start);
let start_address = Address::from_str(address_str)
.map_err(|e| format!("Invalid address: {}", e))?;
Ok(StateDumpConfig {
start_address,
limit: self.limit,
block: self.block,
no_code: self.no_code,
no_storage: self.no_storage,
})
}

pub fn execute(&self, conf: &mut Configuration) -> Result<String, String> {
// Determine output directory
let output_path = match self.output {
Some(ref path) => path,
None => {
"./dump" // Default to "./dump" if no output specified
}
};

// Ensure the directory exists
if !Path::new(output_path).exists() {
fs::create_dir_all(output_path).map_err(|e| {
format!("Failed to create output directory: {}", e)
})?;
}

let exit = Arc::new((Mutex::new(false), Condvar::new()));

let config = self.get_state_dump_config()?;
let state = dump_whole_state(conf, exit, &config)?;
let total_accounts = state.accounts.len();

if self.multi_file {
// Write to multiple files
for (address, account_state) in state.accounts {
// Create filename using address (without 0x prefix)
let filename = format!("{}.json", address);
let file_path = Path::new(output_path).join(&filename);

// Serialize account_state to JSON
let json_content = serde_json::to_string_pretty(&account_state)
.map_err(|e| {
format!(
"Failed to serialize account state for {}: {}",
address, e
)
})?;

// Write to file
fs::write(&file_path, json_content).map_err(|e| {
format!(
"Failed to write file {}: {}",
file_path.display(),
e
)
})?;
}

// Write meta info
let meta_file_path = Path::new(output_path).join("meta.json");
let mut meta_info = HashMap::new();
meta_info.insert("root".to_string(), state.root);
let meta_content = serde_json::to_string_pretty(&meta_info)
.map_err(|e| format!("Failed to serialize state: {}", e))?;
fs::write(&meta_file_path, meta_content)
.map_err(|e| format!("Failed to write meta file: {}", e))?;
} else {
// Write to a single file
let file_path = Path::new(output_path).join("state.json");
let json_content = serde_json::to_string_pretty(&state)
.map_err(|e| format!("Failed to serialize state: {}", e))?;
fs::write(&file_path, json_content).map_err(|e| {
format!("Failed to write file {}: {}", file_path.display(), e)
})?;
}

Ok(format!(
"Dumped {} account state to output directory: {}",
total_accounts, output_path
))
}
}
1 change: 1 addition & 0 deletions bins/conflux/src/command/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
// See http://www.gnu.org/licenses/

pub mod account;
pub mod dump;
pub mod helpers;
pub mod rpc;
15 changes: 14 additions & 1 deletion bins/conflux/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ use client::{
full::FullClient,
light::LightClient,
};
use command::account::{AccountCmd, ImportAccounts, ListAccounts, NewAccount};
use command::{
account::{AccountCmd, ImportAccounts, ListAccounts, NewAccount},
dump::DumpCommand,
};
use log::{info, LevelFilter};
use log4rs::{
append::{console::ConsoleAppender, file::FileAppender},
Expand Down Expand Up @@ -144,6 +147,16 @@ fn handle_sub_command(matches: &ArgMatches) -> Result<Option<String>, String> {
return Ok(Some(execute_output));
}

// dump sub-commands
if let Some(("dump", dump_matches)) = matches.subcommand() {
let dump_cmd = DumpCommand::parse(dump_matches).map_err(|e| {
format!("Failed to parse dump command arguments: {}", e)
})?;
let mut conf = Configuration::parse(&matches)?;
let execute_output = dump_cmd.execute(&mut conf)?;
return Ok(Some(execute_output));
}

// general RPC commands
let mut subcmd_matches = matches;
while let Some(m) = subcmd_matches.subcommand() {
Expand Down
3 changes: 3 additions & 0 deletions crates/cfx_types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ pub use ethereum_types::{
H160, H256, H512, H520, H64, U128, U256, U512, U64,
};

pub type StorageKey = H256;
pub type StorageValue = U256;

mod space;
pub use space::{Space, SpaceMap};

Expand Down
1 change: 1 addition & 0 deletions crates/client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ mod node_types;
pub mod rpc;
pub use cfx_config as configuration;
pub use node_types::{archive, full, light};
pub mod state_dump;
Loading
Loading