Skip to content

Commit 8bd7386

Browse files
committed
add dump subcommand which can dump all espace account as json file
1 parent fbcf102 commit 8bd7386

File tree

18 files changed

+842
-1
lines changed

18 files changed

+842
-1
lines changed

Cargo.lock

Lines changed: 71 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@ serde = { version = "1.0", features = [
272272
], default-features = false }
273273
serde_json = { version = "1.0", default-features = false, features = ["alloc"] }
274274
serde_derive = { version = "1.0", default-features = false }
275+
serde_with = { version = "3", default-features = false, features = ["macros"] }
275276
hex = "0.4"
276277
rustc-hex = "2.1"
277278
hex-literal = "1.0"

bins/conflux/src/cli.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::command::dump::DumpCommand;
12
use clap::{Args, Parser, Subcommand, ValueEnum};
23

34
/// Conflux client
@@ -267,6 +268,9 @@ pub enum Commands {
267268
/// Manage accounts
268269
#[command(subcommand_required = true, arg_required_else_help = true)]
269270
Account(AccountSubcommands),
271+
/// Dump eSpace account state at a given block number
272+
#[command(subcommand_required = false, arg_required_else_help = false)]
273+
Dump(DumpCommand),
270274
/// RPC based subcommands to query blockchain information and send
271275
/// transactions
272276
#[command(subcommand_required = true, arg_required_else_help = true)]

bins/conflux/src/command/dump.rs

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
use cfx_types::Address;
2+
use clap::{ArgMatches, Args};
3+
use client::{
4+
configuration::Configuration,
5+
state_dump::{dump_whole_state, StateDumpConfig},
6+
};
7+
use parking_lot::{Condvar, Mutex};
8+
use serde_json;
9+
use std::{collections::HashMap, fs, path::Path, str::FromStr, sync::Arc};
10+
11+
#[derive(Args, Debug)]
12+
pub struct DumpCommand {
13+
/// Include accounts for which we don't have the address (missing preimage)
14+
// #[arg(id = "incompletes", long = "incompletes")]
15+
// incompletes: bool,
16+
/// Print streaming JSON iteratively, delimited by newlines
17+
// #[arg(id = "iterative", long = "iterative", default_value = "true")]
18+
// iterative: bool,
19+
/// Max number of elements (0 = no limit)
20+
#[arg(
21+
id = "limit",
22+
long = "limit",
23+
value_name = "NUM",
24+
default_value = "0"
25+
)]
26+
limit: u64,
27+
/// Target block number, if not specified, the latest block will be used
28+
#[arg(id = "block", long = "block", value_name = "NUM")]
29+
block: Option<u64>,
30+
/// Exclude contract code (save db lookups)
31+
#[arg(id = "nocode", long = "nocode")]
32+
no_code: bool,
33+
/// Exclude storage entries (save db lookups)
34+
#[arg(id = "nostorage", long = "nostorage")]
35+
no_storage: bool,
36+
/// Start position address
37+
#[arg(
38+
id = "start",
39+
long = "start",
40+
value_name = "String",
41+
default_value = "0x0000000000000000000000000000000000000000"
42+
)]
43+
start: String,
44+
/// Path to the output folder (default: ./dump)
45+
#[arg(id = "output", long = "output", value_name = "PATH")]
46+
output: Option<String>,
47+
/// Multi file mode
48+
#[arg(id = "multifile", long = "multifile")]
49+
multi_file: bool,
50+
}
51+
52+
impl DumpCommand {
53+
pub fn parse(matches: &ArgMatches) -> Result<Self, String> {
54+
let output = matches.get_one::<String>("output").cloned();
55+
Ok(Self {
56+
block: matches.get_one::<u64>("block").cloned(),
57+
// incompletes: matches.get_flag("incompletes"),
58+
// iterative: matches.get_flag("iterative"),
59+
limit: matches.get_one::<u64>("limit").cloned().unwrap_or(0),
60+
no_code: matches.get_flag("nocode"),
61+
no_storage: matches.get_flag("nostorage"),
62+
start: matches.get_one::<String>("start").cloned().unwrap_or(
63+
"0x0000000000000000000000000000000000000000".to_string(),
64+
),
65+
output,
66+
multi_file: matches.get_flag("multifile"),
67+
})
68+
}
69+
70+
fn get_state_dump_config(&self) -> Result<StateDumpConfig, String> {
71+
let address_str = self.start.strip_prefix("0x").unwrap_or(&self.start);
72+
let start_address = Address::from_str(address_str)
73+
.map_err(|e| format!("Invalid address: {}", e))?;
74+
Ok(StateDumpConfig {
75+
start_address,
76+
limit: self.limit,
77+
block: self.block,
78+
no_code: self.no_code,
79+
no_storage: self.no_storage,
80+
})
81+
}
82+
83+
pub fn execute(&self, conf: &mut Configuration) -> Result<String, String> {
84+
// Determine output directory
85+
let output_path = match self.output {
86+
Some(ref path) => path,
87+
None => {
88+
"./dump" // Default to "./dump" if no output specified
89+
}
90+
};
91+
92+
// Ensure the directory exists
93+
if !Path::new(output_path).exists() {
94+
fs::create_dir_all(output_path).map_err(|e| {
95+
format!("Failed to create output directory: {}", e)
96+
})?;
97+
}
98+
99+
let exit = Arc::new((Mutex::new(false), Condvar::new()));
100+
101+
let config = self.get_state_dump_config()?;
102+
let state = dump_whole_state(conf, exit, &config)?;
103+
let total_accounts = state.accounts.len();
104+
105+
if self.multi_file {
106+
// Write to multiple files
107+
for (address, account_state) in state.accounts {
108+
// Create filename using address (without 0x prefix)
109+
let filename = format!("{}.json", address);
110+
let file_path = Path::new(output_path).join(&filename);
111+
112+
// Serialize account_state to JSON
113+
let json_content = serde_json::to_string_pretty(&account_state)
114+
.map_err(|e| {
115+
format!(
116+
"Failed to serialize account state for {}: {}",
117+
address, e
118+
)
119+
})?;
120+
121+
// Write to file
122+
fs::write(&file_path, json_content).map_err(|e| {
123+
format!(
124+
"Failed to write file {}: {}",
125+
file_path.display(),
126+
e
127+
)
128+
})?;
129+
}
130+
131+
// Write meta info
132+
let meta_file_path = Path::new(output_path).join("meta.json");
133+
let mut meta_info = HashMap::new();
134+
meta_info.insert("root".to_string(), state.root);
135+
let meta_content = serde_json::to_string_pretty(&meta_info)
136+
.map_err(|e| format!("Failed to serialize state: {}", e))?;
137+
fs::write(&meta_file_path, meta_content)
138+
.map_err(|e| format!("Failed to write meta file: {}", e))?;
139+
} else {
140+
// Write to a single file
141+
let file_path = Path::new(output_path).join("state.json");
142+
let json_content = serde_json::to_string_pretty(&state)
143+
.map_err(|e| format!("Failed to serialize state: {}", e))?;
144+
fs::write(&file_path, json_content).map_err(|e| {
145+
format!("Failed to write file {}: {}", file_path.display(), e)
146+
})?;
147+
}
148+
149+
Ok(format!(
150+
"Dumped {} account state to output directory: {}",
151+
total_accounts, output_path
152+
))
153+
}
154+
}

