conflux/command/
dump.rs

1use cfx_types::parse_hex_string;
2use clap::{ArgMatches, Args};
3use client::{
4    configuration::Configuration,
5    state_dump::{dump_whole_state, iterate_dump_whole_state, StateDumpConfig},
6};
7use parking_lot::{Condvar, Mutex};
8use serde_json;
9use std::{collections::HashMap, fs, path::Path, sync::Arc};
10
11#[derive(Args, Debug)]
12pub 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
52impl 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(
71        &self, output_path: &str,
72    ) -> Result<StateDumpConfig, String> {
73        let start_address = parse_hex_string(&self.start)
74            .map_err(|e| format!("Invalid address: {}", e))?;
75        Ok(StateDumpConfig {
76            start_address,
77            limit: self.limit,
78            block: self.block,
79            no_code: self.no_code,
80            no_storage: self.no_storage,
81            out_put_path: output_path.to_string(),
82        })
83    }
84
85    pub fn execute(&self, conf: &mut Configuration) -> Result<String, String> {
86        // Determine output directory
87        let output_path = match self.output {
88            Some(ref path) => path,
89            None => {
90                "./dump" // Default to "./dump" if no output specified
91            }
92        };
93
94        // Ensure the directory exists
95        if !Path::new(output_path).exists() {
96            fs::create_dir_all(output_path).map_err(|e| {
97                format!("Failed to create output directory: {}", e)
98            })?;
99        }
100
101        let exit = Arc::new((Mutex::new(false), Condvar::new()));
102        let config = self.get_state_dump_config(output_path)?;
103
104        let _total_accounts = if self.multi_file {
105            // Write to multiple files
106            let state_root = iterate_dump_whole_state(
107                conf,
108                exit,
109                &config,
110                |account_state| {
111                    let address =
112                        account_state.address.expect("address is not set");
113                    let filename = format!("{:?}.json", address);
114                    let file_path = Path::new(output_path).join(&filename);
115
116                    // Serialize account_state to JSON
117                    let json_content =
118                        serde_json::to_string_pretty(&account_state)
119                            .map_err(|e| {
120                                format!(
121                            "Failed to serialize account state for {}: {}",
122                            address, e
123                        )
124                            })
125                            .expect("Failed to serialize account state");
126
127                    // Write to file
128                    fs::write(&file_path, json_content)
129                        .map_err(|e| {
130                            format!(
131                                "Failed to write file {}: {}",
132                                file_path.display(),
133                                e
134                            )
135                        })
136                        .expect("Failed to write file");
137                },
138            )?;
139
140            // Write meta info
141            let mut meta_info = HashMap::new();
142            meta_info.insert("root".to_string(), state_root);
143            let meta_file_path = Path::new(output_path).join("meta.json");
144            let meta_content = serde_json::to_string_pretty(&meta_info)
145                .map_err(|e| format!("Failed to serialize state: {}", e))?;
146            fs::write(&meta_file_path, meta_content)
147                .map_err(|e| format!("Failed to write meta file: {}", e))?;
148            0
149        } else {
150            let state = dump_whole_state(conf, exit, &config)?;
151            let total_accounts = state.accounts.len();
152            // Write to a single file
153            let file_path = Path::new(output_path).join("state.json");
154            let json_content = serde_json::to_string_pretty(&state)
155                .map_err(|e| format!("Failed to serialize state: {}", e))?;
156            fs::write(&file_path, json_content).map_err(|e| {
157                format!("Failed to write file {}: {}", file_path.display(), e)
158            })?;
159            total_accounts
160        };
161
162        Ok(format!(
163            "Dumped account state to output directory: {}",
164            output_path
165        ))
166    }
167}