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 (data_man, _, _, consensus, sync_service, _, _, _, _, _, _, _) =
88 initialize_not_light_node_modules(
89 conf,
90 exit_cond_var,
91 NodeType::Archive,
92 )?;
93
94 while sync_service.catch_up_mode() {
95 thread::sleep(Duration::from_secs(1));
96 }
97
98 let state_manager = data_man.storage_manager.clone();
104 let target_height = match config.block {
105 Some(epoch) => epoch,
106 None => consensus.latest_confirmed_epoch_number(),
107 };
108
109 let epoch_hash = consensus
110 .get_hash_from_epoch_number(target_height.into())
111 .map_err(|e| e.to_string())?;
112
113 let block = consensus
114 .get_phantom_block_by_hash(&epoch_hash, false)?
115 .expect("Failed to get block");
116
117 let state_root = block.pivot_header.deferred_state_root();
118
119 let state_index = data_man
120 .get_state_readonly_index(&epoch_hash)
121 .ok_or("Failed to get state index")?;
122
123 let state = state_manager
124 .get_state_no_commit(state_index, true, Some(Space::Ethereum))
125 .map_err(|e| e.to_string())?
126 .ok_or("Failed to get state")?;
127
128 let state_db = StateDbGeneric::new(state);
129
130 Ok((state_db, *state_root))
131}
132
133fn export_space_accounts(
134 state: &mut StateDbGeneric, space: Space, config: &StateDumpConfig,
135) -> Result<BTreeMap<Address, AccountState>, Box<dyn std::error::Error>> {
136 println("Start to iterate state...");
137 let empty_key = StorageKey::EmptyKey.with_space(space);
138 let kv_pairs = state.read_all(empty_key, None)?;
139
140 let mut accounts_map = BTreeMap::new();
141 let mut codes_map = HashMap::new();
142 let mut storage_map = HashMap::new();
143
144 for (key, value) in kv_pairs {
145 let storage_key_with_space =
146 StorageKeyWithSpace::from_key_bytes::<SkipInputCheck>(&key);
147 if storage_key_with_space.space != space {
148 continue;
149 }
150 match storage_key_with_space.key {
151 StorageKey::AccountKey(address_bytes) => {
152 let address = Address::from_slice(address_bytes);
153 println(&format!("Find account: {:?}", address));
154 let account =
155 Account::new_from_rlp(address, &Rlp::new(&value))?;
156 accounts_map.insert(address, account);
157 }
158 StorageKey::CodeKey {
159 address_bytes,
160 code_hash_bytes: _,
161 } => {
162 if config.no_code {
163 continue;
164 }
165
166 let address = Address::from_slice(address_bytes);
167 let code = Bytes(value.to_vec());
168 codes_map.insert(address, code);
169 }
170 StorageKey::StorageKey {
171 address_bytes,
172 storage_key,
173 } => {
174 if config.no_storage {
175 continue;
176 }
177
178 let address = Address::from_slice(address_bytes);
179 let h256_storage_key = H256::from_slice(storage_key);
180 let storage_value_with_owner: StorageValue =
181 rlp::decode(&value)?;
182 let account_storage_map =
183 storage_map.entry(address).or_insert(BTreeMap::new());
184 account_storage_map
185 .insert(h256_storage_key, storage_value_with_owner.value);
186 }
187 _ => {
188 continue;
189 }
190 }
191 }
192
193 let mut accounts = BTreeMap::new();
194
195 for (address, account) in accounts_map {
196 let is_contract = account.code_hash != KECCAK_EMPTY;
197 let root = EOA_STORAGE_ROOT_H256;
199 let address_hash = keccak(address);
200
201 let code = if is_contract {
202 codes_map.get(&address).cloned()
203 } else {
204 if let Some(code) = codes_map.get(&address) {
205 println(&format!("no-contract account have code: {:?}", code));
206 }
207 None
208 };
209
210 let storage = if is_contract {
211 storage_map.get(&address).cloned()
212 } else {
213 if let Some(_storage) = storage_map.get(&address) {
214 println("no-contract account have storage");
215 }
216 None
217 };
218
219 let account_state = AccountState {
220 balance: account.balance,
221 nonce: account.nonce.as_u64(),
222 root,
223 code_hash: account.code_hash,
224 code,
225 storage,
226 address: Some(address),
227 address_hash: Some(address_hash),
228 };
229
230 accounts.insert(address, account_state);
231
232 if config.limit > 0 && accounts.len() >= config.limit as usize {
233 break;
234 }
235 }
236
237 Ok(accounts)
238}
239
240pub fn export_space_accounts_with_callback<F: Fn(AccountState)>(
241 state: &mut StateDbGeneric, space: Space, config: &StateDumpConfig,
242 callback: F,
243) -> Result<(), Box<dyn std::error::Error>> {
244 println("Start to iterate state...");
245 let mut found_accounts = 0;
246 let mut core_space_key_count: u64 = 0;
247 let mut total_key_count: u64 = 0;
248
249 for i in 0..=255 {
250 let prefix = [i];
251 let start_key = StorageKey::AddressPrefixKey(&prefix).with_space(space);
252
253 let mut account_states = BTreeMap::new();
254
255 let mut inner_callback = |(key, value): (Vec<u8>, Box<[u8]>)| {
256 total_key_count += 1;
257
258 if total_key_count.is_multiple_of(10000) {
259 println(&format!(
260 "total_key_count: {}, core_space_key_count: {}",
261 total_key_count, core_space_key_count
262 ));
263 }
264
265 let storage_key_with_space =
266 StorageKeyWithSpace::from_key_bytes::<SkipInputCheck>(&key);
267 if storage_key_with_space.space != space {
268 core_space_key_count += 1;
269 return;
270 }
271
272 if let StorageKey::AccountKey(address_bytes) =
273 storage_key_with_space.key
274 {
275 let address = Address::from_slice(address_bytes);
276 println(&format!("Find account: {:?}", address));
277 let account = Account::new_from_rlp(address, &Rlp::new(&value))
278 .expect("Failed to decode account");
279
280 account_states.insert(address, account);
281 }
282 };
283
284 state.read_all_with_callback(start_key, &mut inner_callback, true)?;
285
286 if !account_states.is_empty() {
287 println("Start to read account code and storage data...");
288 }
289
290 for (_address, account) in account_states {
291 let account_state =
292 get_account_state(state, &account, config, space)?;
293 callback(account_state);
294 found_accounts += 1;
295 if config.limit > 0 && found_accounts >= config.limit as usize {
296 break;
297 }
298 }
299 }
300
301 Ok(())
302}
303
304#[allow(unused)]
305fn get_account_state(
306 state: &mut StateDbGeneric, account: &Account, config: &StateDumpConfig,
307 space: Space,
308) -> Result<AccountState, Box<dyn std::error::Error>> {
309 let address = account.address();
310
311 let is_contract = account.code_hash != KECCAK_EMPTY;
312 let code = if is_contract && !config.no_code {
314 state
315 .get_code(address, &account.code_hash)?
316 .map(|code_info| Bytes(code_info.code.deref().to_vec()))
317 } else {
318 None
319 };
320
321 let storage = if is_contract && !config.no_storage {
322 let storage =
323 get_contract_storage(state, &address.address, space, config)?;
324 Some(storage)
325 } else {
326 None
327 };
328
329 let root = EOA_STORAGE_ROOT_H256;
331
332 let address_hash = keccak(address.address);
333
334 Ok(AccountState {
335 balance: account.balance,
336 nonce: account.nonce.as_u64(),
337 root,
338 code_hash: account.code_hash,
339 code,
340 storage,
341 address: Some(address.address),
342 address_hash: Some(address_hash),
343 })
344}
345
346fn get_contract_storage(
347 state: &mut StateDbGeneric, address: &Address, space: Space,
348 config: &StateDumpConfig,
349) -> Result<BTreeMap<H256, U256>, Box<dyn std::error::Error>> {
350 let mut storage: BTreeMap<H256, U256> = Default::default();
351 let mut chunk_count = 0;
352
353 let mut inner_callback = |(key, value): (Vec<u8>, Box<[u8]>)| {
354 let storage_key_with_space =
355 StorageKeyWithSpace::from_key_bytes::<SkipInputCheck>(&key);
356 if storage_key_with_space.space != space {
357 return;
358 }
359
360 if let StorageKey::StorageKey {
361 address_bytes: _,
362 storage_key,
363 } = storage_key_with_space.key
364 {
365 let h256_storage_key = H256::from_slice(storage_key);
366 let storage_value_with_owner: StorageValue =
367 rlp::decode(&value).expect("Failed to decode storage value");
368 storage.insert(h256_storage_key, storage_value_with_owner.value);
369
370 if storage.len() == 5_000_000 {
371 chunk_count += 1;
372 let name = format!("{:?}-chunk{}.json", address, chunk_count);
373 let file_path = Path::new(&config.out_put_path).join(&name);
374 let json_content = serde_json::to_string_pretty(&storage)
375 .expect("Failed to serialize storage");
376 fs::write(&file_path, json_content)
377 .expect("Failed to write storage file");
378 storage.clear();
379 }
380 };
381 };
382
383 let start_key = StorageKey::new_storage_root_key(address).with_space(space);
384 state.read_all_with_callback(start_key, &mut inner_callback, false)?;
385
386 Ok(storage)
387}
388
389fn println(message: &str) {
390 println!("[{}] {}", Utc::now().format("%Y-%m-%d %H:%M:%S"), message);
391}