Set StateBackend::Transaction to PrefixedMemoryDB (#14612)

* Yep

* Try to get it working everywhere

* Make `from_raw_storage` start with an empty db

* More fixes!

* Make everything compile

* Fix `child_storage_root`

* Fix after merge

* Cleanups

* Update primitives/state-machine/src/overlayed_changes/mod.rs

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

* Review comments

* Fix issues

* Silence warning

* FMT

* Clippy

---------

Co-authored-by: Davide Galassi <davxy@datawok.net>
This commit is contained in:
Bastian Köcher
2023-08-17 12:49:38 +02:00
committed by GitHub
parent a892fa7f92
commit ecf8035da6
67 changed files with 750 additions and 1150 deletions
@@ -21,7 +21,7 @@ mod changeset;
mod offchain;
use self::changeset::OverlayedChangeSet;
use crate::{backend::Backend, stats::StateMachineStats, DefaultError};
use crate::{backend::Backend, stats::StateMachineStats, BackendTransaction, DefaultError};
use codec::{Decode, Encode};
use hash_db::Hasher;
pub use offchain::OffchainOverlayedChanges;
@@ -34,6 +34,7 @@ use sp_externalities::{Extension, Extensions};
#[cfg(not(feature = "std"))]
use sp_std::collections::btree_map::BTreeMap as Map;
use sp_std::{collections::btree_set::BTreeSet, vec::Vec};
use sp_trie::{empty_child_trie_root, LayoutV1};
#[cfg(feature = "std")]
use std::collections::{hash_map::Entry as MapEntry, HashMap as Map};
#[cfg(feature = "std")]
@@ -88,8 +89,7 @@ impl Extrinsics {
/// The set of changes that are overlaid onto the backend.
///
/// It allows changes to be modified using nestable transactions.
#[derive(Debug, Default, Clone)]
pub struct OverlayedChanges {
pub struct OverlayedChanges<H: Hasher> {
/// Top level storage changes.
top: OverlayedChangeSet,
/// Child storage changes. The map key is the child storage key without the common prefix.
@@ -102,6 +102,52 @@ pub struct OverlayedChanges {
collect_extrinsics: bool,
/// Collect statistic on this execution.
stats: StateMachineStats,
/// Caches the "storage transaction" that is created while calling `storage_root`.
///
/// This transaction can be applied to the backend to persist the state changes.
storage_transaction_cache: Option<StorageTransactionCache<H>>,
}
impl<H: Hasher> Default for OverlayedChanges<H> {
fn default() -> Self {
Self {
top: Default::default(),
children: Default::default(),
offchain: Default::default(),
transaction_index_ops: Default::default(),
collect_extrinsics: Default::default(),
stats: Default::default(),
storage_transaction_cache: None,
}
}
}
impl<H: Hasher> Clone for OverlayedChanges<H> {
fn clone(&self) -> Self {
Self {
top: self.top.clone(),
children: self.children.clone(),
offchain: self.offchain.clone(),
transaction_index_ops: self.transaction_index_ops.clone(),
collect_extrinsics: self.collect_extrinsics,
stats: self.stats.clone(),
storage_transaction_cache: self.storage_transaction_cache.clone(),
}
}
}
impl<H: Hasher> sp_std::fmt::Debug for OverlayedChanges<H> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("OverlayedChanges")
.field("top", &self.top)
.field("children", &self.children)
.field("offchain", &self.offchain)
.field("transaction_index_ops", &self.transaction_index_ops)
.field("collect_extrinsics", &self.collect_extrinsics)
.field("stats", &self.stats)
.field("storage_transaction_cache", &self.storage_transaction_cache)
.finish()
}
}
/// Transaction index operation.
@@ -129,7 +175,7 @@ pub enum IndexOperation {
///
/// This contains all the changes to the storage and transactions to apply theses changes to the
/// backend.
pub struct StorageChanges<Transaction, H: Hasher> {
pub struct StorageChanges<H: Hasher> {
/// All changes to the main storage.
///
/// A value of `None` means that it was deleted.
@@ -142,7 +188,7 @@ pub struct StorageChanges<Transaction, H: Hasher> {
/// [`main_storage_changes`](StorageChanges::main_storage_changes) and from
/// [`child_storage_changes`](StorageChanges::child_storage_changes).
/// [`offchain_storage_changes`](StorageChanges::offchain_storage_changes).
pub transaction: Transaction,
pub transaction: BackendTransaction<H>,
/// The storage root after applying the transaction.
pub transaction_storage_root: H::Out,
/// Changes to the transaction index,
@@ -151,7 +197,7 @@ pub struct StorageChanges<Transaction, H: Hasher> {
}
#[cfg(feature = "std")]
impl<Transaction, H: Hasher> StorageChanges<Transaction, H> {
impl<H: Hasher> StorageChanges<H> {
/// Deconstruct into the inner values
pub fn into_inner(
self,
@@ -159,7 +205,7 @@ impl<Transaction, H: Hasher> StorageChanges<Transaction, H> {
StorageCollection,
ChildStorageCollection,
OffchainChangesCollection,
Transaction,
BackendTransaction<H>,
H::Out,
Vec<IndexOperation>,
) {
@@ -174,30 +220,7 @@ impl<Transaction, H: Hasher> StorageChanges<Transaction, H> {
}
}
/// Storage transactions are calculated as part of the `storage_root`.
/// These transactions can be reused for importing the block into the
/// storage. So, we cache them to not require a recomputation of those transactions.
pub struct StorageTransactionCache<Transaction, H: Hasher> {
/// Contains the changes for the main and the child storages as one transaction.
pub(crate) transaction: Option<Transaction>,
/// The storage root after applying the transaction.
pub(crate) transaction_storage_root: Option<H::Out>,
}
impl<Transaction, H: Hasher> StorageTransactionCache<Transaction, H> {
/// Reset the cached transactions.
pub fn reset(&mut self) {
*self = Self::default();
}
}
impl<Transaction, H: Hasher> Default for StorageTransactionCache<Transaction, H> {
fn default() -> Self {
Self { transaction: None, transaction_storage_root: None }
}
}
impl<Transaction: Default, H: Hasher> Default for StorageChanges<Transaction, H> {
impl<H: Hasher> Default for StorageChanges<H> {
fn default() -> Self {
Self {
main_storage_changes: Default::default(),
@@ -211,7 +234,46 @@ impl<Transaction: Default, H: Hasher> Default for StorageChanges<Transaction, H>
}
}
impl OverlayedChanges {
/// Storage transactions are calculated as part of the `storage_root`.
/// These transactions can be reused for importing the block into the
/// storage. So, we cache them to not require a recomputation of those transactions.
struct StorageTransactionCache<H: Hasher> {
/// Contains the changes for the main and the child storages as one transaction.
transaction: BackendTransaction<H>,
/// The storage root after applying the transaction.
transaction_storage_root: H::Out,
}
impl<H: Hasher> StorageTransactionCache<H> {
fn into_inner(self) -> (BackendTransaction<H>, H::Out) {
(self.transaction, self.transaction_storage_root)
}
}
impl<H: Hasher> Clone for StorageTransactionCache<H> {
fn clone(&self) -> Self {
Self {
transaction: self.transaction.clone(),
transaction_storage_root: self.transaction_storage_root,
}
}
}
impl<H: Hasher> sp_std::fmt::Debug for StorageTransactionCache<H> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let mut debug = f.debug_struct("StorageTransactionCache");
#[cfg(feature = "std")]
debug.field("transaction_storage_root", &self.transaction_storage_root);
#[cfg(not(feature = "std"))]
debug.field("transaction_storage_root", &self.transaction_storage_root.as_ref());
debug.finish()
}
}
impl<H: Hasher> OverlayedChanges<H> {
/// Whether no changes are contained in the top nor in any of the child changes.
pub fn is_empty(&self) -> bool {
self.top.is_empty() && self.children.is_empty()
@@ -234,6 +296,12 @@ impl OverlayedChanges {
})
}
/// Should be called when there are changes that require to reset the
/// `storage_transaction_cache`.
fn mark_dirty(&mut self) {
self.storage_transaction_cache = None;
}
/// Returns mutable reference to current value.
/// If there is no value in the overlay, the given callback is used to initiate the value.
/// Warning this function registers a change, so the mutable reference MUST be modified.
@@ -245,6 +313,8 @@ impl OverlayedChanges {
key: &[u8],
init: impl Fn() -> StorageValue,
) -> &mut StorageValue {
self.mark_dirty();
let value = self.top.modify(key.to_vec(), init, self.extrinsic_index());
// if the value was deleted initialise it back with an empty vec
@@ -266,6 +336,8 @@ impl OverlayedChanges {
///
/// Can be rolled back or committed when called inside a transaction.
pub fn set_storage(&mut self, key: StorageKey, val: Option<StorageValue>) {
self.mark_dirty();
let size_write = val.as_ref().map(|x| x.len() as u64).unwrap_or(0);
self.stats.tally_write_overlay(size_write);
self.top.set(key, val, self.extrinsic_index());
@@ -282,6 +354,8 @@ impl OverlayedChanges {
key: StorageKey,
val: Option<StorageValue>,
) {
self.mark_dirty();
let extrinsic_index = self.extrinsic_index();
let size_write = val.as_ref().map(|x| x.len() as u64).unwrap_or(0);
self.stats.tally_write_overlay(size_write);
@@ -300,6 +374,8 @@ impl OverlayedChanges {
///
/// Can be rolled back or committed when called inside a transaction.
pub(crate) fn clear_child_storage(&mut self, child_info: &ChildInfo) -> u32 {
self.mark_dirty();
let extrinsic_index = self.extrinsic_index();
let storage_key = child_info.storage_key().to_vec();
let top = &self.top;
@@ -316,6 +392,8 @@ impl OverlayedChanges {
///
/// Can be rolled back or committed when called inside a transaction.
pub(crate) fn clear_prefix(&mut self, prefix: &[u8]) -> u32 {
self.mark_dirty();
self.top.clear_where(|key, _| key.starts_with(prefix), self.extrinsic_index())
}
@@ -323,6 +401,8 @@ impl OverlayedChanges {
///
/// Can be rolled back or committed when called inside a transaction
pub(crate) fn clear_child_prefix(&mut self, child_info: &ChildInfo, prefix: &[u8]) -> u32 {
self.mark_dirty();
let extrinsic_index = self.extrinsic_index();
let storage_key = child_info.storage_key().to_vec();
let top = &self.top;
@@ -364,6 +444,8 @@ impl OverlayedChanges {
/// Any changes made during that transaction are discarded. Returns an error if
/// there is no open transaction that can be rolled back.
pub fn rollback_transaction(&mut self) -> Result<(), NoOpenTransaction> {
self.mark_dirty();
self.top.rollback_transaction()?;
retain_map(&mut self.children, |_, (changeset, _)| {
changeset
@@ -432,32 +514,6 @@ impl OverlayedChanges {
Ok(())
}
/// Consume all changes (top + children) and return them.
///
/// After calling this function no more changes are contained in this changeset.
///
/// Panics:
/// Panics if `transaction_depth() > 0`
fn drain_committed(
&mut self,
) -> (
impl Iterator<Item = (StorageKey, Option<StorageValue>)>,
impl Iterator<
Item = (
StorageKey,
(impl Iterator<Item = (StorageKey, Option<StorageValue>)>, ChildInfo),
),
>,
) {
use sp_std::mem::take;
(
take(&mut self.top).drain_commited(),
take(&mut self.children)
.into_iter()
.map(|(key, (val, info))| (key, (val.drain_commited(), info))),
)
}
/// Consume all changes (top + children) and return them.
///
/// After calling this function no more changes are contained in this changeset.
@@ -495,42 +551,33 @@ impl OverlayedChanges {
&self.transaction_index_ops
}
/// Convert this instance with all changes into a [`StorageChanges`] instance.
#[cfg(feature = "std")]
pub fn into_storage_changes<B: Backend<H>, H: Hasher>(
mut self,
backend: &B,
mut cache: StorageTransactionCache<B::Transaction, H>,
state_version: StateVersion,
) -> Result<StorageChanges<B::Transaction, H>, DefaultError>
where
H::Out: Ord + Encode + 'static,
{
self.drain_storage_changes(backend, &mut cache, state_version)
}
/// Drain all changes into a [`StorageChanges`] instance. Leave empty overlay in place.
pub fn drain_storage_changes<B: Backend<H>, H: Hasher>(
pub fn drain_storage_changes<B: Backend<H>>(
&mut self,
backend: &B,
cache: &mut StorageTransactionCache<B::Transaction, H>,
state_version: StateVersion,
) -> Result<StorageChanges<B::Transaction, H>, DefaultError>
) -> Result<StorageChanges<H>, DefaultError>
where
H::Out: Ord + Encode + 'static,
{
// If the transaction does not exist, we generate it.
if cache.transaction.is_none() {
self.storage_root(backend, cache, state_version);
}
let (transaction, transaction_storage_root) = match self.storage_transaction_cache.take() {
Some(cache) => cache.into_inner(),
// If the transaction does not exist, we generate it.
None => {
self.storage_root(backend, state_version);
self.storage_transaction_cache
.take()
.expect("`storage_transaction_cache` was just initialized; qed")
.into_inner()
},
};
let (transaction, transaction_storage_root) = cache
.transaction
.take()
.and_then(|t| cache.transaction_storage_root.take().map(|tr| (t, tr)))
.expect("Transaction was be generated as part of `storage_root`; qed");
use sp_std::mem::take;
let main_storage_changes = take(&mut self.top).drain_commited();
let child_storage_changes = take(&mut self.children)
.into_iter()
.map(|(key, (val, info))| (key, (val.drain_commited(), info)));
let (main_storage_changes, child_storage_changes) = self.drain_committed();
let offchain_storage_changes = self.offchain_drain_committed().collect();
#[cfg(feature = "std")]
@@ -562,29 +609,29 @@ impl OverlayedChanges {
/// Changes that are made outside of extrinsics, are marked with
/// `NO_EXTRINSIC_INDEX` index.
fn extrinsic_index(&self) -> Option<u32> {
match self.collect_extrinsics {
true => Some(
self.storage(EXTRINSIC_INDEX)
.and_then(|idx| idx.and_then(|idx| Decode::decode(&mut &*idx).ok()))
.unwrap_or(NO_EXTRINSIC_INDEX),
),
false => None,
}
self.collect_extrinsics.then(|| {
self.storage(EXTRINSIC_INDEX)
.and_then(|idx| idx.and_then(|idx| Decode::decode(&mut &*idx).ok()))
.unwrap_or(NO_EXTRINSIC_INDEX)
})
}
/// Generate the storage root using `backend` and all changes
/// as seen by the current transaction.
///
/// Returns the storage root and caches storage transaction in the given `cache`.
pub fn storage_root<H: Hasher, B: Backend<H>>(
&self,
/// Returns the storage root and whether it was already cached.
pub fn storage_root<B: Backend<H>>(
&mut self,
backend: &B,
cache: &mut StorageTransactionCache<B::Transaction, H>,
state_version: StateVersion,
) -> H::Out
) -> (H::Out, bool)
where
H::Out: Ord + Encode,
{
if let Some(cache) = &self.storage_transaction_cache {
return (cache.transaction_storage_root, true)
}
let delta = self.changes().map(|(k, v)| (&k[..], v.value().map(|v| &v[..])));
let child_delta = self.children().map(|(changes, info)| {
(info, changes.map(|(k, v)| (&k[..], v.value().map(|v| &v[..]))))
@@ -592,10 +639,72 @@ impl OverlayedChanges {
let (root, transaction) = backend.full_storage_root(delta, child_delta, state_version);
cache.transaction = Some(transaction);
cache.transaction_storage_root = Some(root);
self.storage_transaction_cache =
Some(StorageTransactionCache { transaction, transaction_storage_root: root });
root
(root, false)
}
/// Generate the child storage root using `backend` and all child changes
/// as seen by the current transaction.
///
/// Returns the child storage root and whether it was already cached.
pub fn child_storage_root<B: Backend<H>>(
&mut self,
child_info: &ChildInfo,
backend: &B,
state_version: StateVersion,
) -> Result<(H::Out, bool), B::Error>
where
H::Out: Ord + Encode + Decode,
{
let storage_key = child_info.storage_key();
let prefixed_storage_key = child_info.prefixed_storage_key();
if self.storage_transaction_cache.is_some() {
let root = self
.storage(prefixed_storage_key.as_slice())
.map(|v| Ok(v.map(|v| v.to_vec())))
.or_else(|| backend.storage(prefixed_storage_key.as_slice()).map(Some).transpose())
.transpose()?
.flatten()
.and_then(|k| Decode::decode(&mut &k[..]).ok())
// V1 is equivalent to V0 on empty root.
.unwrap_or_else(empty_child_trie_root::<LayoutV1<H>>);
return Ok((root, true))
}
let root = if let Some((changes, info)) = self.child_changes(storage_key) {
let delta = changes.map(|(k, v)| (k.as_ref(), v.value().map(AsRef::as_ref)));
Some(backend.child_storage_root(info, delta, state_version))
} else {
None
};
let root = if let Some((root, is_empty, _)) = root {
// We store update in the overlay in order to be able to use
// 'self.storage_transaction' cache. This is brittle as it rely on Ext only querying
// the trie backend for storage root.
// A better design would be to manage 'child_storage_transaction' in a
// similar way as 'storage_transaction' but for each child trie.
self.set_storage(prefixed_storage_key.into_inner(), (!is_empty).then(|| root.encode()));
self.mark_dirty();
root
} else {
// empty overlay
let root = backend
.storage(prefixed_storage_key.as_slice())?
.and_then(|k| Decode::decode(&mut &k[..]).ok())
// V1 is equivalent to V0 on empty root.
.unwrap_or_else(empty_child_trie_root::<LayoutV1<H>>);
root
};
Ok((root, false))
}
/// Returns an iterator over the keys (in lexicographic order) following `key` (excluding `key`)
@@ -639,7 +748,7 @@ impl OverlayedChanges {
}
#[cfg(feature = "std")]
impl From<sp_core::storage::Storage> for OverlayedChanges {
impl<H: Hasher> From<sp_core::storage::Storage> for OverlayedChanges<H> {
fn from(storage: sp_core::storage::Storage) -> Self {
Self {
top: storage.top.into(),
@@ -742,7 +851,8 @@ impl<'a> OverlayedExtensions<'a> {
#[cfg(test)]
mod tests {
use super::*;
use crate::{ext::Ext, InMemoryBackend};
use crate::{ext::Ext, new_in_mem, InMemoryBackend};
use array_bytes::bytes2hex;
use sp_core::{traits::Externalities, Blake2Hasher};
use std::collections::BTreeMap;
@@ -755,7 +865,7 @@ mod tests {
#[test]
fn overlayed_storage_works() {
let mut overlayed = OverlayedChanges::default();
let mut overlayed = OverlayedChanges::<Blake2Hasher>::default();
let key = vec![42, 69, 169, 142];
@@ -790,7 +900,7 @@ mod tests {
fn offchain_overlayed_storage_transactions_works() {
use sp_core::offchain::STORAGE_PREFIX;
fn check_offchain_content(
state: &OverlayedChanges,
state: &OverlayedChanges<Blake2Hasher>,
nb_commit: usize,
expected: Vec<(Vec<u8>, Option<Vec<u8>>)>,
) {
@@ -867,18 +977,61 @@ mod tests {
overlay.set_storage(b"dogglesworth".to_vec(), Some(b"cat".to_vec()));
overlay.set_storage(b"doug".to_vec(), None);
let mut cache = StorageTransactionCache::default();
let mut ext = Ext::new(&mut overlay, &mut cache, &backend, None);
let root = array_bytes::hex2bytes_unchecked(
"39245109cef3758c2eed2ccba8d9b370a917850af3824bc8348d505df2c298fa",
);
{
let mut ext = Ext::new(&mut overlay, &backend, None);
let root = "39245109cef3758c2eed2ccba8d9b370a917850af3824bc8348d505df2c298fa";
assert_eq!(&ext.storage_root(state_version)[..], &root);
assert_eq!(bytes2hex("", &ext.storage_root(state_version)), root);
// Calling a second time should use it from the cache
assert_eq!(bytes2hex("", &ext.storage_root(state_version)), root);
}
// Check that the storage root is recalculated
overlay.set_storage(b"doug2".to_vec(), Some(b"yes".to_vec()));
let mut ext = Ext::new(&mut overlay, &backend, None);
let root = "5c0a4e35cb967de785e1cb8743e6f24b6ff6d45155317f2078f6eb3fc4ff3e3d";
assert_eq!(bytes2hex("", &ext.storage_root(state_version)), root);
}
#[test]
fn overlayed_child_storage_root_works() {
let state_version = StateVersion::default();
let child_info = ChildInfo::new_default(b"Child1");
let child_info = &child_info;
let backend = new_in_mem::<Blake2Hasher>();
let mut overlay = OverlayedChanges::<Blake2Hasher>::default();
overlay.start_transaction();
overlay.set_child_storage(child_info, vec![20], Some(vec![20]));
overlay.set_child_storage(child_info, vec![30], Some(vec![30]));
overlay.set_child_storage(child_info, vec![40], Some(vec![40]));
overlay.commit_transaction().unwrap();
overlay.set_child_storage(child_info, vec![10], Some(vec![10]));
overlay.set_child_storage(child_info, vec![30], None);
{
let mut ext = Ext::new(&mut overlay, &backend, None);
let child_root = "c02965e1df4dc5baf6977390ce67dab1d7a9b27a87c1afe27b50d29cc990e0f5";
let root = "eafb765909c3ed5afd92a0c564acf4620d0234b31702e8e8e9b48da72a748838";
assert_eq!(
bytes2hex("", &ext.child_storage_root(child_info, state_version)),
child_root,
);
assert_eq!(bytes2hex("", &ext.storage_root(state_version)), root);
// Calling a second time should use it from the cache
assert_eq!(
bytes2hex("", &ext.child_storage_root(child_info, state_version)),
child_root,
);
}
}
#[test]
fn extrinsic_changes_are_collected() {
let mut overlay = OverlayedChanges::default();
let mut overlay = OverlayedChanges::<Blake2Hasher>::default();
overlay.set_collect_extrinsics(true);
overlay.start_transaction();
@@ -919,7 +1072,7 @@ mod tests {
#[test]
fn next_storage_key_change_works() {
let mut overlay = OverlayedChanges::default();
let mut overlay = OverlayedChanges::<Blake2Hasher>::default();
overlay.start_transaction();
overlay.set_storage(vec![20], Some(vec![20]));
overlay.set_storage(vec![30], Some(vec![30]));
@@ -960,7 +1113,7 @@ mod tests {
let child_info = ChildInfo::new_default(b"Child1");
let child_info = &child_info;
let child = child_info.storage_key();
let mut overlay = OverlayedChanges::default();
let mut overlay = OverlayedChanges::<Blake2Hasher>::default();
overlay.start_transaction();
overlay.set_child_storage(child_info, vec![20], Some(vec![20]));
overlay.set_child_storage(child_info, vec![30], Some(vec![30]));