Skip to content

Commit 655a358

Browse files
committed
support iterate dump mode
1 parent 3e6a5af commit 655a358

File tree

11 files changed

+365
-57
lines changed

11 files changed

+365
-57
lines changed

Cargo.lock

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

bins/conflux/src/command/dump.rs

Lines changed: 42 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
use cfx_types::Address;
1+
use cfx_types::parse_hex_string;
22
use clap::{ArgMatches, Args};
33
use client::{
44
configuration::Configuration,
5-
state_dump::{dump_whole_state, StateDumpConfig},
5+
state_dump::{dump_whole_state, iterate_dump_whole_state, StateDumpConfig},
66
};
77
use parking_lot::{Condvar, Mutex};
88
use serde_json;
9-
use std::{collections::HashMap, fs, path::Path, str::FromStr, sync::Arc};
9+
use std::{collections::HashMap, fs, path::Path, sync::Arc};
1010

1111
#[derive(Args, Debug)]
1212
pub struct DumpCommand {
@@ -68,8 +68,7 @@ impl DumpCommand {
6868
}
6969

7070
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)
71+
let start_address = parse_hex_string(&self.start)
7372
.map_err(|e| format!("Invalid address: {}", e))?;
7473
Ok(StateDumpConfig {
7574
start_address,
@@ -97,58 +96,69 @@ impl DumpCommand {
9796
}
9897

9998
let exit = Arc::new((Mutex::new(false), Condvar::new()));
100-
10199
let config = self.get_state_dump_config()?;
102-
let state = dump_whole_state(conf, exit, &config)?;
103-
let total_accounts = state.accounts.len();
104100

105-
if self.multi_file {
101+
let _total_accounts = if self.multi_file {
106102
// 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);
103+
let state_root = iterate_dump_whole_state(
104+
conf,
105+
exit,
106+
&config,
107+
|account_state| {
108+
let address =
109+
account_state.address.expect("address is not set");
110+
let filename = format!("{:?}.json", address);
111+
let file_path = Path::new(output_path).join(&filename);
111112

112-
// Serialize account_state to JSON
113-
let json_content = serde_json::to_string_pretty(&account_state)
114-
.map_err(|e| {
115-
format!(
113+
// Serialize account_state to JSON
114+
let json_content =
115+
serde_json::to_string_pretty(&account_state)
116+
.map_err(|e| {
117+
format!(
116118
"Failed to serialize account state for {}: {}",
117119
address, e
118120
)
119-
})?;
121+
})
122+
.expect("Failed to serialize account state");
120123

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-
}
124+
// Write to file
125+
fs::write(&file_path, json_content)
126+
.map_err(|e| {
127+
format!(
128+
"Failed to write file {}: {}",
129+
file_path.display(),
130+
e
131+
)
132+
})
133+
.expect("Failed to write file");
134+
},
135+
)?;
130136

131137
// Write meta info
132-
let meta_file_path = Path::new(output_path).join("meta.json");
133138
let mut meta_info = HashMap::new();
134-
meta_info.insert("root".to_string(), state.root);
139+
meta_info.insert("root".to_string(), state_root);
140+
let meta_file_path = Path::new(output_path).join("meta.json");
135141
let meta_content = serde_json::to_string_pretty(&meta_info)
136142
.map_err(|e| format!("Failed to serialize state: {}", e))?;
137143
fs::write(&meta_file_path, meta_content)
138144
.map_err(|e| format!("Failed to write meta file: {}", e))?;
145+
0
139146
} else {
147+
let state = dump_whole_state(conf, exit, &config)?;
148+
let total_accounts = state.accounts.len();
140149
// Write to a single file
141150
let file_path = Path::new(output_path).join("state.json");
142151
let json_content = serde_json::to_string_pretty(&state)
143152
.map_err(|e| format!("Failed to serialize state: {}", e))?;
144153
fs::write(&file_path, json_content).map_err(|e| {
145154
format!("Failed to write file {}: {}", file_path.display(), e)
146155
})?;
147-
}
156+
total_accounts
157+
};
148158

149159
Ok(format!(
150-
"Dumped {} account state to output directory: {}",
151-
total_accounts, output_path
160+
"Dumped account state to output directory: {}",
161+
output_path
152162
))
153163
}
154164
}

crates/client/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ cfx-parity-trace-types = { workspace = true }
7979
cfx-tasks = { workspace = true }
8080
cfx-config = { workspace = true }
8181
cfxcore-types = { workspace = true }
82+
fallible-iterator = { workspace = true }
8283

8384
[dev-dependencies]
8485
criterion = { workspace = true }

crates/client/src/state_dump.rs

