Skip to content

Commit 18378bb

Browse files
committed
Apply paritytech#5956 manually
1 parent f5245c8 commit 18378bb

File tree

7 files changed

+310
-2
lines changed

7 files changed

+310
-2
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.

substrate/client/api/src/backend.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,9 @@ pub trait BlockImportOperation<Block: BlockT> {
232232
/// Add a transaction index operation.
233233
fn update_transaction_index(&mut self, index: Vec<IndexOperation>)
234234
-> sp_blockchain::Result<()>;
235+
236+
/// Configure whether to commit the state changes to the underlying database.
237+
fn set_commit_state(&mut self, commit: bool);
235238
}
236239

237240
/// Interface for performing operations on the backend.
@@ -632,6 +635,27 @@ pub trait Backend<Block: BlockT>: AuxStore + Send + Sync {
632635

633636
/// Tells whether the backend requires full-sync mode.
634637
fn requires_full_sync(&self) -> bool;
638+
639+
/// Import the state changes directly to the database.
640+
///
641+
/// # Arguments
642+
///
643+
/// - `at`: The block hash corresponding to the last available state before updating the trie
644+
/// database.
645+
/// - `storage`: The storage changes reflecting the transition from the last local state to the
646+
/// target block's state being imported.
647+
/// - `state_version`: The state version of the target block, which is resolved from the
648+
/// provided `storage` data.
649+
///
650+
/// # Returns
651+
///
652+
/// Returns the state root after importing the state.
653+
fn import_state(
654+
&self,
655+
at: Block::Hash,
656+
storage: sp_runtime::Storage,
657+
state_version: sp_runtime::StateVersion,
658+
) -> sp_blockchain::Result<Block::Hash>;
635659
}
636660

637661
/// Mark for all Backend implementations, that are making use of state data, stored locally.

substrate/client/api/src/in_mem.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,8 @@ impl<Block: BlockT> backend::BlockImportOperation<Block> for BlockImportOperatio
584584
) -> sp_blockchain::Result<()> {
585585
Ok(())
586586
}
587+
588+
fn set_commit_state(&mut self, _commit: bool) {}
587589
}
588590

589591
/// In-memory backend. Keeps all states and blocks in memory.
@@ -774,6 +776,15 @@ impl<Block: BlockT> backend::Backend<Block> for Backend<Block> {
774776
let mut blocks = self.pinned_blocks.write();
775777
blocks.entry(hash).and_modify(|counter| *counter -= 1).or_insert(-1);
776778
}
779+
780+
fn import_state(
781+
&self,
782+
_at: Block::Hash,
783+
_storage: sp_runtime::Storage,
784+
_state_version: sp_runtime::StateVersion,
785+
) -> sp_blockchain::Result<Block::Hash> {
786+
unimplemented!("Not needed for in-mem backend")
787+
}
777788
}
778789

779790
impl<Block: BlockT> backend::LocalBackend<Block> for Backend<Block> {}

substrate/client/db/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ sp-runtime.workspace = true
4444
sp-runtime.default-features = true
4545
sp-state-machine.workspace = true
4646
sp-state-machine.default-features = true
47+
sp-storage = { workspace = true, default-features = true }
4748
sp-trie.workspace = true
4849
sp-trie.default-features = true
4950

substrate/client/db/src/lib.rs

Lines changed: 133 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ pub mod offchain;
3333
pub mod bench;
3434

3535
mod children;
36+
mod state_importer;
3637
mod parity_db;
3738
mod pinned_blocks_cache;
3839
mod record_stats_state;
@@ -55,6 +56,7 @@ use crate::{
5556
pinned_blocks_cache::PinnedBlocksCache,
5657
record_stats_state::RecordStatsState,
5758
stats::StateUsageStats,
59+
state_importer::StateImporter,
5860
utils::{meta_keys, read_db, read_meta, DatabaseType, Meta},
5961
};
6062
use codec::{Decode, Encode};
@@ -90,7 +92,7 @@ use sp_state_machine::{
9092
OffchainChangesCollection, StateMachineStats, StorageCollection, StorageIterator, StorageKey,
9193
StorageValue, UsageInfo as StateUsageInfo,
9294
};
93-
use sp_trie::{cache::SharedTrieCache, prefixed_key, MemoryDB, MerkleValue, PrefixedMemoryDB};
95+
use sp_trie::{cache::SharedTrieCache, prefixed_key, MemoryDB, MerkleValue, PrefixedMemoryDB, TrieError};
9496

