Use trie-cache for validate_block (#2813)

* Simple cache

* Fix node insertion

* Switch to hashbrown hashmap

* Remove unused phantomdata

* Return error when fetch_node fails

* Remove cargo patches

* Move trie cache to extra module

* Add ReadOnceBackend

* Add readonlybackend

* Improve naming and get_or_insert

* Stylistic improvements

* Improve naming, add docs

* Revert unwanted changes

* Remove unused dependencies

* Improve docs

* Use RefCell

* lockfile

* Remove ReadOnceBackend

* Apply suggestions from code review

Co-authored-by: Bastian Köcher <git@kchr.de>

* Code review

* Do not use value cache when calculating storage roots

* Apply suggestions from code review

Co-authored-by: Davide Galassi <davxy@datawok.net>

* Remove hash-db dep

* Update pallets/parachain-system/src/validate_block/trie_cache.rs

Co-authored-by: Anton <anton.kalyaev@gmail.com>

---------

Co-authored-by: Bastian Köcher <git@kchr.de>
Co-authored-by: Davide Galassi <davxy@datawok.net>
Co-authored-by: Anton <anton.kalyaev@gmail.com>
This commit is contained in:
Sebastian Kunert
2023-07-19 13:46:58 +02:00
committed by GitHub
parent 18340b304b
commit 5b5942ef60
5 changed files with 124 additions and 4 deletions
+1
View File
@@ -2907,6 +2907,7 @@ dependencies = [
"sp-tracing",
"sp-trie",
"sp-version",
"trie-db",
"xcm",
]
@@ -11,6 +11,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features =
environmental = { version = "1.1.4", default-features = false }
impl-trait-for-tuples = "0.2.1"
log = { version = "0.4.19", default-features = false }
trie-db = { version = "0.27.1", default-features = false }
scale-info = { version = "2.9.0", default-features = false, features = ["derive"] }
# Substrate
@@ -69,6 +70,7 @@ std = [
"sp-std/std",
"sp-trie/std",
"xcm/std",
"trie-db/std",
]
runtime-benchmarks = [
@@ -16,7 +16,7 @@
//! The actual implementation of the validate block functionality.
use super::MemoryOptimizedValidationParams;
use super::{trie_cache, MemoryOptimizedValidationParams};
use cumulus_primitives_core::{
relay_chain::Hash as RHash, ParachainBlockData, PersistedValidationData,
};
@@ -34,7 +34,11 @@ use sp_runtime::traits::{Block as BlockT, Extrinsic, HashFor, Header as HeaderT}
use sp_std::prelude::*;
use sp_trie::MemoryDB;
type TrieBackend<B> = sp_state_machine::TrieBackend<MemoryDB<HashFor<B>>, HashFor<B>>;
type TrieBackend<B> = sp_state_machine::TrieBackend<
MemoryDB<HashFor<B>>,
HashFor<B>,
trie_cache::CacheProvider<HashFor<B>>,
>;
type Ext<'a, B> = sp_state_machine::Ext<'a, HashFor<B>, TrieBackend<B>>;
@@ -114,10 +118,15 @@ where
sp_std::mem::drop(storage_proof);
let cache_provider = trie_cache::CacheProvider::new();
// We use the storage root of the `parent_head` to ensure that it is the correct root.
// This is already being done above while creating the in-memory db, but let's be paranoid!!
let backend =
sp_state_machine::TrieBackendBuilder::new(db, *parent_header.state_root()).build();
let backend = sp_state_machine::TrieBackendBuilder::new_with_cache(
db,
*parent_header.state_root(),
cache_provider,
)
.build();
let _guard = (
// Replace storage calls with our own implementations
@@ -22,6 +22,10 @@ pub mod implementation;
#[cfg(test)]
mod tests;
#[cfg(not(feature = "std"))]
#[doc(hidden)]
mod trie_cache;
#[cfg(not(feature = "std"))]
#[doc(hidden)]
pub use bytes;
@@ -0,0 +1,104 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use sp_state_machine::TrieCacheProvider;
use sp_std::{
boxed::Box,
cell::{RefCell, RefMut},
collections::btree_map::{BTreeMap, Entry},
};
use sp_trie::NodeCodec;
use trie_db::{node::NodeOwned, Hasher};
/// Special purpose trie cache implementation that is able to cache an unlimited number
/// of values. To be used in `validate_block` to serve values and nodes that
/// have already been loaded and decoded from the storage proof.
pub(crate) struct TrieCache<'a, H: Hasher> {
node_cache: RefMut<'a, BTreeMap<H::Out, NodeOwned<H::Out>>>,
value_cache: Option<RefMut<'a, BTreeMap<Box<[u8]>, trie_db::CachedValue<H::Out>>>>,
}
impl<'a, H: Hasher> trie_db::TrieCache<NodeCodec<H>> for TrieCache<'a, H> {
fn lookup_value_for_key(&mut self, key: &[u8]) -> Option<&trie_db::CachedValue<H::Out>> {
self.value_cache.as_ref().and_then(|cache| cache.get(key))
}
fn cache_value_for_key(&mut self, key: &[u8], value: trie_db::CachedValue<H::Out>) {
self.value_cache.as_mut().and_then(|cache| cache.insert(key.into(), value));
}
fn get_or_insert_node(
&mut self,
hash: <NodeCodec<H> as trie_db::NodeCodec>::HashOut,
fetch_node: &mut dyn FnMut() -> trie_db::Result<
NodeOwned<H::Out>,
H::Out,
<NodeCodec<H> as trie_db::NodeCodec>::Error,
>,
) -> trie_db::Result<&NodeOwned<H::Out>, H::Out, <NodeCodec<H> as trie_db::NodeCodec>::Error> {
match self.node_cache.entry(hash) {
Entry::Occupied(entry) => Ok(entry.into_mut()),
Entry::Vacant(entry) => Ok(entry.insert(fetch_node()?)),
}
}
fn get_node(
&mut self,
hash: &H::Out,
) -> Option<&NodeOwned<<NodeCodec<H> as trie_db::NodeCodec>::HashOut>> {
self.node_cache.get(hash)
}
}
/// Provider of [`TrieCache`] instances.
pub(crate) struct CacheProvider<H: Hasher> {
node_cache: RefCell<BTreeMap<H::Out, NodeOwned<H::Out>>>,
value_cache: RefCell<BTreeMap<Box<[u8]>, trie_db::CachedValue<H::Out>>>,
}
impl<H: Hasher> CacheProvider<H> {
/// Constructs a new instance of [`CacheProvider`] with an uninitialized state
/// and empty node and value caches.
pub fn new() -> Self {
CacheProvider { node_cache: Default::default(), value_cache: Default::default() }
}
}
impl<H: Hasher> TrieCacheProvider<H> for CacheProvider<H> {
type Cache<'a> = TrieCache<'a, H> where H: 'a;
fn as_trie_db_cache(&self, _storage_root: <H as Hasher>::Out) -> Self::Cache<'_> {
TrieCache {
value_cache: Some(self.value_cache.borrow_mut()),
node_cache: self.node_cache.borrow_mut(),
}
}
fn as_trie_db_mut_cache(&self) -> Self::Cache<'_> {
// This method is called when we calculate the storage root.
// Since we are using a simplified cache architecture,
// we do not have separate key spaces for different storage roots.
// The value cache is therefore disabled here.
TrieCache { value_cache: None, node_cache: self.node_cache.borrow_mut() }
}
fn merge<'a>(&'a self, _other: Self::Cache<'a>, _new_root: <H as Hasher>::Out) {}
}
// This is safe here since we are single-threaded in WASM
unsafe impl<H: Hasher> Send for CacheProvider<H> {}
unsafe impl<H: Hasher> Sync for CacheProvider<H> {}