Lines changed: 139 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,21 @@ use cfx_config::Configuration;
33
use cfx_rpc_eth_types::{AccountState, StateDump, EOA_STORAGE_ROOT_H256};
44
use cfx_rpc_primitives::Bytes;
55
use cfx_statedb::{StateDbExt, StateDbGeneric};
6-
use cfx_storage::state_manager::StateManagerTrait;
6+
use cfx_storage::{
7+
state_manager::StateManagerTrait, utils::to_key_prefix_iter_upper_bound,
8+
KeyValueDbIterableTrait,
9+
};
710
use cfx_types::{Address, Space, H256};
811
use cfxcore::NodeType;
12+
use fallible_iterator::FallibleIterator;
913
use keccak_hash::{keccak, KECCAK_EMPTY};
1014
use parking_lot::{Condvar, Mutex};
1115
use primitives::{
1216
Account, SkipInputCheck, StorageKey, StorageKeyWithSpace, StorageValue,
1317
};
1418
use rlp::Rlp;
1519
use std::{
16-
collections::{BTreeMap, HashMap},
20+
collections::{BTreeMap, HashMap, HashSet},
1721
ops::Deref,
1822
sync::Arc,
1923
thread,
@@ -32,6 +36,44 @@ pub fn dump_whole_state(
3236
conf: &mut Configuration, exit_cond_var: Arc<(Mutex<bool>, Condvar)>,
3337
config: &StateDumpConfig,
3438
) -> Result<StateDump, String> {
39+
let (mut state_db, state_root) =
40+
prepare_state_db(conf, exit_cond_var, config)?;
41+
42+
let accounts =
43+
export_space_accounts(&mut state_db, Space::Ethereum, config)
44+
.map_err(|e| e.to_string())?;
45+
46+
let state_dump = StateDump {
47+
root: state_root,
48+
accounts,
49+
next: None,
50+
};
51+
52+
Ok(state_dump)
53+
}
54+
55+
pub fn iterate_dump_whole_state<F: Fn(AccountState)>(
56+
conf: &mut Configuration, exit_cond_var: Arc<(Mutex<bool>, Condvar)>,
57+
config: &StateDumpConfig, callback: F,
58+
) -> Result<H256, String> {
59+
let (mut state_db, state_root) =
60+
prepare_state_db(conf, exit_cond_var, config)?;
61+
62+
export_space_accounts_with_iterator(
63+
&mut state_db,
64+
Space::Ethereum,
65+
config,
66+
callback,
67+
)
68+
.map_err(|e| e.to_string())?;
69+
70+
Ok(state_root)
71+
}
72+
73+
fn prepare_state_db(
74+
conf: &mut Configuration, exit_cond_var: Arc<(Mutex<bool>, Condvar)>,
75+
config: &StateDumpConfig,
76+
) -> Result<(StateDbGeneric, H256), String> {
3577
println!("Preparing state...");
3678
let (
3779
data_man,
@@ -71,8 +113,6 @@ pub fn dump_whole_state(
71113
None => consensus.latest_confirmed_epoch_number(),
72114
};
73115

74-
println!("Start to dump state at epoch: {:?}", target_height);
75-
76116
let epoch_hash = consensus
77117
.get_hash_from_epoch_number(target_height.into())
78118
.map_err(|e| e.to_string())?;
@@ -92,19 +132,9 @@ pub fn dump_whole_state(
92132
.map_err(|e| e.to_string())?
93133
.ok_or("Failed to get state")?;
94134

95-
let mut state_db = StateDbGeneric::new(state);
96-
97-
let accounts =
98-
export_space_accounts(&mut state_db, Space::Ethereum, config)
99-
.map_err(|e| e.to_string())?;
100-
101-
let state_dump = StateDump {
102-
root: state_root.clone(),
103-
accounts,
104-
next: None,
105-
};
135+
let state_db = StateDbGeneric::new(state);
106136

107-
Ok(state_dump)
137+
Ok((state_db, state_root.clone()))
108138
}
109139

110140
fn export_space_accounts(
@@ -177,12 +207,18 @@ fn export_space_accounts(
177207
let code = if is_contract {
178208
codes_map.get(&address).cloned()
179209
} else {
210+
if let Some(code) = codes_map.get(&address) {
211+
println!("no-contract account have code: {:?}", code);
212+
}
180213
None
181214
};
182215

183216
let storage = if is_contract {
184217
storage_map.get(&address).cloned()
185218
} else {
219+
if let Some(storage) = storage_map.get(&address) {
220+
println!("no-contract account have storage: {:?}", storage);
221+
}
186222
None
187223
};
188224

@@ -207,6 +243,93 @@ fn export_space_accounts(
207243
Ok(accounts)
208244
}
209245

246+
fn export_space_accounts_with_iterator<F: Fn(AccountState)>(
247+
state: &mut StateDbGeneric, space: Space, config: &StateDumpConfig,
248+
callback: F,
249+
) -> Result<(), Box<dyn std::error::Error>> {
250+
let empty_key = StorageKey::EmptyKey.with_space(space);
251+
let (kvs, maybe_kv_iterator) = state.read_all_iterator(empty_key)?;
252+
253+
let mut deleted_keys = HashSet::new();
254+
let mut found_accounts = 0;
255+
256+
// Iterate key value pairs from delta trie and intermediate trie
257+
for (k, v) in kvs {
258+
let storage_key = StorageKeyWithSpace::from_delta_mpt_key(&k);
259+
let key = storage_key.to_key_bytes();
260+
deleted_keys.insert(key.clone());
261+
262+
let storage_key_with_space =
263+
StorageKeyWithSpace::from_key_bytes::<SkipInputCheck>(&key);
264+
if storage_key_with_space.space != space {
265+
continue;
266+
}
267+
268+
if let StorageKey::AccountKey(address_bytes) =
269+
storage_key_with_space.key
270+
{
271+
let address = Address::from_slice(address_bytes);
272+
println!("Find account: {:?}", address);
273+
let account = Account::new_from_rlp(address, &Rlp::new(&v))?;
274+
275+
let account_state = get_account_state(state, &account, config)?;
276+
callback(account_state);
277+
found_accounts += 1;
278+
279+
if config.limit > 0 && found_accounts >= config.limit as usize {
280+
break;
281+
}
282+
} else {
283+
continue;
284+
}
285+
}
286+
287+
let lower_bound_incl = empty_key.to_key_bytes();
288+
let upper_bound_excl = to_key_prefix_iter_upper_bound(&lower_bound_incl);
289+
290+
if let Some(mut kv_iterator) = maybe_kv_iterator {
291+
let mut kvs = kv_iterator
292+
.iter_range(
293+
lower_bound_incl.as_slice(),
294+
upper_bound_excl.as_ref().map(|v| &**v),
295+
)?
296+
.take();
297+
298+
while let Some((key, value)) = kvs.next()? {
299+
if deleted_keys.contains(&key) {
300+
continue;
301+
}
302+
303+
let storage_key_with_space =
304+
StorageKeyWithSpace::from_key_bytes::<SkipInputCheck>(&key);
305+
if storage_key_with_space.space != space {
306+
continue;
307+
}
308+
309+
if let StorageKey::AccountKey(address_bytes) =
310+
storage_key_with_space.key
311+
{
312+
let address = Address::from_slice(address_bytes);
313+
println!("Find account: {:?}", address);
314+
let account =
315+
Account::new_from_rlp(address, &Rlp::new(&value))?;
316+
317+
let account_state = get_account_state(state, &account, config)?;
318+
callback(account_state);
319+
found_accounts += 1;
320+
321+
if config.limit > 0 && found_accounts >= config.limit as usize {
322+
break;
323+
}
324+
} else {
325+
continue;
326+
}
327+
}
328+
}
329+
330+
Ok(())
331+
}
332+
210333
#[allow(unused)]
211334
fn get_account_state(
212335
state: &mut StateDbGeneric, account: &Account, config: &StateDumpConfig,

crates/dbs/statedb/src/lib.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,15 @@ mod impls {
188188
self.delete_all::<access_mode::Read>(key_prefix, debug_record)
189189
}
190190

191+
pub fn read_all_iterator(
192+
&mut self, access_key_prefix: StorageKeyWithSpace,
193+
) -> Result<(Vec<MptKeyValue>, Option<KvdbSqliteSharded<Box<[u8]>>>)>
194+
{
195+
self.storage
196+
.read_all_iterator(access_key_prefix)
197+
.map_err(|err| err.into())
198+
}
199+
191200
pub fn delete_all<AM: access_mode::AccessMode>(
192201
&mut self, key_prefix: StorageKeyWithSpace,
193202
debug_record: Option<&mut ComputeEpochDebugRecord>,
@@ -535,7 +544,7 @@ mod impls {
535544
};
536545
use cfx_storage::{
537546
utils::{access_mode, to_key_prefix_iter_upper_bound},
538-
MptKeyValue, StorageStateTrait,
547+
KvdbSqliteSharded, MptKeyValue, StorageStateTrait,
539548
};
540549
use cfx_types::{
541550
address_util::AddressUtil, Address, AddressWithSpace, Space,

crates/dbs/storage/src/impls/recording_storage.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ impl<Storage: StateTrait + StateTraitExt> StateTrait
5454
fn compute_state_root(&mut self) -> Result<StateRootWithAuxInfo>;
5555
fn get_state_root(&self) -> Result<StateRootWithAuxInfo>;
5656
fn commit(&mut self, epoch_id: EpochId) -> Result<StateRootWithAuxInfo>;
57+
fn read_all_iterator(&mut self, access_key_prefix: StorageKeyWithSpace) -> Result<(Vec<MptKeyValue>, Option<KvdbSqliteSharded<Box<[u8]>>>)>;
5758
}
5859
}
5960

@@ -94,6 +95,7 @@ impl<Storage: StateTrait + StateTraitExt> StateTrait
9495
use crate::{
9596
impls::{
9697
errors::*, merkle_patricia_trie::MptKeyValue, state_proof::StateProof,
98+
storage_db::kvdb_sqlite_sharded::KvdbSqliteSharded,
9799
},
98100
state::*,
99101
StateProofMerger,

0 commit comments

Comments
 (0)