bins/conflux/src/command/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@
33
// See http://www.gnu.org/licenses/
44

55
pub mod account;
6+
pub mod dump;
67
pub mod helpers;
78
pub mod rpc;

bins/conflux/src/main.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ use client::{
1919
full::FullClient,
2020
light::LightClient,
2121
};
22-
use command::account::{AccountCmd, ImportAccounts, ListAccounts, NewAccount};
22+
use command::{
23+
account::{AccountCmd, ImportAccounts, ListAccounts, NewAccount},
24+
dump::DumpCommand,
25+
};
2326
use log::{info, LevelFilter};
2427
use log4rs::{
2528
append::{console::ConsoleAppender, file::FileAppender},
@@ -144,6 +147,16 @@ fn handle_sub_command(matches: &ArgMatches) -> Result<Option<String>, String> {
144147
return Ok(Some(execute_output));
145148
}
146149

150+
// dump sub-commands
151+
if let Some(("dump", dump_matches)) = matches.subcommand() {
152+
let dump_cmd = DumpCommand::parse(dump_matches).map_err(|e| {
153+
format!("Failed to parse dump command arguments: {}", e)
154+
})?;
155+
let mut conf = Configuration::parse(&matches)?;
156+
let execute_output = dump_cmd.execute(&mut conf)?;
157+
return Ok(Some(execute_output));
158+
}
159+
147160
// general RPC commands
148161
let mut subcmd_matches = matches;
149162
while let Some(m) = subcmd_matches.subcommand() {

crates/cfx_types/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ pub use ethereum_types::{
77
H160, H256, H512, H520, H64, U128, U256, U512, U64,
88
};
99

10+
pub type StorageKey = H256;
11+
pub type StorageValue = U256;
12+
1013
mod space;
1114
pub use space::{Space, SpaceMap};
1215

crates/client/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ mod node_types;
1313
pub mod rpc;
1414
pub use cfx_config as configuration;
1515
pub use node_types::{archive, full, light};
16+
pub mod state_dump;

0 commit comments

Comments
 (0)