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
35pub 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
59pub 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 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 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 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 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}