Fast sync child trie support. (#9239)

* state machine proofs.

* initial implementation

* Remove todo.

* Extend test and fix import.

* fix no proof, with proof ko.

* fix start at logic.

* Restore response size.

* Rework comments.

* Add explicit ref

* Use compact proof.

* ref change

* elaborato on empty change set condition.

* KeyValueState renaming.

* Do not add two time child trie with same root to sync reply.

* rust format

* Fix merge.

* fix warnings and fmt

* fmt

* update protocol id to V2
This commit is contained in:
cheme
2021-11-07 14:13:02 +01:00
committed by GitHub
parent 7827dbb73c
commit ca5b07243f
13 changed files with 855 additions and 140 deletions
+169 -47
View File
@@ -64,7 +64,10 @@ use sp_consensus::{BlockOrigin, BlockStatus, Error as ConsensusError};
use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedSender};
use sp_core::{
convert_hash,
storage::{well_known_keys, ChildInfo, PrefixedStorageKey, StorageData, StorageKey},
storage::{
well_known_keys, ChildInfo, ChildType, PrefixedStorageKey, StorageChild, StorageData,
StorageKey,
},
ChangesTrieConfiguration, NativeOrEncoded,
};
#[cfg(feature = "test-helpers")]
@@ -78,11 +81,12 @@ use sp_runtime::{
BuildStorage, Justification, Justifications,
};
use sp_state_machine::{
key_changes, key_changes_proof, prove_child_read, prove_range_read_with_size, prove_read,
read_range_proof_check, Backend as StateBackend, ChangesTrieAnchorBlockId,
ChangesTrieConfigurationRange, ChangesTrieRootsStorage, ChangesTrieStorage, DBValue,
key_changes, key_changes_proof, prove_child_read, prove_range_read_with_child_with_size,
prove_read, read_range_proof_check_with_child_on_proving_backend, Backend as StateBackend,
ChangesTrieAnchorBlockId, ChangesTrieConfigurationRange, ChangesTrieRootsStorage,
ChangesTrieStorage, DBValue, KeyValueStates, KeyValueStorageLevel, MAX_NESTED_TRIE_DEPTH,
};
use sp_trie::StorageProof;
use sp_trie::{CompactProof, StorageProof};
use std::{
collections::{BTreeMap, HashMap, HashSet},
marker::PhantomData,
@@ -824,10 +828,37 @@ where
Some((main_sc, child_sc))
},
sc_consensus::StorageChanges::Import(changes) => {
let storage = sp_storage::Storage {
top: changes.state.into_iter().collect(),
children_default: Default::default(),
};
let mut storage = sp_storage::Storage::default();
for state in changes.state.0.into_iter() {
if state.parent_storage_keys.len() == 0 && state.state_root.len() == 0 {
for (key, value) in state.key_values.into_iter() {
storage.top.insert(key, value);
}
} else {
for parent_storage in state.parent_storage_keys {
let storage_key = PrefixedStorageKey::new_ref(&parent_storage);
let storage_key =
match ChildType::from_prefixed_key(&storage_key) {
Some((ChildType::ParentKeyId, storage_key)) =>
storage_key,
None =>
return Err(Error::Backend(
"Invalid child storage key.".to_string(),
)),
};
let entry = storage
.children_default
.entry(storage_key.to_vec())
.or_insert_with(|| StorageChild {
data: Default::default(),
child_info: ChildInfo::new_default(storage_key),
});
for (key, value) in state.key_values.iter() {
entry.data.insert(key.clone(), value.clone());
}
}
}
}
let state_root = operation.op.reset_storage(storage)?;
if state_root != *import_headers.post().state_root() {
@@ -1347,62 +1378,153 @@ where
fn read_proof_collection(
&self,
id: &BlockId<Block>,
start_key: &[u8],
start_key: &[Vec<u8>],
size_limit: usize,
) -> sp_blockchain::Result<(StorageProof, u32)> {
) -> sp_blockchain::Result<(CompactProof, u32)> {
let state = self.state_at(id)?;
Ok(prove_range_read_with_size::<_, HashFor<Block>>(
state,
None,
None,
size_limit,
Some(start_key),
)?)
let root = state.storage_root(std::iter::empty()).0;
let (proof, count) = prove_range_read_with_child_with_size::<_, HashFor<Block>>(
state, size_limit, start_key,
)?;
let proof = sp_trie::encode_compact::<sp_trie::Layout<HashFor<Block>>>(proof, root)
.map_err(|e| sp_blockchain::Error::from_state(Box::new(e)))?;
Ok((proof, count))
}
fn storage_collection(
&self,
id: &BlockId<Block>,
start_key: &[u8],
start_key: &[Vec<u8>],
size_limit: usize,
) -> sp_blockchain::Result<Vec<(Vec<u8>, Vec<u8>)>> {
) -> sp_blockchain::Result<Vec<(KeyValueStorageLevel, bool)>> {
if start_key.len() > MAX_NESTED_TRIE_DEPTH {
return Err(Error::Backend("Invalid start key.".to_string()))
}
let state = self.state_at(id)?;
let mut current_key = start_key.to_vec();
let mut total_size = 0;
let mut entries = Vec::new();
while let Some(next_key) = state
.next_storage_key(&current_key)
.map_err(|e| sp_blockchain::Error::from_state(Box::new(e)))?
{
let value = state
.storage(next_key.as_ref())
let child_info = |storage_key: &Vec<u8>| -> sp_blockchain::Result<ChildInfo> {
let storage_key = PrefixedStorageKey::new_ref(&storage_key);
match ChildType::from_prefixed_key(&storage_key) {
Some((ChildType::ParentKeyId, storage_key)) =>
Ok(ChildInfo::new_default(storage_key)),
None => Err(Error::Backend("Invalid child storage key.".to_string())),
}
};
let mut current_child = if start_key.len() == 2 {
let start_key = start_key.get(0).expect("checked len");
if let Some(child_root) = state
.storage(&start_key)
.map_err(|e| sp_blockchain::Error::from_state(Box::new(e)))?
.unwrap_or_default();
let size = value.len() + next_key.len();
if total_size + size > size_limit && !entries.is_empty() {
{
Some((child_info(start_key)?, child_root))
} else {
return Err(Error::Backend("Invalid root start key.".to_string()))
}
} else {
None
};
let mut current_key = start_key.last().map(Clone::clone).unwrap_or(Vec::new());
let mut total_size = 0;
let mut result = vec![(
KeyValueStorageLevel {
state_root: Vec::new(),
key_values: Vec::new(),
parent_storage_keys: Vec::new(),
},
false,
)];
let mut child_roots = HashSet::new();
loop {
let mut entries = Vec::new();
let mut complete = true;
let mut switch_child_key = None;
while let Some(next_key) = if let Some(child) = current_child.as_ref() {
state
.next_child_storage_key(&child.0, &current_key)
.map_err(|e| sp_blockchain::Error::from_state(Box::new(e)))?
} else {
state
.next_storage_key(&current_key)
.map_err(|e| sp_blockchain::Error::from_state(Box::new(e)))?
} {
let value = if let Some(child) = current_child.as_ref() {
state
.child_storage(&child.0, next_key.as_ref())
.map_err(|e| sp_blockchain::Error::from_state(Box::new(e)))?
.unwrap_or_default()
} else {
state
.storage(next_key.as_ref())
.map_err(|e| sp_blockchain::Error::from_state(Box::new(e)))?
.unwrap_or_default()
};
let size = value.len() + next_key.len();
if total_size + size > size_limit && !entries.is_empty() {
complete = false;
break
}
total_size += size;
if current_child.is_none() &&
sp_core::storage::well_known_keys::is_child_storage_key(next_key.as_slice())
{
if !child_roots.contains(value.as_slice()) {
child_roots.insert(value.clone());
switch_child_key = Some((next_key.clone(), value.clone()));
entries.push((next_key.clone(), value));
break
}
}
entries.push((next_key.clone(), value));
current_key = next_key;
}
if let Some((child, child_root)) = switch_child_key.take() {
result[0].0.key_values.extend(entries.into_iter());
current_child = Some((child_info(&child)?, child_root));
current_key = Vec::new();
} else if let Some((child, child_root)) = current_child.take() {
current_key = child.into_prefixed_storage_key().into_inner();
result.push((
KeyValueStorageLevel {
state_root: child_root,
key_values: entries,
parent_storage_keys: Vec::new(),
},
complete,
));
if !complete {
break
}
} else {
result[0].0.key_values.extend(entries.into_iter());
result[0].1 = complete;
break
}
total_size += size;
entries.push((next_key.clone(), value));
current_key = next_key;
}
Ok(entries)
Ok(result)
}
fn verify_range_proof(
&self,
root: Block::Hash,
proof: StorageProof,
start_key: &[u8],
) -> sp_blockchain::Result<(Vec<(Vec<u8>, Vec<u8>)>, bool)> {
Ok(read_range_proof_check::<HashFor<Block>>(
root,
proof,
None,
None,
None,
Some(start_key),
)?)
proof: CompactProof,
start_key: &[Vec<u8>],
) -> sp_blockchain::Result<(KeyValueStates, usize)> {
let mut db = sp_state_machine::MemoryDB::<HashFor<Block>>::new(&[]);
let _ = sp_trie::decode_compact::<sp_state_machine::Layout<HashFor<Block>>, _, _>(
&mut db,
proof.iter_compact_encoded_nodes(),
Some(&root),
)
.map_err(|e| sp_blockchain::Error::from_state(Box::new(e)))?;
let proving_backend = sp_state_machine::TrieBackend::new(db, root);
let state = read_range_proof_check_with_child_on_proving_backend::<HashFor<Block>>(
&proving_backend,
start_key,
)?;
Ok(state)
}
}