9597
// Re-export the Database trait so that one can pass an implementation of it.
9698
pub use sc_state_db::PruningMode;
@@ -113,6 +115,9 @@ const DB_HASH_LEN: usize = 32;
113115
/// Hash type that this backend uses for the database.
114116
pub type DbHash = sp_core::H256;
115117

118+
type LayoutV0<Block> = sp_trie::LayoutV0<HashingFor<Block>>;
119+
type LayoutV1<Block> = sp_trie::LayoutV1<HashingFor<Block>>;
120+
116121
/// An extrinsic entry in the database.
117122
#[derive(Debug, Encode, Decode)]
118123
enum DbExtrinsic<B: BlockT> {
@@ -995,6 +1000,10 @@ impl<Block: BlockT> sc_client_api::backend::BlockImportOperation<Block>
9951000
self.index_ops = index_ops;
9961001
Ok(())
9971002
}
1003+
1004+
fn set_commit_state(&mut self, commit: bool) {
1005+
self.commit_state = commit;
1006+
}
9981007
}
9991008

10001009
struct StorageDb<Block: BlockT> {
@@ -2455,6 +2464,129 @@ impl<Block: BlockT> sc_client_api::backend::Backend<Block> for Backend<Block> {
24552464
}
24562465
}
24572466

2467+
fn import_state(
2468+
&self,
2469+
at: Block::Hash,
2470+
storage: sp_runtime::Storage,
2471+
state_version: sp_runtime::StateVersion,
2472+
) -> sp_blockchain::Result<Block::Hash> {
2473+
let root = self.blockchain.header_metadata(at).map(|header| header.state_root)?;
2474+
2475+
let storage_db: Arc<dyn sp_state_machine::Storage<HashingFor<Block>>> =
2476+
self.storage.clone();
2477+
let mut state_importer = StateImporter::new(&storage_db, self.storage.db.clone());
2478+
2479+
let trie_err =
2480+
|err: Box<TrieError<LayoutV0<Block>>>| sp_blockchain::Error::Application(err);
2481+
2482+
let child_deltas = storage.children_default.values().map(|child_content| {
2483+
(
2484+
&child_content.child_info,
2485+
child_content.data.iter().map(|(k, v)| (&k[..], Some(&v[..]))),
2486+
)
2487+
});
2488+
2489+
let mut child_roots = Vec::new();
2490+
2491+
// child first
2492+
for (child_info, child_delta) in child_deltas {
2493+
let default_root = match child_info.child_type() {
2494+
sp_storage::ChildType::ParentKeyId =>
2495+
sp_trie::empty_child_trie_root::<LayoutV1<Block>>(),
2496+
};
2497+
2498+
let new_child_root = match state_version {
2499+
StateVersion::V0 => {
2500+
let child_root = match crate::state_importer::read_child_root::<
2501+
_,
2502+
_,
2503+
LayoutV0<Block>,
2504+
>(&state_importer, &root, &child_info)
2505+
{
2506+
Ok(Some(hash)) => hash,
2507+
Ok(None) => default_root,
2508+
Err(e) => {
2509+
warn!(target: "trie", "Failed to read child storage root: {}", e);
2510+
default_root
2511+
},
2512+
};
2513+
2514+
sp_trie::child_delta_trie_root::<LayoutV0<Block>, _, _, _, _, _, _>(
2515+
child_info.keyspace(),
2516+
&mut state_importer,
2517+
child_root,
2518+
child_delta,
2519+
None,
2520+
None,
2521+
)
2522+
.map_err(trie_err)?
2523+
},
2524+
StateVersion::V1 => {
2525+
let child_root = match crate::state_importer::read_child_root::<
2526+
_,
2527+
_,
2528+
LayoutV1<Block>,
2529+
>(&state_importer, &root, &child_info)
2530+
{
2531+
Ok(Some(hash)) => hash,
2532+
Ok(None) => default_root,
2533+
Err(e) => {
2534+
warn!(target: "trie", "Failed to read child storage root: {}", e);
2535+
default_root
2536+
},
2537+
};
2538+
2539+
sp_trie::child_delta_trie_root::<LayoutV1<Block>, _, _, _, _, _, _>(
2540+
child_info.keyspace(),
2541+
&mut state_importer,
2542+
child_root,
2543+
child_delta,
2544+
None,
2545+
None,
2546+
)
2547+
.map_err(trie_err)?
2548+
},
2549+
};
2550+
2551+
let is_default = new_child_root == default_root;
2552+
2553+
let prefixed_storage_key = child_info.prefixed_storage_key().into_inner();
2554+
2555+
if is_default {
2556+
child_roots.push((prefixed_storage_key, None));
2557+
} else {
2558+
child_roots.push((prefixed_storage_key, Some(new_child_root.encode())));
2559+
}
2560+
}
2561+
2562+
let delta = storage
2563+
.top
2564+
.into_iter()
2565+
.map(|(k, v)| (k, Some(v)))
2566+
.chain(child_roots.into_iter());
2567+
2568+
let state_root = match state_version {
2569+
StateVersion::V0 => sp_trie::delta_trie_root::<LayoutV0<Block>, _, _, _, _, _>(
2570+
&mut state_importer,
2571+
root,
2572+
delta,
2573+
None,
2574+
None,
2575+
)
2576+
.map_err(trie_err)?,
2577+
StateVersion::V1 => sp_trie::delta_trie_root::<LayoutV1<Block>, _, _, _, _, _>(
2578+
&mut state_importer,
2579+
root,
2580+
delta,
2581+
None,
2582+
None,
2583+
)
2584+
.map_err(trie_err)?,
2585+
};
2586+
2587+
Ok(state_root)
2588+
}
2589+
24582590
fn have_state_at(&self, hash: Block::Hash, number: NumberFor<Block>) -> bool {
24592591
if self.is_archive {
24602592
match self.blockchain.header_metadata(hash) {
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
use crate::{columns, DbHash};
2+
use hash_db::{AsHashDB, HashDB, HashDBRef, Hasher, Prefix};
3+
use sp_database::{Change, Database, Transaction};
4+
use sp_state_machine::TrieBackendStorage;
5+
use sp_storage::ChildInfo;
6+
use sp_trie::{DBValue, TrieError, TrieHash, TrieLayout};
7+
use std::{marker::PhantomData, sync::Arc};
8+
9+
/// [`StateImporter`] is responsible for importing the state changes
10+
/// directly into the database, bypassing the in-memory intermediate storage
11+
/// (`PrefixedMemoryDB`).
12+
///
13+
/// This approach avoids potential OOM issues that can arise when dealing with
14+
/// large state imports, especially when importing the state downloaded from
15+
/// fast sync or warp sync.
16+
pub(crate) struct StateImporter<'a, S: 'a + TrieBackendStorage<H>, H: 'a + Hasher> {
17+
/// Old state storage backend.
18+
storage: &'a S,
19+
/// Handle to the trie database where changes will be committed.
20+
trie_database: Arc<dyn Database<DbHash>>,
21+
/// Default child storage root.
22+
default_child_root: H::Out,
23+
_phantom: PhantomData<H>,
24+
}
25+
26+
impl<'a, S: TrieBackendStorage<H>, H: Hasher> StateImporter<'a, S, H> {
27+
pub fn new(storage: &'a S, trie_database: Arc<dyn Database<DbHash>>) -> Self {
28+
let default_child_root = sp_trie::empty_child_trie_root::<sp_trie::LayoutV1<H>>();
29+
Self { storage, trie_database, default_child_root, _phantom: Default::default() }
30+
}
31+
}
32+
33+
pub(crate) fn read_child_root<'a, S, H, L>(
34+
state_importer: &StateImporter<'a, S, H>,
35+
root: &TrieHash<L>,
36+
child_info: &ChildInfo,
37+
) -> Result<Option<H::Out>, Box<TrieError<L>>>
38+
where
39+
S: 'a + TrieBackendStorage<H>,
40+
H: Hasher,
41+
L: TrieLayout,
42+
StateImporter<'a, S, H>: HashDBRef<<L as TrieLayout>::Hash, Vec<u8>>,
43+
{
44+
let key = child_info.prefixed_storage_key();
45+
Ok(sp_trie::read_trie_value::<L, _>(state_importer, root, key.as_slice(), None, None)?.map(
46+
|r| {
47+
let mut hash = H::Out::default();
48+
49+
// root is fetched from DB, not writable by runtime, so it's always valid.
50+
hash.as_mut().copy_from_slice(&r[..]);
51+
52+
hash
53+
},
54+
))
55+
}
56+
57+
impl<'a, S: 'a + TrieBackendStorage<H>, H: Hasher> hash_db::HashDB<H, DBValue>
58+
for StateImporter<'a, S, H>
59+
{
60+
fn get(&self, key: &H::Out, prefix: Prefix) -> Option<DBValue> {
61+
// TODO: we'll run into IncompleteDatabase error without this special handling.
62+
// Double check and provide an explanation.
63+
if *key == self.default_child_root {
64+
return Some([0u8].to_vec());
65+
}
66+
67+
let db_key = sp_trie::prefixed_key::<H>(key, prefix);
68+
69+
let res = self.trie_database.get(columns::STATE, &db_key).or_else(|| {
70+
self.storage.get(key, prefix).unwrap_or_else(|e| {
71+
log::warn!(target: "trie", "Failed to read from DB: {}", e);
72+
None
73+
})
74+
});
75+
76+
// TODO: we'll run into IncompleteDatabase error without this special handling.
77+
// Double check and provide an explanation.
78+
if prefix == sp_trie::EMPTY_PREFIX && res.is_none() {
79+
Some([0u8].to_vec())
80+
} else {
81+
res
82+
}
83+
}
84+
85+
fn contains(&self, key: &H::Out, prefix: Prefix) -> bool {
86+
HashDB::get(self, key, prefix).is_some()
87+
}
88+
89+
fn insert(&mut self, prefix: Prefix, value: &[u8]) -> H::Out {
90+
let key = H::hash(value);
91+
self.emplace(key, prefix, value.to_vec());
92+
key
93+
}
94+
95+
fn emplace(&mut self, key: H::Out, prefix: Prefix, value: DBValue) {
96+
let key = sp_trie::prefixed_key::<H>(&key, prefix);
97+
let tx = Transaction(vec![Change::Set(columns::STATE, key, value)]);
98+
// TODO: better error handling?
99+
self.trie_database
100+
.commit(tx)
101+
.unwrap_or_else(|err| panic!("Failed to put value into the state database: {err:?}"))
102+
}
103+
104+
fn remove(&mut self, key: &H::Out, prefix: Prefix) {
105+
let key = sp_trie::prefixed_key::<H>(&key, prefix);
106+
let tx = Transaction(vec![Change::Remove(columns::STATE, key)]);
107+
// TODO: better error handling?
108+
self.trie_database
109+
.commit(tx)
110+
.unwrap_or_else(|err| panic!("Failed to remove value in the state database: {err:?}"))
111+
}
112+
}
113+
114+
impl<'a, S: 'a + TrieBackendStorage<H>, H: Hasher> HashDBRef<H, DBValue>
115+
for StateImporter<'a, S, H>
116+
{
117+
fn get(&self, key: &H::Out, prefix: Prefix) -> Option<DBValue> {
118+
HashDB::get(self, key, prefix)
119+
}
120+
121+
fn contains(&self, key: &H::Out, prefix: Prefix) -> bool {
122+
HashDB::contains(self, key, prefix)
123+
}
124+
}
125+
126+
impl<'a, S: 'a + TrieBackendStorage<H>, H: 'a + Hasher> AsHashDB<H, DBValue>
127+
for StateImporter<'a, S, H>
128+
{
129+
fn as_hash_db<'b>(&'b self) -> &'b (dyn HashDB<H, DBValue> + 'b) {
130+
self
131+
}
132+
fn as_hash_db_mut<'b>(&'b mut self) -> &'b mut (dyn HashDB<H, DBValue> + 'b) {
133+
self
134+
}
135+
}

0 commit comments

Comments
 (0)