client/
state_dump.rs

1use crate::common::initialize_not_light_node_modules;
2use cfx_config::Configuration;
3use cfx_rpc_eth_types::{AccountState, StateDump, EOA_STORAGE_ROOT_H256};
4use cfx_rpc_primitives::Bytes;
5use cfx_statedb::{StateDbExt, StateDbGeneric};
6use cfx_storage::state_manager::StateManagerTrait;
7use cfx_types::{Address, Space, H256, U256};
8use cfxcore::NodeType;
9use chrono::Utc;
10use keccak_hash::{keccak, KECCAK_EMPTY};
11use parking_lot::{Condvar, Mutex};
12use primitives::{
13    Account, SkipInputCheck, StorageKey, StorageKeyWithSpace, StorageValue,
14};
15use rlp::Rlp;
16use std::{
17    collections::{BTreeMap, HashMap},
18    fs,
19    ops::Deref,
20    path::Path,
21    sync::Arc,
22    thread,
23    time::Duration,
24};
25
26pub struct StateDumpConfig {
27    pub start_address: Address,
28    pub limit: u64,
29    pub block: Option<u64>,
30    pub no_code: bool,
31    pub no_storage: bool,
32    pub out_put_path: String,
33}
34
35// This method will read all data (k, v) from the Conflux state tree (including
36// core space and espace accounts, code, storage, deposit, vote_list) into
37// memory at once, then parse and assemble them and assemble all account states
38// into a StateDump struct and return it
39pub fn dump_whole_state(
40    conf: &mut Configuration, exit_cond_var: Arc<(Mutex<bool>, Condvar)>,
41    config: &StateDumpConfig,
42) -> Result<StateDump, String> {
43    let (mut state_db, state_root) =
44        prepare_state_db(conf, exit_cond_var, config)?;
45
46    let accounts =
47        export_space_accounts(&mut state_db, Space::Ethereum, config)
48            .map_err(|e| e.to_string())?;
49
50    let state_dump = StateDump {
51        root: state_root,
52        accounts,
53        next: None,
54    };
55
56    Ok(state_dump)
57}
58
59// This method will iterate through the entire state tree, storing each found
60// account in a temporary map After iterating through all accounts, it will
61// retrieve the code and storage for each account, then call the callback method
62// Pass the AccountState as a parameter to the callback method, which will
63// handle the AccountState
64pub fn iterate_dump_whole_state<F: Fn(AccountState)>(
65    conf: &mut Configuration, exit_cond_var: Arc<(Mutex<bool>, Condvar)>,
66    config: &StateDumpConfig, callback: F,
67) -> Result<H256, String> {
68    let (mut state_db, state_root) =
69        prepare_state_db(conf, exit_cond_var, config)?;
70
71    export_space_accounts_with_callback(
72        &mut state_db,
73        Space::Ethereum,
74        config,
75        callback,
76    )
77    .map_err(|e| e.to_string())?;
78
79    Ok(state_root)
80}
81
82fn prepare_state_db(
83    conf: &mut Configuration, exit_cond_var: Arc<(Mutex<bool>, Condvar)>,
84    config: &StateDumpConfig,
85) -> Result<(StateDbGeneric, H256), String> {
86    println("Preparing state...");
87    let (
88        data_man,
89        _,
90        _,
91        consensus,
92        sync_service,
93        _,
94        _,
95        _,
96        _,
97        _,
98        _,
99        _,
100        _,
101        _,
102        _,
103        _,
104        _,
105        _,
106    ) = initialize_not_light_node_modules(
107        conf,
108        exit_cond_var,
109        NodeType::Archive,
110    )?;
111
112    while sync_service.catch_up_mode() {
113        thread::sleep(Duration::from_secs(1));
114    }
115
116    /*
117    1. Get the state at the target epoch, or the latest state if target_epoch is None
118    2. Iterate through the state, and dump the account state
119    */
120
121    let state_manager = data_man.storage_manager.clone();
122    let target_height = match config.block {
123        Some(epoch) => epoch,
124        None => consensus.latest_confirmed_epoch_number(),
125    };
126
127    let epoch_hash = consensus
128        .get_hash_from_epoch_number(target_height.into())
129        .map_err(|e| e.to_string())?;
130
131    let block = consensus
132        .get_phantom_block_by_hash(&epoch_hash, false)?
133        .expect("Failed to get block");
134
135    let state_root = block.pivot_header.deferred_state_root();
136
137    let state_index = data_man
138        .get_state_readonly_index(&epoch_hash)
139        .ok_or("Failed to get state index")?;
140
141    let state = state_manager
142        .get_state_no_commit(state_index, true, Some(Space::Ethereum))
143        .map_err(|e| e.to_string())?
144        .ok_or("Failed to get state")?;
145
146    let state_db = StateDbGeneric::new(state);
147
148    Ok((state_db, state_root.clone()))
149}
150
151fn export_space_accounts(
152    state: &mut StateDbGeneric, space: Space, config: &StateDumpConfig,
153) -> Result<BTreeMap<Address, AccountState>, Box<dyn std::error::Error>> {
154    println("Start to iterate state...");
155    let empty_key = StorageKey::EmptyKey.with_space(space);
156    let kv_pairs = state.read_all(empty_key, None)?;
157
158    let mut accounts_map = BTreeMap::new();
159    let mut codes_map = HashMap::new();
160    let mut storage_map = HashMap::new();
161
162    for (key, value) in kv_pairs {
163        let storage_key_with_space =
164            StorageKeyWithSpace::from_key_bytes::<SkipInputCheck>(&key);
165        if storage_key_with_space.space != space {
166            continue;
167        }
168        match storage_key_with_space.key {
169            StorageKey::AccountKey(address_bytes) => {
170                let address = Address::from_slice(address_bytes);
171                println(&format!("Find account: {:?}", address));
172                let account =
173                    Account::new_from_rlp(address, &Rlp::new(&value))?;
174                accounts_map.insert(address, account);
175            }
176            StorageKey::CodeKey {
177                address_bytes,
178                code_hash_bytes: _,
179            } => {
180                if config.no_code {
181                    continue;
182                }
183
184                let address = Address::from_slice(address_bytes);
185                let code = Bytes(value.to_vec());
186                codes_map.insert(address, code);
187            }
188            StorageKey::StorageKey {
189                address_bytes,
190                storage_key,
191            } => {
192                if config.no_storage {
193                    continue;
194                }
195
196                let address = Address::from_slice(address_bytes);
197                let h256_storage_key = H256::from_slice(storage_key);
198                let storage_value_with_owner: StorageValue =
199                    rlp::decode(&value)?;
200                let account_storage_map =
201                    storage_map.entry(address).or_insert(BTreeMap::new());
202                account_storage_map
203                    .insert(h256_storage_key, storage_value_with_owner.value);
204            }
205            _ => {
206                continue;
207            }
208        }
209    }
210
211    let mut accounts = BTreeMap::new();
212
213    for (address, account) in accounts_map {
214        let is_contract = account.code_hash != KECCAK_EMPTY;
215        // conflux state tree don't have storage root, so we use a fixed value
216        let root = EOA_STORAGE_ROOT_H256;
217        let address_hash = keccak(address);
218
219        let code = if is_contract {
220            codes_map.get(&address).cloned()
221        } else {
222            if let Some(code) = codes_map.get(&address) {
223                println(&format!("no-contract account have code: {:?}", code));
224            }
225            None
226        };
227
228        let storage = if is_contract {
229            storage_map.get(&address).cloned()
230        } else {
231            if let Some(_storage) = storage_map.get(&address) {
232                println(&format!("no-contract account have storage"));
233            }
234            None
235        };
236
237        let account_state = AccountState {
238            balance: account.balance,
239            nonce: account.nonce.as_u64(),
240            root,
241            code_hash: account.code_hash,
242            code,
243            storage,
244            address: Some(address),
245            address_hash: Some(address_hash),
246        };
247
248        accounts.insert(address, account_state);
249
250        if config.limit > 0 && accounts.len() >= config.limit as usize {
251            break;
252        }
253    }
254
255    Ok(accounts)
256}
257
258pub fn export_space_accounts_with_callback<F: Fn(AccountState)>(
259    state: &mut StateDbGeneric, space: Space, config: &StateDumpConfig,
260    callback: F,
261) -> Result<(), Box<dyn std::error::Error>> {
262    println("Start to iterate state...");
263    let mut found_accounts = 0;
264    let mut core_space_key_count: u64 = 0;
265    let mut total_key_count: u64 = 0;
266
267    for i in 0..=255 {
268        let prefix = [i];
269        let start_key = StorageKey::AddressPrefixKey(&prefix).with_space(space);
270
271        let mut account_states = BTreeMap::new();
272
273        let mut inner_callback = |(key, value): (Vec<u8>, Box<[u8]>)| {
274            total_key_count += 1;
275
276            if total_key_count % 10000 == 0 {
277                println(&format!(
278                    "total_key_count: {}, core_space_key_count: {}",
279                    total_key_count, core_space_key_count
280                ));
281            }
282
283            let storage_key_with_space =
284                StorageKeyWithSpace::from_key_bytes::<SkipInputCheck>(&key);
285            if storage_key_with_space.space != space {
286                core_space_key_count += 1;
287                return;
288            }
289
290            if let StorageKey::AccountKey(address_bytes) =
291                storage_key_with_space.key
292            {
293                let address = Address::from_slice(address_bytes);
294                println(&format!("Find account: {:?}", address));
295                let account = Account::new_from_rlp(address, &Rlp::new(&value))
296                    .expect("Failed to decode account");
297
298                account_states.insert(address, account);
299            }
300        };
301
302        state.read_all_with_callback(start_key, &mut inner_callback, true)?;
303
304        if account_states.len() > 0 {
305            println("Start to read account code and storage data...");
306        }
307
308        for (_address, account) in account_states {
309            let account_state =
310                get_account_state(state, &account, config, space)?;
311            callback(account_state);
312            found_accounts += 1;
313            if config.limit > 0 && found_accounts >= config.limit as usize {
314                break;
315            }
316        }
317    }
318
319    Ok(())
320}
321
322#[allow(unused)]
323fn get_account_state(
324    state: &mut StateDbGeneric, account: &Account, config: &StateDumpConfig,
325    space: Space,
326) -> Result<AccountState, Box<dyn std::error::Error>> {
327    let address = account.address();
328
329    let is_contract = account.code_hash != KECCAK_EMPTY;
330    // get code
331    let code = if is_contract && !config.no_code {
332        state
333            .get_code(address, &account.code_hash)?
334            .map(|code_info| Bytes(code_info.code.deref().to_vec()))
335    } else {
336        None
337    };
338
339    let storage = if is_contract && !config.no_storage {
340        let storage =
341            get_contract_storage(state, &address.address, space, config)?;
342        Some(storage)
343    } else {
344        None
345    };
346
347    // conflux state tree don't have storage root, so we use a fixed value
348    let root = EOA_STORAGE_ROOT_H256;
349
350    let address_hash = keccak(address.address);
351
352    Ok(AccountState {
353        balance: account.balance,
354        nonce: account.nonce.as_u64(),
355        root,
356        code_hash: account.code_hash,
357        code,
358        storage,
359        address: Some(address.address),
360        address_hash: Some(address_hash),
361    })
362}
363
364fn get_contract_storage(
365    state: &mut StateDbGeneric, address: &Address, space: Space,
366    config: &StateDumpConfig,
367) -> Result<BTreeMap<H256, U256>, Box<dyn std::error::Error>> {
368    let mut storage: BTreeMap<H256, U256> = Default::default();
369    let mut chunk_count = 0;
370
371    let mut inner_callback = |(key, value): (Vec<u8>, Box<[u8]>)| {
372        let storage_key_with_space =
373            StorageKeyWithSpace::from_key_bytes::<SkipInputCheck>(&key);
374        if storage_key_with_space.space != space {
375            return;
376        }
377
378        if let StorageKey::StorageKey {
379            address_bytes: _,
380            storage_key,
381        } = storage_key_with_space.key
382        {
383            let h256_storage_key = H256::from_slice(storage_key);
384            let storage_value_with_owner: StorageValue =
385                rlp::decode(&value).expect("Failed to decode storage value");
386            storage.insert(h256_storage_key, storage_value_with_owner.value);
387
388            if storage.len() == 5000_000 {
389                chunk_count += 1;
390                let name = format!("{:?}-chunk{}.json", address, chunk_count);
391                let file_path = Path::new(&config.out_put_path).join(&name);
392                let json_content = serde_json::to_string_pretty(&storage)
393                    .expect("Failed to serialize storage");
394                fs::write(&file_path, json_content)
395                    .expect("Failed to write storage file");
396                storage.clear();
397            }
398        };
399    };
400
401    let start_key = StorageKey::new_storage_root_key(address).with_space(space);
402    state.read_all_with_callback(start_key, &mut inner_callback, false)?;
403
404    Ok(storage)
405}
406
407fn println(message: &str) {
408    println!("[{}] {}", Utc::now().format("%Y-%m-%d %H:%M:%S"), message);
409}