Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
73 changes: 73 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
167 changes: 167 additions & 0 deletions bins/conflux/src/command/dump.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
use cfx_types::parse_hex_string;
use clap::{ArgMatches, Args};
use client::{
configuration::Configuration,
state_dump::{dump_whole_state, iterate_dump_whole_state, StateDumpConfig},
};
use parking_lot::{Condvar, Mutex};
use serde_json;
use std::{collections::HashMap, fs, path::Path, 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, output_path: &str,
) -> Result<StateDumpConfig, String> {
let start_address = parse_hex_string(&self.start)
.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,
out_put_path: output_path.to_string(),
})
}

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(output_path)?;

let _total_accounts = if self.multi_file {
// Write to multiple files
let state_root = iterate_dump_whole_state(
conf,
exit,
&config,
|account_state| {
let address =
account_state.address.expect("address is not set");
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
)
})
.expect("Failed to serialize account state");

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

// Write meta info
let mut meta_info = HashMap::new();
meta_info.insert("root".to_string(), state_root);
let meta_file_path = Path::new(output_path).join("meta.json");
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))?;
0
} else {
let state = dump_whole_state(conf, exit, &config)?;
let total_accounts = state.accounts.len();
// 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)
})?;
total_accounts
};

Ok(format!(
"Dumped account state to output directory: {}",
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 @@ -30,7 +30,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 @@ -155,6 +158,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
2 changes: 2 additions & 0 deletions crates/client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ cfx-parity-trace-types = { workspace = true }
cfx-tasks = { workspace = true }
cfx-config = { workspace = true }
cfxcore-types = { workspace = true }
fallible-iterator = { workspace = true }
chrono = { workspace = true }
cfx-mallocator-utils = { workspace = true }

[dev-dependencies]
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