feat: Rebrand Polkadot/Substrate references to PezkuwiChain
This commit systematically rebrands various references from Parity Technologies' Polkadot/Substrate ecosystem to PezkuwiChain within the kurdistan-sdk. Key changes include: - Updated external repository URLs (zombienet-sdk, parity-db, parity-scale-codec, wasm-instrument) to point to pezkuwichain forks. - Modified internal documentation and code comments to reflect PezkuwiChain naming and structure. - Replaced direct references to with or specific paths within the for XCM, Pezkuwi, and other modules. - Cleaned up deprecated issue and PR references in various and files, particularly in and modules. - Adjusted image and logo URLs in documentation to point to PezkuwiChain assets. - Removed or rephrased comments related to external Polkadot/Substrate PRs and issues. This is a significant step towards fully customizing the SDK for the PezkuwiChain ecosystem.
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
[package]
|
||||
name = "pezsp-state-machine"
|
||||
version = "0.35.0"
|
||||
authors.workspace = true
|
||||
description = "Bizinikiwi State Machine"
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
documentation = "https://docs.rs/pezsp-state-machine"
|
||||
readme = "README.md"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
arbitrary = { features = ["derive"], optional = true, workspace = true }
|
||||
codec = { workspace = true }
|
||||
hash-db = { workspace = true }
|
||||
log = { workspace = true }
|
||||
parking_lot = { optional = true, workspace = true, default-features = true }
|
||||
rand = { optional = true, workspace = true, default-features = true }
|
||||
smallvec = { workspace = true, default-features = true }
|
||||
pezsp-core = { workspace = true }
|
||||
pezsp-externalities = { workspace = true }
|
||||
pezsp-panic-handler = { optional = true, workspace = true, default-features = true }
|
||||
pezsp-trie = { workspace = true }
|
||||
thiserror = { optional = true, workspace = true }
|
||||
tracing = { optional = true, workspace = true, default-features = true }
|
||||
trie-db = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
arbitrary = { features = ["derive"], workspace = true }
|
||||
array-bytes = { workspace = true, default-features = true }
|
||||
assert_matches = { workspace = true }
|
||||
pretty_assertions = { workspace = true }
|
||||
rand = { workspace = true, default-features = true }
|
||||
pezsp-runtime = { workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
fuzzing = ["arbitrary"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"hash-db/std",
|
||||
"log/std",
|
||||
"parking_lot",
|
||||
"rand",
|
||||
"pezsp-core/std",
|
||||
"pezsp-externalities/std",
|
||||
"pezsp-panic-handler",
|
||||
"pezsp-runtime/std",
|
||||
"pezsp-trie/std",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
"trie-db/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
"pezsp-trie/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,3 @@
|
||||
Bizinikiwi state machine implementation.
|
||||
|
||||
License: Apache-2.0
|
||||
@@ -0,0 +1,30 @@
|
||||
[package]
|
||||
name = "pezsp-state-machine-fuzz"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
license = "Apache-2.0"
|
||||
edition = "2021"
|
||||
|
||||
[package.metadata]
|
||||
cargo-fuzz = true
|
||||
|
||||
[dependencies]
|
||||
libfuzzer-sys = "0.4"
|
||||
pezsp-runtime = { path = "../../runtime" }
|
||||
|
||||
[dependencies.pezsp-state-machine]
|
||||
features = ["fuzzing"]
|
||||
path = ".."
|
||||
|
||||
# Prevent this from interfering with workspaces
|
||||
[workspace]
|
||||
members = ["."]
|
||||
|
||||
[profile.release]
|
||||
debug = 1
|
||||
|
||||
[[bin]]
|
||||
name = "fuzz_append"
|
||||
path = "fuzz_targets/fuzz_append.rs"
|
||||
test = false
|
||||
doc = false
|
||||
@@ -0,0 +1,26 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// 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.
|
||||
|
||||
#![no_main]
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use pezsp_state_machine::fuzzing::{fuzz_append, FuzzAppendPayload};
|
||||
use pezsp_runtime::traits::BlakeTwo256;
|
||||
|
||||
fuzz_target!(|data: FuzzAppendPayload| {
|
||||
fuzz_append::<BlakeTwo256>(data);
|
||||
});
|
||||
@@ -0,0 +1,443 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// 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.
|
||||
|
||||
//! State machine backends. These manage the code and storage of contracts.
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use crate::trie_backend::TrieBackend;
|
||||
use crate::{
|
||||
trie_backend_essence::TrieBackendStorage, ChildStorageCollection, StorageCollection,
|
||||
StorageKey, StorageValue, UsageInfo,
|
||||
};
|
||||
use alloc::vec::Vec;
|
||||
use codec::Encode;
|
||||
use core::marker::PhantomData;
|
||||
use hash_db::Hasher;
|
||||
use pezsp_core::storage::{ChildInfo, StateVersion, TrackedStorageKey};
|
||||
#[cfg(feature = "std")]
|
||||
use pezsp_core::traits::RuntimeCode;
|
||||
use pezsp_trie::{MerkleValue, PrefixedMemoryDB, RandomState};
|
||||
|
||||
/// A struct containing arguments for iterating over the storage.
|
||||
#[derive(Default)]
|
||||
#[non_exhaustive]
|
||||
pub struct IterArgs<'a> {
|
||||
/// The prefix of the keys over which to iterate.
|
||||
pub prefix: Option<&'a [u8]>,
|
||||
|
||||
/// The prefix from which to start the iteration from.
|
||||
///
|
||||
/// This is inclusive and the iteration will include the key which is specified here.
|
||||
pub start_at: Option<&'a [u8]>,
|
||||
|
||||
/// If this is `true` then the iteration will *not* include
|
||||
/// the key specified in `start_at`, if there is such a key.
|
||||
pub start_at_exclusive: bool,
|
||||
|
||||
/// The info of the child trie over which to iterate over.
|
||||
pub child_info: Option<ChildInfo>,
|
||||
|
||||
/// Whether to stop iteration when a missing trie node is reached.
|
||||
///
|
||||
/// When a missing trie node is reached the iterator will:
|
||||
/// - return an error if this is set to `false` (default)
|
||||
/// - return `None` if this is set to `true`
|
||||
pub stop_on_incomplete_database: bool,
|
||||
}
|
||||
|
||||
/// A trait for a raw storage iterator.
|
||||
pub trait StorageIterator<H>
|
||||
where
|
||||
H: Hasher,
|
||||
{
|
||||
/// The state backend over which the iterator is iterating.
|
||||
type Backend;
|
||||
|
||||
/// The error type.
|
||||
type Error;
|
||||
|
||||
/// Fetches the next key from the storage.
|
||||
fn next_key(
|
||||
&mut self,
|
||||
backend: &Self::Backend,
|
||||
) -> Option<core::result::Result<StorageKey, Self::Error>>;
|
||||
|
||||
/// Fetches the next key and value from the storage.
|
||||
fn next_pair(
|
||||
&mut self,
|
||||
backend: &Self::Backend,
|
||||
) -> Option<core::result::Result<(StorageKey, StorageValue), Self::Error>>;
|
||||
|
||||
/// Returns whether the end of iteration was reached without an error.
|
||||
fn was_complete(&self) -> bool;
|
||||
}
|
||||
|
||||
/// An iterator over storage keys and values.
|
||||
pub struct PairsIter<'a, H, I>
|
||||
where
|
||||
H: Hasher,
|
||||
I: StorageIterator<H>,
|
||||
{
|
||||
backend: Option<&'a I::Backend>,
|
||||
raw_iter: I,
|
||||
_phantom: PhantomData<H>,
|
||||
}
|
||||
|
||||
impl<'a, H, I> Iterator for PairsIter<'a, H, I>
|
||||
where
|
||||
H: Hasher,
|
||||
I: StorageIterator<H>,
|
||||
{
|
||||
type Item = Result<(Vec<u8>, Vec<u8>), <I as StorageIterator<H>>::Error>;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.raw_iter.next_pair(self.backend.as_ref()?)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, H, I> Default for PairsIter<'a, H, I>
|
||||
where
|
||||
H: Hasher,
|
||||
I: StorageIterator<H> + Default,
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
backend: Default::default(),
|
||||
raw_iter: Default::default(),
|
||||
_phantom: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, H, I> PairsIter<'a, H, I>
|
||||
where
|
||||
H: Hasher,
|
||||
I: StorageIterator<H> + Default,
|
||||
{
|
||||
#[cfg(feature = "std")]
|
||||
pub(crate) fn was_complete(&self) -> bool {
|
||||
self.raw_iter.was_complete()
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator over storage keys.
|
||||
pub struct KeysIter<'a, H, I>
|
||||
where
|
||||
H: Hasher,
|
||||
I: StorageIterator<H>,
|
||||
{
|
||||
backend: Option<&'a I::Backend>,
|
||||
raw_iter: I,
|
||||
_phantom: PhantomData<H>,
|
||||
}
|
||||
|
||||
impl<'a, H, I> Iterator for KeysIter<'a, H, I>
|
||||
where
|
||||
H: Hasher,
|
||||
I: StorageIterator<H>,
|
||||
{
|
||||
type Item = Result<Vec<u8>, <I as StorageIterator<H>>::Error>;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.raw_iter.next_key(self.backend.as_ref()?)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, H, I> Default for KeysIter<'a, H, I>
|
||||
where
|
||||
H: Hasher,
|
||||
I: StorageIterator<H> + Default,
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
backend: Default::default(),
|
||||
raw_iter: Default::default(),
|
||||
_phantom: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The transaction type used by [`Backend`].
|
||||
///
|
||||
/// This transaction contains all the changes that need to be applied to the backend to create the
|
||||
/// state for a new block.
|
||||
pub type BackendTransaction<H> = PrefixedMemoryDB<H>;
|
||||
|
||||
/// A state backend is used to read state data and can have changes committed
|
||||
/// to it.
|
||||
///
|
||||
/// The clone operation (if implemented) should be cheap.
|
||||
pub trait Backend<H: Hasher>: core::fmt::Debug {
|
||||
/// An error type when fetching data is not possible.
|
||||
type Error: super::Error;
|
||||
|
||||
/// Type of trie backend storage.
|
||||
type TrieBackendStorage: TrieBackendStorage<H>;
|
||||
|
||||
/// Type of the raw storage iterator.
|
||||
type RawIter: StorageIterator<H, Backend = Self, Error = Self::Error>;
|
||||
|
||||
/// Get keyed storage or None if there is nothing associated.
|
||||
fn storage(&self, key: &[u8]) -> Result<Option<StorageValue>, Self::Error>;
|
||||
|
||||
/// Get keyed storage value hash or None if there is nothing associated.
|
||||
fn storage_hash(&self, key: &[u8]) -> Result<Option<H::Out>, Self::Error>;
|
||||
|
||||
/// Get the merkle value or None if there is nothing associated.
|
||||
fn closest_merkle_value(&self, key: &[u8]) -> Result<Option<MerkleValue<H::Out>>, Self::Error>;
|
||||
|
||||
/// Get the child merkle value or None if there is nothing associated.
|
||||
fn child_closest_merkle_value(
|
||||
&self,
|
||||
child_info: &ChildInfo,
|
||||
key: &[u8],
|
||||
) -> Result<Option<MerkleValue<H::Out>>, Self::Error>;
|
||||
|
||||
/// Get child keyed child storage or None if there is nothing associated.
|
||||
fn child_storage(
|
||||
&self,
|
||||
child_info: &ChildInfo,
|
||||
key: &[u8],
|
||||
) -> Result<Option<StorageValue>, Self::Error>;
|
||||
|
||||
/// Get child keyed storage value hash or None if there is nothing associated.
|
||||
fn child_storage_hash(
|
||||
&self,
|
||||
child_info: &ChildInfo,
|
||||
key: &[u8],
|
||||
) -> Result<Option<H::Out>, Self::Error>;
|
||||
|
||||
/// true if a key exists in storage.
|
||||
fn exists_storage(&self, key: &[u8]) -> Result<bool, Self::Error> {
|
||||
Ok(self.storage_hash(key)?.is_some())
|
||||
}
|
||||
|
||||
/// true if a key exists in child storage.
|
||||
fn exists_child_storage(
|
||||
&self,
|
||||
child_info: &ChildInfo,
|
||||
key: &[u8],
|
||||
) -> Result<bool, Self::Error> {
|
||||
Ok(self.child_storage_hash(child_info, key)?.is_some())
|
||||
}
|
||||
|
||||
/// Return the next key in storage in lexicographic order or `None` if there is no value.
|
||||
fn next_storage_key(&self, key: &[u8]) -> Result<Option<StorageKey>, Self::Error>;
|
||||
|
||||
/// Return the next key in child storage in lexicographic order or `None` if there is no value.
|
||||
fn next_child_storage_key(
|
||||
&self,
|
||||
child_info: &ChildInfo,
|
||||
key: &[u8],
|
||||
) -> Result<Option<StorageKey>, Self::Error>;
|
||||
|
||||
/// Calculate the storage root, with given delta over what is already stored in
|
||||
/// the backend, and produce a "transaction" that can be used to commit.
|
||||
/// Does not include child storage updates.
|
||||
fn storage_root<'a>(
|
||||
&self,
|
||||
delta: impl Iterator<Item = (&'a [u8], Option<&'a [u8]>)>,
|
||||
state_version: StateVersion,
|
||||
) -> (H::Out, BackendTransaction<H>)
|
||||
where
|
||||
H::Out: Ord;
|
||||
|
||||
/// Calculate the child storage root, with given delta over what is already stored in
|
||||
/// the backend, and produce a "transaction" that can be used to commit. The second argument
|
||||
/// is true if child storage root equals default storage root.
|
||||
fn child_storage_root<'a>(
|
||||
&self,
|
||||
child_info: &ChildInfo,
|
||||
delta: impl Iterator<Item = (&'a [u8], Option<&'a [u8]>)>,
|
||||
state_version: StateVersion,
|
||||
) -> (H::Out, bool, BackendTransaction<H>)
|
||||
where
|
||||
H::Out: Ord;
|
||||
|
||||
/// Returns a lifetimeless raw storage iterator.
|
||||
fn raw_iter(&self, args: IterArgs) -> Result<Self::RawIter, Self::Error>;
|
||||
|
||||
/// Get an iterator over key/value pairs.
|
||||
fn pairs<'a>(&'a self, args: IterArgs) -> Result<PairsIter<'a, H, Self::RawIter>, Self::Error> {
|
||||
Ok(PairsIter {
|
||||
backend: Some(self),
|
||||
raw_iter: self.raw_iter(args)?,
|
||||
_phantom: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Get an iterator over keys.
|
||||
fn keys<'a>(&'a self, args: IterArgs) -> Result<KeysIter<'a, H, Self::RawIter>, Self::Error> {
|
||||
Ok(KeysIter {
|
||||
backend: Some(self),
|
||||
raw_iter: self.raw_iter(args)?,
|
||||
_phantom: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Calculate the storage root, with given delta over what is already stored
|
||||
/// in the backend, and produce a "transaction" that can be used to commit.
|
||||
/// Does include child storage updates.
|
||||
fn full_storage_root<'a>(
|
||||
&self,
|
||||
delta: impl Iterator<Item = (&'a [u8], Option<&'a [u8]>)>,
|
||||
child_deltas: impl Iterator<
|
||||
Item = (&'a ChildInfo, impl Iterator<Item = (&'a [u8], Option<&'a [u8]>)>),
|
||||
>,
|
||||
state_version: StateVersion,
|
||||
) -> (H::Out, BackendTransaction<H>)
|
||||
where
|
||||
H::Out: Ord + Encode,
|
||||
{
|
||||
let mut txs = BackendTransaction::with_hasher(RandomState::default());
|
||||
let mut child_roots: Vec<_> = Default::default();
|
||||
// child first
|
||||
for (child_info, child_delta) in child_deltas {
|
||||
let (child_root, empty, child_txs) =
|
||||
self.child_storage_root(child_info, child_delta, state_version);
|
||||
let prefixed_storage_key = child_info.prefixed_storage_key();
|
||||
txs.consolidate(child_txs);
|
||||
if empty {
|
||||
child_roots.push((prefixed_storage_key.into_inner(), None));
|
||||
} else {
|
||||
child_roots.push((prefixed_storage_key.into_inner(), Some(child_root.encode())));
|
||||
}
|
||||
}
|
||||
let (root, parent_txs) = self.storage_root(
|
||||
delta
|
||||
.map(|(k, v)| (k, v.as_ref().map(|v| &v[..])))
|
||||
.chain(child_roots.iter().map(|(k, v)| (&k[..], v.as_ref().map(|v| &v[..])))),
|
||||
state_version,
|
||||
);
|
||||
txs.consolidate(parent_txs);
|
||||
|
||||
(root, txs)
|
||||
}
|
||||
|
||||
/// Register stats from overlay of state machine.
|
||||
///
|
||||
/// By default nothing is registered.
|
||||
fn register_overlay_stats(&self, _stats: &crate::stats::StateMachineStats);
|
||||
|
||||
/// Query backend usage statistics (i/o, memory)
|
||||
///
|
||||
/// Not all implementations are expected to be able to do this. In the
|
||||
/// case when they don't, empty statistics is returned.
|
||||
fn usage_info(&self) -> UsageInfo;
|
||||
|
||||
/// Wipe the state database.
|
||||
fn wipe(&self) -> Result<(), Self::Error> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
/// Commit given transaction to storage.
|
||||
fn commit(
|
||||
&self,
|
||||
_: H::Out,
|
||||
_: BackendTransaction<H>,
|
||||
_: StorageCollection,
|
||||
_: ChildStorageCollection,
|
||||
) -> Result<(), Self::Error> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
/// Get the read/write count of the db
|
||||
fn read_write_count(&self) -> (u32, u32, u32, u32) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
/// Get the read/write count of the db
|
||||
fn reset_read_write_count(&self) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
/// Get the whitelist for tracking db reads/writes
|
||||
fn get_whitelist(&self) -> Vec<TrackedStorageKey> {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Update the whitelist for tracking db reads/writes
|
||||
fn set_whitelist(&self, _: Vec<TrackedStorageKey>) {}
|
||||
|
||||
/// Estimate proof size
|
||||
fn proof_size(&self) -> Option<u32> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
/// Extend storage info for benchmarking db
|
||||
fn get_read_and_written_keys(&self) -> Vec<(Vec<u8>, u32, u32, bool)> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
/// Something that can be converted into a [`TrieBackend`].
|
||||
#[cfg(feature = "std")]
|
||||
pub trait AsTrieBackend<H: Hasher, C = pezsp_trie::cache::LocalTrieCache<H>> {
|
||||
/// Type of trie backend storage.
|
||||
type TrieBackendStorage: TrieBackendStorage<H>;
|
||||
|
||||
/// Return the type as [`TrieBackend`].
|
||||
fn as_trie_backend(&self) -> &TrieBackend<Self::TrieBackendStorage, H, C>;
|
||||
}
|
||||
|
||||
/// Wrapper to create a [`RuntimeCode`] from a type that implements [`Backend`].
|
||||
#[cfg(feature = "std")]
|
||||
pub struct BackendRuntimeCode<'a, B, H> {
|
||||
backend: &'a B,
|
||||
_marker: PhantomData<H>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<'a, B: Backend<H>, H: Hasher> pezsp_core::traits::FetchRuntimeCode
|
||||
for BackendRuntimeCode<'a, B, H>
|
||||
{
|
||||
fn fetch_runtime_code(&self) -> Option<std::borrow::Cow<'_, [u8]>> {
|
||||
self.backend
|
||||
.storage(pezsp_core::storage::well_known_keys::CODE)
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<'a, B: Backend<H>, H: Hasher> BackendRuntimeCode<'a, B, H>
|
||||
where
|
||||
H::Out: Encode,
|
||||
{
|
||||
/// Create a new instance.
|
||||
pub fn new(backend: &'a B) -> Self {
|
||||
Self { backend, _marker: PhantomData }
|
||||
}
|
||||
|
||||
/// Return the [`RuntimeCode`] build from the wrapped `backend`.
|
||||
pub fn runtime_code(&self) -> Result<RuntimeCode<'_>, &'static str> {
|
||||
let hash = self
|
||||
.backend
|
||||
.storage_hash(pezsp_core::storage::well_known_keys::CODE)
|
||||
.ok()
|
||||
.flatten()
|
||||
.ok_or("`:code` hash not found")?
|
||||
.encode();
|
||||
let heap_pages = self
|
||||
.backend
|
||||
.storage(pezsp_core::storage::well_known_keys::HEAP_PAGES)
|
||||
.ok()
|
||||
.flatten()
|
||||
.and_then(|d| codec::Decode::decode(&mut &d[..]).ok());
|
||||
|
||||
Ok(RuntimeCode { code_fetcher: self, hash, heap_pages })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,461 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// 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.
|
||||
|
||||
//! Basic implementation for Externalities.
|
||||
|
||||
use crate::{Backend, OverlayedChanges, StorageKey, StorageValue};
|
||||
use alloc::{boxed::Box, collections::BTreeMap, vec::Vec};
|
||||
use codec::Encode;
|
||||
use core::{
|
||||
any::{Any, TypeId},
|
||||
iter::FromIterator,
|
||||
};
|
||||
use hash_db::Hasher;
|
||||
use log::warn;
|
||||
use pezsp_core::{
|
||||
storage::{
|
||||
well_known_keys::is_child_storage_key, ChildInfo, StateVersion, Storage, TrackedStorageKey,
|
||||
},
|
||||
traits::Externalities,
|
||||
Blake2Hasher,
|
||||
};
|
||||
use pezsp_externalities::{Extension, Extensions, MultiRemovalResults};
|
||||
use pezsp_trie::{empty_child_trie_root, LayoutV0, LayoutV1, TrieConfiguration};
|
||||
|
||||
/// Simple Map-based Externalities impl.
|
||||
#[derive(Debug)]
|
||||
pub struct BasicExternalities {
|
||||
overlay: OverlayedChanges<Blake2Hasher>,
|
||||
extensions: Extensions,
|
||||
}
|
||||
|
||||
impl BasicExternalities {
|
||||
/// Create a new instance of `BasicExternalities`
|
||||
pub fn new(inner: Storage) -> Self {
|
||||
BasicExternalities { overlay: inner.into(), extensions: Default::default() }
|
||||
}
|
||||
|
||||
/// New basic externalities with empty storage.
|
||||
pub fn new_empty() -> Self {
|
||||
Self::new(Storage::default())
|
||||
}
|
||||
|
||||
/// Insert key/value
|
||||
pub fn insert(&mut self, k: StorageKey, v: StorageValue) {
|
||||
self.overlay.set_storage(k, Some(v));
|
||||
}
|
||||
|
||||
/// Consume self and returns inner storages
|
||||
#[cfg(feature = "std")]
|
||||
pub fn into_storages(mut self) -> Storage {
|
||||
Storage {
|
||||
top: self
|
||||
.overlay
|
||||
.changes_mut()
|
||||
.filter_map(|(k, v)| v.value().map(|v| (k.to_vec(), v.to_vec())))
|
||||
.collect(),
|
||||
children_default: self
|
||||
.overlay
|
||||
.children_mut()
|
||||
.map(|(iter, i)| {
|
||||
(
|
||||
i.storage_key().to_vec(),
|
||||
pezsp_core::storage::StorageChild {
|
||||
data: iter
|
||||
.filter_map(|(k, v)| v.value().map(|v| (k.to_vec(), v.to_vec())))
|
||||
.collect(),
|
||||
child_info: i.clone(),
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute the given closure `f` with the externalities set and initialized with `storage`.
|
||||
///
|
||||
/// Returns the result of the closure and updates `storage` with all changes.
|
||||
#[cfg(feature = "std")]
|
||||
pub fn execute_with_storage<R>(
|
||||
storage: &mut pezsp_core::storage::Storage,
|
||||
f: impl FnOnce() -> R,
|
||||
) -> R {
|
||||
let mut ext = Self::new(core::mem::take(storage));
|
||||
|
||||
let r = ext.execute_with(f);
|
||||
|
||||
*storage = ext.into_storages();
|
||||
|
||||
r
|
||||
}
|
||||
|
||||
/// Execute the given closure while `self` is set as externalities.
|
||||
///
|
||||
/// Returns the result of the given closure.
|
||||
pub fn execute_with<R>(&mut self, f: impl FnOnce() -> R) -> R {
|
||||
pezsp_externalities::set_and_run_with_externalities(self, f)
|
||||
}
|
||||
|
||||
/// List of active extensions.
|
||||
pub fn extensions(&mut self) -> &mut Extensions {
|
||||
&mut self.extensions
|
||||
}
|
||||
|
||||
/// Register an extension.
|
||||
pub fn register_extension(&mut self, ext: impl Extension) {
|
||||
self.extensions.register(ext);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl PartialEq for BasicExternalities {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.overlay
|
||||
.changes()
|
||||
.map(|(k, v)| (k, v.value_ref().materialize()))
|
||||
.collect::<BTreeMap<_, _>>() ==
|
||||
other
|
||||
.overlay
|
||||
.changes()
|
||||
.map(|(k, v)| (k, v.value_ref().materialize()))
|
||||
.collect::<BTreeMap<_, _>>() &&
|
||||
self.overlay
|
||||
.children()
|
||||
.map(|(iter, i)| {
|
||||
(
|
||||
i,
|
||||
iter.map(|(k, v)| (k, v.value_ref().materialize()))
|
||||
.collect::<BTreeMap<_, _>>(),
|
||||
)
|
||||
})
|
||||
.collect::<BTreeMap<_, _>>() ==
|
||||
other
|
||||
.overlay
|
||||
.children()
|
||||
.map(|(iter, i)| {
|
||||
(
|
||||
i,
|
||||
iter.map(|(k, v)| (k, v.value_ref().materialize()))
|
||||
.collect::<BTreeMap<_, _>>(),
|
||||
)
|
||||
})
|
||||
.collect::<BTreeMap<_, _>>()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<(StorageKey, StorageValue)> for BasicExternalities {
|
||||
fn from_iter<I: IntoIterator<Item = (StorageKey, StorageValue)>>(iter: I) -> Self {
|
||||
let mut t = Self::default();
|
||||
iter.into_iter().for_each(|(k, v)| t.insert(k, v));
|
||||
t
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for BasicExternalities {
|
||||
fn default() -> Self {
|
||||
Self::new(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BTreeMap<StorageKey, StorageValue>> for BasicExternalities {
|
||||
fn from(map: BTreeMap<StorageKey, StorageValue>) -> Self {
|
||||
Self::from_iter(map.into_iter())
|
||||
}
|
||||
}
|
||||
|
||||
impl Externalities for BasicExternalities {
|
||||
fn set_offchain_storage(&mut self, _key: &[u8], _value: Option<&[u8]>) {}
|
||||
|
||||
fn storage(&mut self, key: &[u8]) -> Option<StorageValue> {
|
||||
self.overlay.storage(key).and_then(|v| v.map(|v| v.to_vec()))
|
||||
}
|
||||
|
||||
fn storage_hash(&mut self, key: &[u8]) -> Option<Vec<u8>> {
|
||||
self.storage(key).map(|v| Blake2Hasher::hash(&v).encode())
|
||||
}
|
||||
|
||||
fn child_storage(&mut self, child_info: &ChildInfo, key: &[u8]) -> Option<StorageValue> {
|
||||
self.overlay.child_storage(child_info, key).and_then(|v| v.map(|v| v.to_vec()))
|
||||
}
|
||||
|
||||
fn child_storage_hash(&mut self, child_info: &ChildInfo, key: &[u8]) -> Option<Vec<u8>> {
|
||||
self.child_storage(child_info, key).map(|v| Blake2Hasher::hash(&v).encode())
|
||||
}
|
||||
|
||||
fn next_storage_key(&mut self, key: &[u8]) -> Option<StorageKey> {
|
||||
self.overlay.iter_after(key).find_map(|(k, v)| v.value().map(|_| k.to_vec()))
|
||||
}
|
||||
|
||||
fn next_child_storage_key(&mut self, child_info: &ChildInfo, key: &[u8]) -> Option<StorageKey> {
|
||||
self.overlay
|
||||
.child_iter_after(child_info.storage_key(), key)
|
||||
.find_map(|(k, v)| v.value().map(|_| k.to_vec()))
|
||||
}
|
||||
|
||||
fn place_storage(&mut self, key: StorageKey, maybe_value: Option<StorageValue>) {
|
||||
if is_child_storage_key(&key) {
|
||||
warn!(target: "trie", "Refuse to set child storage key via main storage");
|
||||
return;
|
||||
}
|
||||
|
||||
self.overlay.set_storage(key, maybe_value)
|
||||
}
|
||||
|
||||
fn place_child_storage(
|
||||
&mut self,
|
||||
child_info: &ChildInfo,
|
||||
key: StorageKey,
|
||||
value: Option<StorageValue>,
|
||||
) {
|
||||
self.overlay.set_child_storage(child_info, key, value);
|
||||
}
|
||||
|
||||
fn kill_child_storage(
|
||||
&mut self,
|
||||
child_info: &ChildInfo,
|
||||
_maybe_limit: Option<u32>,
|
||||
_maybe_cursor: Option<&[u8]>,
|
||||
) -> MultiRemovalResults {
|
||||
let count = self.overlay.clear_child_storage(child_info);
|
||||
MultiRemovalResults { maybe_cursor: None, backend: count, unique: count, loops: count }
|
||||
}
|
||||
|
||||
fn clear_prefix(
|
||||
&mut self,
|
||||
prefix: &[u8],
|
||||
_maybe_limit: Option<u32>,
|
||||
_maybe_cursor: Option<&[u8]>,
|
||||
) -> MultiRemovalResults {
|
||||
if is_child_storage_key(prefix) {
|
||||
warn!(
|
||||
target: "trie",
|
||||
"Refuse to clear prefix that is part of child storage key via main storage"
|
||||
);
|
||||
let maybe_cursor = Some(prefix.to_vec());
|
||||
return MultiRemovalResults { maybe_cursor, backend: 0, unique: 0, loops: 0 };
|
||||
}
|
||||
|
||||
let count = self.overlay.clear_prefix(prefix);
|
||||
MultiRemovalResults { maybe_cursor: None, backend: count, unique: count, loops: count }
|
||||
}
|
||||
|
||||
fn clear_child_prefix(
|
||||
&mut self,
|
||||
child_info: &ChildInfo,
|
||||
prefix: &[u8],
|
||||
_maybe_limit: Option<u32>,
|
||||
_maybe_cursor: Option<&[u8]>,
|
||||
) -> MultiRemovalResults {
|
||||
let count = self.overlay.clear_child_prefix(child_info, prefix);
|
||||
MultiRemovalResults { maybe_cursor: None, backend: count, unique: count, loops: count }
|
||||
}
|
||||
|
||||
fn storage_append(&mut self, key: Vec<u8>, element: Vec<u8>) {
|
||||
self.overlay.append_storage(key, element, Default::default);
|
||||
}
|
||||
|
||||
fn storage_root(&mut self, state_version: StateVersion) -> Vec<u8> {
|
||||
let mut top = self
|
||||
.overlay
|
||||
.changes_mut()
|
||||
.filter_map(|(k, v)| v.value().map(|v| (k.clone(), v.clone())))
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
// Single child trie implementation currently allows using the same child
|
||||
// empty root for all child trie. Using null storage key until multiple
|
||||
// type of child trie support.
|
||||
let empty_hash = empty_child_trie_root::<LayoutV1<Blake2Hasher>>();
|
||||
for child_info in self.overlay.children().map(|d| d.1.clone()).collect::<Vec<_>>() {
|
||||
let child_root = self.child_storage_root(&child_info, state_version);
|
||||
if empty_hash[..] == child_root[..] {
|
||||
top.remove(child_info.prefixed_storage_key().as_slice());
|
||||
} else {
|
||||
top.insert(child_info.prefixed_storage_key().into_inner(), child_root);
|
||||
}
|
||||
}
|
||||
|
||||
match state_version {
|
||||
StateVersion::V0 => LayoutV0::<Blake2Hasher>::trie_root(top).as_ref().into(),
|
||||
StateVersion::V1 => LayoutV1::<Blake2Hasher>::trie_root(top).as_ref().into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn child_storage_root(
|
||||
&mut self,
|
||||
child_info: &ChildInfo,
|
||||
state_version: StateVersion,
|
||||
) -> Vec<u8> {
|
||||
if let Some((data, child_info)) = self.overlay.child_changes_mut(child_info.storage_key()) {
|
||||
let delta =
|
||||
data.into_iter().map(|(k, v)| (k.as_ref(), v.value().map(|v| v.as_slice())));
|
||||
crate::in_memory_backend::new_in_mem::<Blake2Hasher>()
|
||||
.child_storage_root(&child_info, delta, state_version)
|
||||
.0
|
||||
} else {
|
||||
empty_child_trie_root::<LayoutV1<Blake2Hasher>>()
|
||||
}
|
||||
.encode()
|
||||
}
|
||||
|
||||
fn storage_start_transaction(&mut self) {
|
||||
self.overlay.start_transaction()
|
||||
}
|
||||
|
||||
fn storage_rollback_transaction(&mut self) -> Result<(), ()> {
|
||||
self.overlay.rollback_transaction().map_err(drop)
|
||||
}
|
||||
|
||||
fn storage_commit_transaction(&mut self) -> Result<(), ()> {
|
||||
self.overlay.commit_transaction().map_err(drop)
|
||||
}
|
||||
|
||||
fn wipe(&mut self) {}
|
||||
|
||||
fn commit(&mut self) {}
|
||||
|
||||
fn read_write_count(&self) -> (u32, u32, u32, u32) {
|
||||
unimplemented!("read_write_count is not supported in Basic")
|
||||
}
|
||||
|
||||
fn reset_read_write_count(&mut self) {
|
||||
unimplemented!("reset_read_write_count is not supported in Basic")
|
||||
}
|
||||
|
||||
fn get_whitelist(&self) -> Vec<TrackedStorageKey> {
|
||||
unimplemented!("get_whitelist is not supported in Basic")
|
||||
}
|
||||
|
||||
fn set_whitelist(&mut self, _: Vec<TrackedStorageKey>) {
|
||||
unimplemented!("set_whitelist is not supported in Basic")
|
||||
}
|
||||
|
||||
fn get_read_and_written_keys(&self) -> Vec<(Vec<u8>, u32, u32, bool)> {
|
||||
unimplemented!("get_read_and_written_keys is not supported in Basic")
|
||||
}
|
||||
}
|
||||
|
||||
impl pezsp_externalities::ExtensionStore for BasicExternalities {
|
||||
fn extension_by_type_id(&mut self, type_id: TypeId) -> Option<&mut dyn Any> {
|
||||
self.extensions.get_mut(type_id)
|
||||
}
|
||||
|
||||
fn register_extension_with_type_id(
|
||||
&mut self,
|
||||
type_id: TypeId,
|
||||
extension: Box<dyn pezsp_externalities::Extension>,
|
||||
) -> Result<(), pezsp_externalities::Error> {
|
||||
self.extensions.register_with_type_id(type_id, extension)
|
||||
}
|
||||
|
||||
fn deregister_extension_by_type_id(
|
||||
&mut self,
|
||||
type_id: TypeId,
|
||||
) -> Result<(), pezsp_externalities::Error> {
|
||||
if self.extensions.deregister(type_id) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(pezsp_externalities::Error::ExtensionIsNotRegistered(type_id))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use pezsp_core::{
|
||||
map,
|
||||
storage::{well_known_keys::CODE, Storage, StorageChild},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn commit_should_work() {
|
||||
let mut ext = BasicExternalities::default();
|
||||
ext.set_storage(b"doe".to_vec(), b"reindeer".to_vec());
|
||||
ext.set_storage(b"dog".to_vec(), b"puppy".to_vec());
|
||||
ext.set_storage(b"dogglesworth".to_vec(), b"cat".to_vec());
|
||||
let root = array_bytes::hex2bytes_unchecked(
|
||||
"39245109cef3758c2eed2ccba8d9b370a917850af3824bc8348d505df2c298fa",
|
||||
);
|
||||
|
||||
assert_eq!(&ext.storage_root(StateVersion::default())[..], &root);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_and_retrieve_code() {
|
||||
let mut ext = BasicExternalities::default();
|
||||
|
||||
let code = vec![1, 2, 3];
|
||||
ext.set_storage(CODE.to_vec(), code.clone());
|
||||
|
||||
assert_eq!(&ext.storage(CODE).unwrap(), &code);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn children_works() {
|
||||
let child_info = ChildInfo::new_default(b"storage_key");
|
||||
let child_info = &child_info;
|
||||
let mut ext = BasicExternalities::new(Storage {
|
||||
top: Default::default(),
|
||||
children_default: map![
|
||||
child_info.storage_key().to_vec() => StorageChild {
|
||||
data: map![ b"doe".to_vec() => b"reindeer".to_vec() ],
|
||||
child_info: child_info.to_owned(),
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
assert_eq!(ext.child_storage(child_info, b"doe"), Some(b"reindeer".to_vec()));
|
||||
|
||||
ext.set_child_storage(child_info, b"dog".to_vec(), b"puppy".to_vec());
|
||||
assert_eq!(ext.child_storage(child_info, b"dog"), Some(b"puppy".to_vec()));
|
||||
|
||||
ext.clear_child_storage(child_info, b"dog");
|
||||
assert_eq!(ext.child_storage(child_info, b"dog"), None);
|
||||
|
||||
let _ = ext.kill_child_storage(child_info, None, None);
|
||||
assert_eq!(ext.child_storage(child_info, b"doe"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn kill_child_storage_returns_num_elements_removed() {
|
||||
let child_info = ChildInfo::new_default(b"storage_key");
|
||||
let child_info = &child_info;
|
||||
let mut ext = BasicExternalities::new(Storage {
|
||||
top: Default::default(),
|
||||
children_default: map![
|
||||
child_info.storage_key().to_vec() => StorageChild {
|
||||
data: map![
|
||||
b"doe".to_vec() => b"reindeer".to_vec(),
|
||||
b"dog".to_vec() => b"puppy".to_vec(),
|
||||
b"hello".to_vec() => b"world".to_vec(),
|
||||
],
|
||||
child_info: child_info.to_owned(),
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
let res = ext.kill_child_storage(child_info, None, None);
|
||||
assert_eq!(res.deconstruct(), (None, 3, 3, 3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_externalities_is_empty() {
|
||||
// Make sure no values are set by default in `BasicExternalities`.
|
||||
let storage = BasicExternalities::new_empty().into_storages();
|
||||
assert!(storage.top.is_empty());
|
||||
assert!(storage.children_default.is_empty());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// 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.
|
||||
|
||||
/// State Machine Errors
|
||||
use core::fmt;
|
||||
|
||||
/// State Machine Error bound.
|
||||
///
|
||||
/// This should reflect Wasm error type bound for future compatibility.
|
||||
pub trait Error: 'static + fmt::Debug + fmt::Display + Send + Sync {}
|
||||
|
||||
impl<T: 'static + fmt::Debug + fmt::Display + Send + Sync> Error for T {}
|
||||
|
||||
/// Externalities Error.
|
||||
///
|
||||
/// Externalities are not really allowed to have errors, since it's assumed that dependent code
|
||||
/// would not be executed unless externalities were available. This is included for completeness,
|
||||
/// and as a transition away from the pre-existing framework.
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
#[allow(missing_docs)]
|
||||
#[cfg_attr(feature = "std", derive(thiserror::Error))]
|
||||
pub enum ExecutionError {
|
||||
#[cfg_attr(feature = "std", error("Backend error {0:?}"))]
|
||||
Backend(crate::DefaultError),
|
||||
|
||||
#[cfg_attr(feature = "std", error("`:code` entry does not exist in storage"))]
|
||||
CodeEntryDoesNotExist,
|
||||
|
||||
#[cfg_attr(feature = "std", error("Unable to generate proof"))]
|
||||
UnableToGenerateProof,
|
||||
|
||||
#[cfg_attr(feature = "std", error("Invalid execution proof"))]
|
||||
InvalidProof,
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,319 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// 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.
|
||||
|
||||
//! State machine fuzzing implementation, behind `fuzzing` feature.
|
||||
|
||||
use super::{ext::Ext, *};
|
||||
use crate::ext::StorageAppend;
|
||||
use arbitrary::Arbitrary;
|
||||
#[cfg(test)]
|
||||
use codec::Encode;
|
||||
use hash_db::Hasher;
|
||||
use pezsp_core::{storage::StateVersion, traits::Externalities};
|
||||
#[cfg(test)]
|
||||
use pezsp_runtime::traits::BlakeTwo256;
|
||||
use pezsp_trie::PrefixedMemoryDB;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[derive(Arbitrary, Debug, Clone)]
|
||||
enum DataLength {
|
||||
Zero = 0,
|
||||
Small = 1,
|
||||
Medium = 3,
|
||||
Big = 300, // 2 byte scale encode length
|
||||
}
|
||||
|
||||
#[derive(Arbitrary, Debug, Clone)]
|
||||
#[repr(u8)]
|
||||
enum DataValue {
|
||||
A = b'a',
|
||||
B = b'b',
|
||||
C = b'c',
|
||||
D = b'd', // This can be read as a multiple byte compact length.
|
||||
EasyBug = 20u8, // value compact len.
|
||||
}
|
||||
|
||||
/// Action to fuzz
|
||||
#[derive(Arbitrary, Debug, Clone)]
|
||||
enum FuzzAppendItem {
|
||||
Append(DataValue, DataLength),
|
||||
Insert(DataValue, DataLength),
|
||||
StartTransaction,
|
||||
RollbackTransaction,
|
||||
CommitTransaction,
|
||||
Read,
|
||||
Remove,
|
||||
// To go over 256 items easily (different compact size then).
|
||||
Append50(DataValue, DataLength),
|
||||
}
|
||||
|
||||
/// Arbitrary payload for fuzzing append.
|
||||
#[derive(Arbitrary, Debug, Clone)]
|
||||
pub struct FuzzAppendPayload(Vec<FuzzAppendItem>, Option<(DataValue, DataLength)>);
|
||||
|
||||
struct SimpleOverlay {
|
||||
data: Vec<BTreeMap<Vec<u8>, Option<Vec<u8>>>>,
|
||||
}
|
||||
|
||||
impl Default for SimpleOverlay {
|
||||
fn default() -> Self {
|
||||
Self { data: vec![BTreeMap::new()] }
|
||||
}
|
||||
}
|
||||
|
||||
impl SimpleOverlay {
|
||||
fn insert(&mut self, key: Vec<u8>, value: Option<Vec<u8>>) {
|
||||
self.data.last_mut().expect("always at least one item").insert(key, value);
|
||||
}
|
||||
|
||||
fn append<H>(
|
||||
&mut self,
|
||||
key: Vec<u8>,
|
||||
value: Vec<u8>,
|
||||
backend: &mut TrieBackend<PrefixedMemoryDB<H>, H>,
|
||||
) where
|
||||
H: Hasher,
|
||||
H::Out: codec::Decode + codec::Encode + 'static,
|
||||
{
|
||||
let current_value = self
|
||||
.data
|
||||
.last_mut()
|
||||
.expect("always at least one item")
|
||||
.entry(key.clone())
|
||||
.or_insert_with(|| {
|
||||
Some(backend.storage(&key).expect("Ext not allowed to fail").unwrap_or_default())
|
||||
});
|
||||
if current_value.is_none() {
|
||||
*current_value = Some(vec![]);
|
||||
}
|
||||
StorageAppend::new(current_value.as_mut().expect("init above")).append(value);
|
||||
}
|
||||
|
||||
fn get(&mut self, key: &[u8]) -> Option<&Vec<u8>> {
|
||||
self.data
|
||||
.last_mut()
|
||||
.expect("always at least one item")
|
||||
.get(key)
|
||||
.and_then(|o| o.as_ref())
|
||||
}
|
||||
|
||||
fn commit_transaction(&mut self) {
|
||||
if let Some(to_commit) = self.data.pop() {
|
||||
let dest = self.data.last_mut().expect("always at least one item");
|
||||
for (k, v) in to_commit.into_iter() {
|
||||
dest.insert(k, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn rollback_transaction(&mut self) {
|
||||
let _ = self.data.pop();
|
||||
}
|
||||
|
||||
fn start_transaction(&mut self) {
|
||||
let cloned = self.data.last().expect("always at least one item").clone();
|
||||
self.data.push(cloned);
|
||||
}
|
||||
}
|
||||
|
||||
struct FuzzAppendState<H: Hasher> {
|
||||
key: Vec<u8>,
|
||||
|
||||
// reference simple implementation
|
||||
reference: SimpleOverlay,
|
||||
|
||||
// trie backend
|
||||
backend: TrieBackend<PrefixedMemoryDB<H>, H>,
|
||||
// Standard Overlay
|
||||
overlay: OverlayedChanges<H>,
|
||||
|
||||
// block dropping/commiting too many transaction
|
||||
transaction_depth: usize,
|
||||
}
|
||||
|
||||
impl<H> FuzzAppendState<H>
|
||||
where
|
||||
H: Hasher,
|
||||
H::Out: codec::Decode + codec::Encode + 'static,
|
||||
{
|
||||
fn process_item(&mut self, item: FuzzAppendItem) {
|
||||
let mut ext = Ext::new(&mut self.overlay, &mut self.backend, None);
|
||||
match item {
|
||||
FuzzAppendItem::Append(value, length) => {
|
||||
let value = vec![value as u8; length as usize];
|
||||
ext.storage_append(self.key.clone(), value.clone());
|
||||
self.reference.append(self.key.clone(), value, &mut self.backend);
|
||||
},
|
||||
FuzzAppendItem::Append50(value, length) => {
|
||||
let value = vec![value as u8; length as usize];
|
||||
for _ in 0..50 {
|
||||
let mut ext = Ext::new(&mut self.overlay, &mut self.backend, None);
|
||||
ext.storage_append(self.key.clone(), value.clone());
|
||||
self.reference.append(self.key.clone(), value.clone(), &mut self.backend);
|
||||
}
|
||||
},
|
||||
FuzzAppendItem::Insert(value, length) => {
|
||||
let value = vec![value as u8; length as usize];
|
||||
ext.set_storage(self.key.clone(), value.clone());
|
||||
self.reference.insert(self.key.clone(), Some(value));
|
||||
},
|
||||
FuzzAppendItem::Remove => {
|
||||
ext.clear_storage(&self.key);
|
||||
self.reference.insert(self.key.clone(), None);
|
||||
},
|
||||
FuzzAppendItem::Read => {
|
||||
let left = ext.storage(self.key.as_slice());
|
||||
let right = self.reference.get(self.key.as_slice());
|
||||
assert_eq!(left.as_ref(), right);
|
||||
},
|
||||
FuzzAppendItem::StartTransaction => {
|
||||
self.transaction_depth += 1;
|
||||
self.reference.start_transaction();
|
||||
ext.storage_start_transaction();
|
||||
},
|
||||
FuzzAppendItem::RollbackTransaction => {
|
||||
if self.transaction_depth == 0 {
|
||||
return;
|
||||
}
|
||||
self.transaction_depth -= 1;
|
||||
self.reference.rollback_transaction();
|
||||
ext.storage_rollback_transaction().unwrap();
|
||||
},
|
||||
FuzzAppendItem::CommitTransaction => {
|
||||
if self.transaction_depth == 0 {
|
||||
return;
|
||||
}
|
||||
self.transaction_depth -= 1;
|
||||
self.reference.commit_transaction();
|
||||
ext.storage_commit_transaction().unwrap();
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn check_final_state(&mut self) {
|
||||
let mut ext = Ext::new(&mut self.overlay, &mut self.backend, None);
|
||||
let left = ext.storage(self.key.as_slice());
|
||||
let right = self.reference.get(self.key.as_slice());
|
||||
assert_eq!(left.as_ref(), right);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fuzz_scenarii() {
|
||||
assert_eq!(codec::Compact(5u16).encode()[0], DataValue::EasyBug as u8);
|
||||
let scenarii = vec![
|
||||
(
|
||||
vec![
|
||||
FuzzAppendItem::Append(DataValue::A, DataLength::Small),
|
||||
FuzzAppendItem::StartTransaction,
|
||||
FuzzAppendItem::Append50(DataValue::D, DataLength::Small),
|
||||
FuzzAppendItem::Read,
|
||||
FuzzAppendItem::RollbackTransaction,
|
||||
FuzzAppendItem::StartTransaction,
|
||||
FuzzAppendItem::Append(DataValue::D, DataLength::Small),
|
||||
FuzzAppendItem::Read,
|
||||
FuzzAppendItem::RollbackTransaction,
|
||||
],
|
||||
Some((DataValue::D, DataLength::Small)),
|
||||
),
|
||||
(
|
||||
vec![
|
||||
FuzzAppendItem::Append(DataValue::B, DataLength::Small),
|
||||
FuzzAppendItem::StartTransaction,
|
||||
FuzzAppendItem::Append(DataValue::A, DataLength::Small),
|
||||
FuzzAppendItem::StartTransaction,
|
||||
FuzzAppendItem::Remove,
|
||||
FuzzAppendItem::StartTransaction,
|
||||
FuzzAppendItem::Append(DataValue::A, DataLength::Zero),
|
||||
FuzzAppendItem::CommitTransaction,
|
||||
FuzzAppendItem::CommitTransaction,
|
||||
FuzzAppendItem::Remove,
|
||||
],
|
||||
Some((DataValue::EasyBug, DataLength::Small)),
|
||||
),
|
||||
(
|
||||
vec![
|
||||
FuzzAppendItem::Append(DataValue::A, DataLength::Small),
|
||||
FuzzAppendItem::StartTransaction,
|
||||
FuzzAppendItem::Append(DataValue::A, DataLength::Medium),
|
||||
FuzzAppendItem::StartTransaction,
|
||||
FuzzAppendItem::Remove,
|
||||
FuzzAppendItem::CommitTransaction,
|
||||
FuzzAppendItem::RollbackTransaction,
|
||||
],
|
||||
Some((DataValue::B, DataLength::Big)),
|
||||
),
|
||||
(
|
||||
vec![
|
||||
FuzzAppendItem::Append(DataValue::A, DataLength::Big),
|
||||
FuzzAppendItem::StartTransaction,
|
||||
FuzzAppendItem::Append(DataValue::A, DataLength::Medium),
|
||||
FuzzAppendItem::Remove,
|
||||
FuzzAppendItem::RollbackTransaction,
|
||||
FuzzAppendItem::StartTransaction,
|
||||
FuzzAppendItem::Append(DataValue::A, DataLength::Zero),
|
||||
],
|
||||
None,
|
||||
),
|
||||
(
|
||||
vec![
|
||||
FuzzAppendItem::StartTransaction,
|
||||
FuzzAppendItem::RollbackTransaction,
|
||||
FuzzAppendItem::RollbackTransaction,
|
||||
FuzzAppendItem::Append(DataValue::A, DataLength::Zero),
|
||||
],
|
||||
None,
|
||||
),
|
||||
(vec![FuzzAppendItem::StartTransaction], Some((DataValue::EasyBug, DataLength::Zero))),
|
||||
];
|
||||
|
||||
for (scenario, init) in scenarii.into_iter() {
|
||||
fuzz_append::<BlakeTwo256>(FuzzAppendPayload(scenario, init));
|
||||
}
|
||||
}
|
||||
|
||||
/// Test append operation for a given fuzzing payload.
|
||||
pub fn fuzz_append<H>(payload: FuzzAppendPayload)
|
||||
where
|
||||
H: Hasher,
|
||||
H::Out: codec::Decode + codec::Encode + 'static,
|
||||
{
|
||||
let FuzzAppendPayload(to_fuzz, initial) = payload;
|
||||
let key = b"k".to_vec();
|
||||
let mut reference = SimpleOverlay::default();
|
||||
let initial: BTreeMap<_, _> = initial
|
||||
.into_iter()
|
||||
.map(|(v, l)| (key.clone(), vec![v as u8; l as usize]))
|
||||
.collect();
|
||||
for (k, v) in initial.iter() {
|
||||
reference.data[0].insert(k.clone(), Some(v.clone()));
|
||||
}
|
||||
reference.start_transaction(); // level 0 is backend, keep it untouched.
|
||||
let overlay = OverlayedChanges::default();
|
||||
|
||||
let mut state = FuzzAppendState::<H> {
|
||||
key,
|
||||
reference,
|
||||
overlay,
|
||||
backend: (initial, StateVersion::default()).into(),
|
||||
transaction_depth: 0,
|
||||
};
|
||||
for item in to_fuzz {
|
||||
state.process_item(item);
|
||||
}
|
||||
state.check_final_state();
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// 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.
|
||||
|
||||
//! State machine in memory backend.
|
||||
|
||||
use crate::{
|
||||
backend::Backend, trie_backend::TrieBackend, StorageCollection, StorageKey, StorageValue,
|
||||
TrieBackendBuilder,
|
||||
};
|
||||
use alloc::{collections::BTreeMap, vec::Vec};
|
||||
use codec::Codec;
|
||||
use hash_db::Hasher;
|
||||
use pezsp_core::storage::{ChildInfo, StateVersion, Storage};
|
||||
use pezsp_trie::{empty_trie_root, LayoutV1, PrefixedMemoryDB, RandomState};
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use std::collections::HashMap as MapType;
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
use alloc::collections::BTreeMap as MapType;
|
||||
|
||||
/// Create a new empty instance of in-memory backend.
|
||||
pub fn new_in_mem<H>() -> TrieBackend<PrefixedMemoryDB<H>, H>
|
||||
where
|
||||
H: Hasher,
|
||||
H::Out: Codec + Ord,
|
||||
{
|
||||
// V1 is same as V0 for an empty trie.
|
||||
TrieBackendBuilder::new(
|
||||
PrefixedMemoryDB::with_hasher(RandomState::default()),
|
||||
empty_trie_root::<LayoutV1<H>>(),
|
||||
)
|
||||
.build()
|
||||
}
|
||||
|
||||
impl<H: Hasher> TrieBackend<PrefixedMemoryDB<H>, H>
|
||||
where
|
||||
H::Out: Codec + Ord,
|
||||
{
|
||||
/// Copy the state, with applied updates
|
||||
pub fn update<T: IntoIterator<Item = (Option<ChildInfo>, StorageCollection)>>(
|
||||
&self,
|
||||
changes: T,
|
||||
state_version: StateVersion,
|
||||
) -> Self {
|
||||
let mut clone = self.clone();
|
||||
clone.insert(changes, state_version);
|
||||
clone
|
||||
}
|
||||
|
||||
/// Insert values into backend trie.
|
||||
pub fn insert<T: IntoIterator<Item = (Option<ChildInfo>, StorageCollection)>>(
|
||||
&mut self,
|
||||
changes: T,
|
||||
state_version: StateVersion,
|
||||
) {
|
||||
let (top, child) = changes.into_iter().partition::<Vec<_>, _>(|v| v.0.is_none());
|
||||
let (root, transaction) = self.full_storage_root(
|
||||
top.iter().flat_map(|(_, v)| v).map(|(k, v)| (&k[..], v.as_deref())),
|
||||
child.iter().filter_map(|v| {
|
||||
v.0.as_ref().map(|c| (c, v.1.iter().map(|(k, v)| (&k[..], v.as_deref()))))
|
||||
}),
|
||||
state_version,
|
||||
);
|
||||
|
||||
self.apply_transaction(root, transaction);
|
||||
}
|
||||
|
||||
/// Merge trie nodes into this backend.
|
||||
pub fn update_backend(&self, root: H::Out, changes: PrefixedMemoryDB<H>) -> Self {
|
||||
let mut clone = self.backend_storage().clone();
|
||||
clone.consolidate(changes);
|
||||
TrieBackendBuilder::new(clone, root).build()
|
||||
}
|
||||
|
||||
/// Apply the given transaction to this backend and set the root to the given value.
|
||||
pub fn apply_transaction(&mut self, root: H::Out, transaction: PrefixedMemoryDB<H>) {
|
||||
let mut storage = core::mem::take(self).into_storage();
|
||||
|
||||
storage.consolidate(transaction);
|
||||
*self = TrieBackendBuilder::new(storage, root).build();
|
||||
}
|
||||
|
||||
/// Compare with another in-memory backend.
|
||||
pub fn eq(&self, other: &Self) -> bool {
|
||||
self.root() == other.root()
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: Hasher> Clone for TrieBackend<PrefixedMemoryDB<H>, H>
|
||||
where
|
||||
H::Out: Codec + Ord,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
TrieBackendBuilder::new(self.backend_storage().clone(), *self.root()).build()
|
||||
}
|
||||
}
|
||||
|
||||
impl<H> Default for TrieBackend<PrefixedMemoryDB<H>, H>
|
||||
where
|
||||
H: Hasher,
|
||||
H::Out: Codec + Ord,
|
||||
{
|
||||
fn default() -> Self {
|
||||
new_in_mem()
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: Hasher> From<(MapType<Option<ChildInfo>, BTreeMap<StorageKey, StorageValue>>, StateVersion)>
|
||||
for TrieBackend<PrefixedMemoryDB<H>, H>
|
||||
where
|
||||
H::Out: Codec + Ord,
|
||||
{
|
||||
fn from(
|
||||
(inner, state_version): (
|
||||
MapType<Option<ChildInfo>, BTreeMap<StorageKey, StorageValue>>,
|
||||
StateVersion,
|
||||
),
|
||||
) -> Self {
|
||||
let mut backend = new_in_mem();
|
||||
backend.insert(
|
||||
inner
|
||||
.into_iter()
|
||||
.map(|(k, m)| (k, m.into_iter().map(|(k, v)| (k, Some(v))).collect())),
|
||||
state_version,
|
||||
);
|
||||
backend
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<H: Hasher> From<(Storage, StateVersion)> for TrieBackend<PrefixedMemoryDB<H>, H>
|
||||
where
|
||||
H::Out: Codec + Ord,
|
||||
{
|
||||
fn from((inners, state_version): (Storage, StateVersion)) -> Self {
|
||||
let mut inner: MapType<Option<ChildInfo>, BTreeMap<StorageKey, StorageValue>> = inners
|
||||
.children_default
|
||||
.into_values()
|
||||
.map(|c| (Some(c.child_info), c.data))
|
||||
.collect();
|
||||
inner.insert(None, inners.top);
|
||||
(inner, state_version).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: Hasher> From<(BTreeMap<StorageKey, StorageValue>, StateVersion)>
|
||||
for TrieBackend<PrefixedMemoryDB<H>, H>
|
||||
where
|
||||
H::Out: Codec + Ord,
|
||||
{
|
||||
fn from((inner, state_version): (BTreeMap<StorageKey, StorageValue>, StateVersion)) -> Self {
|
||||
let mut expanded = MapType::new();
|
||||
expanded.insert(None, inner);
|
||||
(expanded, state_version).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: Hasher> From<(Vec<(Option<ChildInfo>, StorageCollection)>, StateVersion)>
|
||||
for TrieBackend<PrefixedMemoryDB<H>, H>
|
||||
where
|
||||
H::Out: Codec + Ord,
|
||||
{
|
||||
fn from(
|
||||
(inner, state_version): (Vec<(Option<ChildInfo>, StorageCollection)>, StateVersion),
|
||||
) -> Self {
|
||||
let mut expanded: MapType<Option<ChildInfo>, BTreeMap<StorageKey, StorageValue>> =
|
||||
MapType::new();
|
||||
for (child_info, key_values) in inner {
|
||||
let entry = expanded.entry(child_info).or_default();
|
||||
for (key, value) in key_values {
|
||||
if let Some(value) = value {
|
||||
entry.insert(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
(expanded, state_version).into()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::backend::{AsTrieBackend, Backend};
|
||||
use pezsp_core::storage::StateVersion;
|
||||
use pezsp_runtime::traits::BlakeTwo256;
|
||||
|
||||
/// Assert in memory backend with only child trie keys works as trie backend.
|
||||
#[test]
|
||||
fn in_memory_with_child_trie_only() {
|
||||
let state_version = StateVersion::default();
|
||||
let storage = new_in_mem::<BlakeTwo256>();
|
||||
let child_info = ChildInfo::new_default(b"1");
|
||||
let child_info = &child_info;
|
||||
let storage = storage.update(
|
||||
vec![(Some(child_info.clone()), vec![(b"2".to_vec(), Some(b"3".to_vec()))])],
|
||||
state_version,
|
||||
);
|
||||
let trie_backend = storage.as_trie_backend();
|
||||
assert_eq!(trie_backend.child_storage(child_info, b"2").unwrap(), Some(b"3".to_vec()));
|
||||
let storage_key = child_info.prefixed_storage_key();
|
||||
assert!(trie_backend.storage(storage_key.as_slice()).unwrap().is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_multiple_times_child_data_works() {
|
||||
let state_version = StateVersion::default();
|
||||
let mut storage = new_in_mem::<BlakeTwo256>();
|
||||
let child_info = ChildInfo::new_default(b"1");
|
||||
|
||||
storage.insert(
|
||||
vec![(Some(child_info.clone()), vec![(b"2".to_vec(), Some(b"3".to_vec()))])],
|
||||
state_version,
|
||||
);
|
||||
storage.insert(
|
||||
vec![(Some(child_info.clone()), vec![(b"1".to_vec(), Some(b"3".to_vec()))])],
|
||||
state_version,
|
||||
);
|
||||
|
||||
assert_eq!(storage.child_storage(&child_info, &b"2"[..]), Ok(Some(b"3".to_vec())));
|
||||
assert_eq!(storage.child_storage(&child_info, &b"1"[..]), Ok(Some(b"3".to_vec())));
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,133 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// 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.
|
||||
|
||||
//! Overlayed changes for offchain indexing.
|
||||
|
||||
use super::changeset::OverlayedMap;
|
||||
use alloc::vec::Vec;
|
||||
use pezsp_core::offchain::OffchainOverlayedChange;
|
||||
|
||||
/// In-memory storage for offchain workers recoding changes for the actual offchain storage
|
||||
/// implementation.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct OffchainOverlayedChanges(OverlayedMap<(Vec<u8>, Vec<u8>), OffchainOverlayedChange>);
|
||||
|
||||
/// Item for iterating over offchain changes.
|
||||
///
|
||||
/// First element i a tuple of `(prefix, key)`, second element ist the actual change
|
||||
/// (remove or set value).
|
||||
type OffchainOverlayedChangesItem<'i> = (&'i (Vec<u8>, Vec<u8>), &'i OffchainOverlayedChange);
|
||||
|
||||
/// Iterator over offchain changes, owned memory version.
|
||||
type OffchainOverlayedChangesItemOwned = ((Vec<u8>, Vec<u8>), OffchainOverlayedChange);
|
||||
|
||||
impl OffchainOverlayedChanges {
|
||||
/// Consume the offchain storage and iterate over all key value pairs.
|
||||
pub fn into_iter(self) -> impl Iterator<Item = OffchainOverlayedChangesItemOwned> {
|
||||
self.0.into_changes().map(|kv| (kv.0, kv.1.into_value()))
|
||||
}
|
||||
|
||||
/// Iterate over all key value pairs by reference.
|
||||
pub fn iter(&mut self) -> impl Iterator<Item = OffchainOverlayedChangesItem<'_>> {
|
||||
self.0.changes().map(|kv| (kv.0, kv.1.value_ref()))
|
||||
}
|
||||
|
||||
/// Drain all elements of changeset.
|
||||
pub fn drain(&mut self) -> impl Iterator<Item = OffchainOverlayedChangesItemOwned> {
|
||||
core::mem::take(self).into_iter()
|
||||
}
|
||||
|
||||
/// Remove a key and its associated value from the offchain database.
|
||||
pub fn remove(&mut self, prefix: &[u8], key: &[u8]) {
|
||||
let _ = self.0.set_offchain(
|
||||
(prefix.to_vec(), key.to_vec()),
|
||||
OffchainOverlayedChange::Remove,
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
/// Set the value associated with a key under a prefix to the value provided.
|
||||
pub fn set(&mut self, prefix: &[u8], key: &[u8], value: &[u8]) {
|
||||
let _ = self.0.set_offchain(
|
||||
(prefix.to_vec(), key.to_vec()),
|
||||
OffchainOverlayedChange::SetValue(value.to_vec()),
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
/// Obtain a associated value to the given key in storage with prefix.
|
||||
pub fn get(&mut self, prefix: &[u8], key: &[u8]) -> Option<OffchainOverlayedChange> {
|
||||
let key = (prefix.to_vec(), key.to_vec());
|
||||
self.0.get(&key).map(|entry| entry.value_ref()).cloned()
|
||||
}
|
||||
|
||||
/// Reference to inner change set.
|
||||
pub fn overlay(&self) -> &OverlayedMap<(Vec<u8>, Vec<u8>), OffchainOverlayedChange> {
|
||||
&self.0
|
||||
}
|
||||
|
||||
/// Mutable reference to inner change set.
|
||||
pub fn overlay_mut(
|
||||
&mut self,
|
||||
) -> &mut OverlayedMap<(Vec<u8>, Vec<u8>), OffchainOverlayedChange> {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use pezsp_core::offchain::STORAGE_PREFIX;
|
||||
|
||||
#[test]
|
||||
fn test_drain() {
|
||||
let mut ooc = OffchainOverlayedChanges::default();
|
||||
ooc.set(STORAGE_PREFIX, b"kkk", b"vvv");
|
||||
let drained = ooc.drain().count();
|
||||
assert_eq!(drained, 1);
|
||||
let leftover = ooc.iter().count();
|
||||
assert_eq!(leftover, 0);
|
||||
|
||||
ooc.set(STORAGE_PREFIX, b"a", b"v");
|
||||
ooc.set(STORAGE_PREFIX, b"b", b"v");
|
||||
ooc.set(STORAGE_PREFIX, b"c", b"v");
|
||||
ooc.set(STORAGE_PREFIX, b"d", b"v");
|
||||
ooc.set(STORAGE_PREFIX, b"e", b"v");
|
||||
assert_eq!(ooc.iter().count(), 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_accumulated_set_remove_set() {
|
||||
let mut ooc = OffchainOverlayedChanges::default();
|
||||
ooc.set(STORAGE_PREFIX, b"ppp", b"qqq");
|
||||
ooc.remove(STORAGE_PREFIX, b"ppp");
|
||||
// keys are equiv, so it will overwrite the value and the overlay will contain
|
||||
// one item
|
||||
assert_eq!(ooc.iter().count(), 1);
|
||||
|
||||
ooc.set(STORAGE_PREFIX, b"ppp", b"rrr");
|
||||
let mut iter = ooc.into_iter();
|
||||
assert_eq!(
|
||||
iter.next(),
|
||||
Some((
|
||||
(STORAGE_PREFIX.to_vec(), b"ppp".to_vec()),
|
||||
OffchainOverlayedChange::SetValue(b"rrr".to_vec())
|
||||
))
|
||||
);
|
||||
assert_eq!(iter.next(), None);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// 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.
|
||||
|
||||
//! Read-only version of Externalities.
|
||||
|
||||
use crate::{Backend, StorageKey, StorageValue};
|
||||
use alloc::{boxed::Box, vec::Vec};
|
||||
use codec::Encode;
|
||||
use core::{
|
||||
any::{Any, TypeId},
|
||||
marker::PhantomData,
|
||||
};
|
||||
use hash_db::Hasher;
|
||||
use pezsp_core::{
|
||||
storage::{ChildInfo, StateVersion, TrackedStorageKey},
|
||||
traits::Externalities,
|
||||
};
|
||||
use pezsp_externalities::MultiRemovalResults;
|
||||
|
||||
/// Trait for inspecting state in any backend.
|
||||
///
|
||||
/// Implemented for any backend.
|
||||
pub trait InspectState<H: Hasher, B: Backend<H>> {
|
||||
/// Inspect state with a closure.
|
||||
///
|
||||
/// Self will be set as read-only externalities and inspection
|
||||
/// closure will be run against it.
|
||||
///
|
||||
/// Returns the result of the closure.
|
||||
fn inspect_state<F: FnOnce() -> R, R>(&self, f: F) -> R;
|
||||
}
|
||||
|
||||
impl<H: Hasher, B: Backend<H>> InspectState<H, B> for B
|
||||
where
|
||||
H::Out: Encode,
|
||||
{
|
||||
fn inspect_state<F: FnOnce() -> R, R>(&self, f: F) -> R {
|
||||
ReadOnlyExternalities::from(self).execute_with(f)
|
||||
}
|
||||
}
|
||||
|
||||
/// Simple read-only externalities for any backend.
|
||||
///
|
||||
/// To be used in test for state inspection. Will panic if something writes
|
||||
/// to the storage.
|
||||
#[derive(Debug)]
|
||||
pub struct ReadOnlyExternalities<'a, H: Hasher, B: 'a + Backend<H>> {
|
||||
backend: &'a B,
|
||||
_phantom: PhantomData<H>,
|
||||
}
|
||||
|
||||
impl<'a, H: Hasher, B: 'a + Backend<H>> From<&'a B> for ReadOnlyExternalities<'a, H, B> {
|
||||
fn from(backend: &'a B) -> Self {
|
||||
ReadOnlyExternalities { backend, _phantom: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, H: Hasher, B: 'a + Backend<H>> ReadOnlyExternalities<'a, H, B>
|
||||
where
|
||||
H::Out: Encode,
|
||||
{
|
||||
/// Execute the given closure while `self` is set as externalities.
|
||||
///
|
||||
/// Returns the result of the given closure.
|
||||
pub fn execute_with<R>(&mut self, f: impl FnOnce() -> R) -> R {
|
||||
pezsp_externalities::set_and_run_with_externalities(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, H: Hasher, B: 'a + Backend<H>> Externalities for ReadOnlyExternalities<'a, H, B>
|
||||
where
|
||||
H::Out: Encode,
|
||||
{
|
||||
fn set_offchain_storage(&mut self, _key: &[u8], _value: Option<&[u8]>) {
|
||||
panic!("Should not be used in read-only externalities!")
|
||||
}
|
||||
|
||||
fn storage(&mut self, key: &[u8]) -> Option<StorageValue> {
|
||||
self.backend
|
||||
.storage(key)
|
||||
.expect("Backed failed for storage in ReadOnlyExternalities")
|
||||
}
|
||||
|
||||
fn storage_hash(&mut self, key: &[u8]) -> Option<Vec<u8>> {
|
||||
self.backend
|
||||
.storage_hash(key)
|
||||
.expect("Backed failed for storage_hash in ReadOnlyExternalities")
|
||||
.map(|h| h.encode())
|
||||
}
|
||||
|
||||
fn child_storage(&mut self, child_info: &ChildInfo, key: &[u8]) -> Option<StorageValue> {
|
||||
self.backend
|
||||
.child_storage(child_info, key)
|
||||
.expect("Backed failed for child_storage in ReadOnlyExternalities")
|
||||
}
|
||||
|
||||
fn child_storage_hash(&mut self, child_info: &ChildInfo, key: &[u8]) -> Option<Vec<u8>> {
|
||||
self.backend
|
||||
.child_storage_hash(child_info, key)
|
||||
.expect("Backed failed for child_storage_hash in ReadOnlyExternalities")
|
||||
.map(|h| h.encode())
|
||||
}
|
||||
|
||||
fn next_storage_key(&mut self, key: &[u8]) -> Option<StorageKey> {
|
||||
self.backend
|
||||
.next_storage_key(key)
|
||||
.expect("Backed failed for next_storage_key in ReadOnlyExternalities")
|
||||
}
|
||||
|
||||
fn next_child_storage_key(&mut self, child_info: &ChildInfo, key: &[u8]) -> Option<StorageKey> {
|
||||
self.backend
|
||||
.next_child_storage_key(child_info, key)
|
||||
.expect("Backed failed for next_child_storage_key in ReadOnlyExternalities")
|
||||
}
|
||||
|
||||
fn place_storage(&mut self, _key: StorageKey, _maybe_value: Option<StorageValue>) {
|
||||
unimplemented!("place_storage not supported in ReadOnlyExternalities")
|
||||
}
|
||||
|
||||
fn place_child_storage(
|
||||
&mut self,
|
||||
_child_info: &ChildInfo,
|
||||
_key: StorageKey,
|
||||
_value: Option<StorageValue>,
|
||||
) {
|
||||
unimplemented!("place_child_storage not supported in ReadOnlyExternalities")
|
||||
}
|
||||
|
||||
fn kill_child_storage(
|
||||
&mut self,
|
||||
_child_info: &ChildInfo,
|
||||
_maybe_limit: Option<u32>,
|
||||
_maybe_cursor: Option<&[u8]>,
|
||||
) -> MultiRemovalResults {
|
||||
unimplemented!("kill_child_storage is not supported in ReadOnlyExternalities")
|
||||
}
|
||||
|
||||
fn clear_prefix(
|
||||
&mut self,
|
||||
_prefix: &[u8],
|
||||
_maybe_limit: Option<u32>,
|
||||
_maybe_cursor: Option<&[u8]>,
|
||||
) -> MultiRemovalResults {
|
||||
unimplemented!("clear_prefix is not supported in ReadOnlyExternalities")
|
||||
}
|
||||
|
||||
fn clear_child_prefix(
|
||||
&mut self,
|
||||
_child_info: &ChildInfo,
|
||||
_prefix: &[u8],
|
||||
_maybe_limit: Option<u32>,
|
||||
_maybe_cursor: Option<&[u8]>,
|
||||
) -> MultiRemovalResults {
|
||||
unimplemented!("clear_child_prefix is not supported in ReadOnlyExternalities")
|
||||
}
|
||||
|
||||
fn storage_append(&mut self, _key: Vec<u8>, _value: Vec<u8>) {
|
||||
unimplemented!("storage_append is not supported in ReadOnlyExternalities")
|
||||
}
|
||||
|
||||
fn storage_root(&mut self, _state_version: StateVersion) -> Vec<u8> {
|
||||
unimplemented!("storage_root is not supported in ReadOnlyExternalities")
|
||||
}
|
||||
|
||||
fn child_storage_root(
|
||||
&mut self,
|
||||
_child_info: &ChildInfo,
|
||||
_state_version: StateVersion,
|
||||
) -> Vec<u8> {
|
||||
unimplemented!("child_storage_root is not supported in ReadOnlyExternalities")
|
||||
}
|
||||
|
||||
fn storage_start_transaction(&mut self) {
|
||||
unimplemented!("Transactions are not supported by ReadOnlyExternalities");
|
||||
}
|
||||
|
||||
fn storage_rollback_transaction(&mut self) -> Result<(), ()> {
|
||||
unimplemented!("Transactions are not supported by ReadOnlyExternalities");
|
||||
}
|
||||
|
||||
fn storage_commit_transaction(&mut self) -> Result<(), ()> {
|
||||
unimplemented!("Transactions are not supported by ReadOnlyExternalities");
|
||||
}
|
||||
|
||||
fn wipe(&mut self) {}
|
||||
|
||||
fn commit(&mut self) {}
|
||||
|
||||
fn read_write_count(&self) -> (u32, u32, u32, u32) {
|
||||
unimplemented!("read_write_count is not supported in ReadOnlyExternalities")
|
||||
}
|
||||
|
||||
fn reset_read_write_count(&mut self) {
|
||||
unimplemented!("reset_read_write_count is not supported in ReadOnlyExternalities")
|
||||
}
|
||||
|
||||
fn get_whitelist(&self) -> Vec<TrackedStorageKey> {
|
||||
unimplemented!("get_whitelist is not supported in ReadOnlyExternalities")
|
||||
}
|
||||
|
||||
fn set_whitelist(&mut self, _: Vec<TrackedStorageKey>) {
|
||||
unimplemented!("set_whitelist is not supported in ReadOnlyExternalities")
|
||||
}
|
||||
|
||||
fn get_read_and_written_keys(&self) -> Vec<(Vec<u8>, u32, u32, bool)> {
|
||||
unimplemented!("get_read_and_written_keys is not supported in ReadOnlyExternalities")
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, H: Hasher, B: 'a + Backend<H>> pezsp_externalities::ExtensionStore
|
||||
for ReadOnlyExternalities<'a, H, B>
|
||||
{
|
||||
fn extension_by_type_id(&mut self, _type_id: TypeId) -> Option<&mut dyn Any> {
|
||||
unimplemented!("extension_by_type_id is not supported in ReadOnlyExternalities")
|
||||
}
|
||||
|
||||
fn register_extension_with_type_id(
|
||||
&mut self,
|
||||
_type_id: TypeId,
|
||||
_extension: Box<dyn pezsp_externalities::Extension>,
|
||||
) -> Result<(), pezsp_externalities::Error> {
|
||||
unimplemented!("register_extension_with_type_id is not supported in ReadOnlyExternalities")
|
||||
}
|
||||
|
||||
fn deregister_extension_by_type_id(
|
||||
&mut self,
|
||||
_type_id: TypeId,
|
||||
) -> Result<(), pezsp_externalities::Error> {
|
||||
unimplemented!("deregister_extension_by_type_id is not supported in ReadOnlyExternalities")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// 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.
|
||||
|
||||
//! Usage statistics for state db
|
||||
|
||||
use core::cell::RefCell;
|
||||
#[cfg(feature = "std")]
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
/// Measured count of operations and total bytes.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct UsageUnit {
|
||||
/// Number of operations.
|
||||
pub ops: u64,
|
||||
/// Number of bytes.
|
||||
pub bytes: u64,
|
||||
}
|
||||
|
||||
/// Usage statistics for state backend.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct UsageInfo {
|
||||
/// Read statistics (total).
|
||||
pub reads: UsageUnit,
|
||||
/// Write statistics (total).
|
||||
pub writes: UsageUnit,
|
||||
/// Write trie nodes statistics.
|
||||
pub nodes_writes: UsageUnit,
|
||||
/// Write into cached state machine
|
||||
/// change overlay.
|
||||
pub overlay_writes: UsageUnit,
|
||||
/// Removed trie nodes statistics.
|
||||
pub removed_nodes: UsageUnit,
|
||||
/// Cache read statistics.
|
||||
pub cache_reads: UsageUnit,
|
||||
/// Modified value read statistics.
|
||||
pub modified_reads: UsageUnit,
|
||||
/// Memory used.
|
||||
pub memory: usize,
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
/// Moment at which current statistics has been started being collected.
|
||||
pub started: Instant,
|
||||
#[cfg(feature = "std")]
|
||||
/// Timespan of the statistics.
|
||||
pub span: Duration,
|
||||
}
|
||||
|
||||
/// Accumulated usage statistics specific to state machine
|
||||
/// crate.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct StateMachineStats {
|
||||
/// Number of read query from runtime
|
||||
/// that hit a modified value (in state
|
||||
/// machine overlay).
|
||||
pub reads_modified: RefCell<u64>,
|
||||
/// Size in byte of read queries that
|
||||
/// hit a modified value.
|
||||
pub bytes_read_modified: RefCell<u64>,
|
||||
/// Number of time a write operation
|
||||
/// occurs into the state machine overlay.
|
||||
pub writes_overlay: RefCell<u64>,
|
||||
/// Size in bytes of the writes overlay
|
||||
/// operation.
|
||||
pub bytes_writes_overlay: RefCell<u64>,
|
||||
}
|
||||
|
||||
impl StateMachineStats {
|
||||
/// Accumulates some registered stats.
|
||||
pub fn add(&self, other: &StateMachineStats) {
|
||||
*self.reads_modified.borrow_mut() += *other.reads_modified.borrow();
|
||||
*self.bytes_read_modified.borrow_mut() += *other.bytes_read_modified.borrow();
|
||||
*self.writes_overlay.borrow_mut() += *other.writes_overlay.borrow();
|
||||
*self.bytes_writes_overlay.borrow_mut() += *other.bytes_writes_overlay.borrow();
|
||||
}
|
||||
}
|
||||
|
||||
impl UsageInfo {
|
||||
/// Empty statistics.
|
||||
///
|
||||
/// Means no data was collected.
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
reads: UsageUnit::default(),
|
||||
writes: UsageUnit::default(),
|
||||
overlay_writes: UsageUnit::default(),
|
||||
nodes_writes: UsageUnit::default(),
|
||||
removed_nodes: UsageUnit::default(),
|
||||
cache_reads: UsageUnit::default(),
|
||||
modified_reads: UsageUnit::default(),
|
||||
memory: 0,
|
||||
#[cfg(feature = "std")]
|
||||
started: Instant::now(),
|
||||
#[cfg(feature = "std")]
|
||||
span: Default::default(),
|
||||
}
|
||||
}
|
||||
/// Add collected state machine to this state.
|
||||
pub fn include_state_machine_states(&mut self, count: &StateMachineStats) {
|
||||
self.modified_reads.ops += *count.reads_modified.borrow();
|
||||
self.modified_reads.bytes += *count.bytes_read_modified.borrow();
|
||||
self.overlay_writes.ops += *count.writes_overlay.borrow();
|
||||
self.overlay_writes.bytes += *count.bytes_writes_overlay.borrow();
|
||||
}
|
||||
}
|
||||
|
||||
impl StateMachineStats {
|
||||
/// Tally one read modified operation, of some length.
|
||||
pub fn tally_read_modified(&self, data_bytes: u64) {
|
||||
*self.reads_modified.borrow_mut() += 1;
|
||||
*self.bytes_read_modified.borrow_mut() += data_bytes;
|
||||
}
|
||||
/// Tally one write overlay operation, of some length.
|
||||
pub fn tally_write_overlay(&self, data_bytes: u64) {
|
||||
*self.writes_overlay.borrow_mut() += 1;
|
||||
*self.bytes_writes_overlay.borrow_mut() += data_bytes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,540 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// 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.
|
||||
|
||||
//! Test implementation for Externalities.
|
||||
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
panic::{AssertUnwindSafe, UnwindSafe},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
backend::Backend, ext::Ext, InMemoryBackend, OverlayedChanges, StorageKey, StorageValue,
|
||||
TrieBackendBuilder,
|
||||
};
|
||||
|
||||
use hash_db::{HashDB, Hasher};
|
||||
use pezsp_core::{
|
||||
offchain::testing::TestPersistentOffchainDB,
|
||||
storage::{
|
||||
well_known_keys::{is_child_storage_key, CODE},
|
||||
StateVersion, Storage,
|
||||
},
|
||||
};
|
||||
use pezsp_externalities::{Extension, ExtensionStore, Extensions};
|
||||
use pezsp_trie::{recorder::Recorder, PrefixedMemoryDB, StorageProof};
|
||||
|
||||
/// Simple HashMap-based Externalities impl.
|
||||
pub struct TestExternalities<H>
|
||||
where
|
||||
H: Hasher + 'static,
|
||||
H::Out: codec::Codec + Ord,
|
||||
{
|
||||
/// The overlay changed storage.
|
||||
overlay: OverlayedChanges<H>,
|
||||
offchain_db: TestPersistentOffchainDB,
|
||||
/// Storage backend.
|
||||
pub backend: InMemoryBackend<H>,
|
||||
/// Extensions.
|
||||
pub extensions: Extensions,
|
||||
/// State version to use during tests.
|
||||
pub state_version: StateVersion,
|
||||
}
|
||||
|
||||
impl<H> TestExternalities<H>
|
||||
where
|
||||
H: Hasher + 'static,
|
||||
H::Out: Ord + 'static + codec::Codec,
|
||||
{
|
||||
/// Get externalities implementation.
|
||||
pub fn ext(&mut self) -> Ext<'_, H, InMemoryBackend<H>> {
|
||||
Ext::new(&mut self.overlay, &self.backend, Some(&mut self.extensions))
|
||||
}
|
||||
|
||||
/// Create a new instance of `TestExternalities` with storage.
|
||||
pub fn new(storage: Storage) -> Self {
|
||||
Self::new_with_code_and_state(&[], storage, Default::default())
|
||||
}
|
||||
|
||||
/// Create a new instance of `TestExternalities` with storage for a given state version.
|
||||
pub fn new_with_state_version(storage: Storage, state_version: StateVersion) -> Self {
|
||||
Self::new_with_code_and_state(&[], storage, state_version)
|
||||
}
|
||||
|
||||
/// New empty test externalities.
|
||||
pub fn new_empty() -> Self {
|
||||
Self::new_with_code_and_state(&[], Storage::default(), Default::default())
|
||||
}
|
||||
|
||||
/// Create a new instance of `TestExternalities` with code and storage.
|
||||
pub fn new_with_code(code: &[u8], storage: Storage) -> Self {
|
||||
Self::new_with_code_and_state(code, storage, Default::default())
|
||||
}
|
||||
|
||||
/// Create a new instance of `TestExternalities` with code and storage for a given state
|
||||
/// version.
|
||||
pub fn new_with_code_and_state(
|
||||
code: &[u8],
|
||||
mut storage: Storage,
|
||||
state_version: StateVersion,
|
||||
) -> Self {
|
||||
assert!(storage.top.keys().all(|key| !is_child_storage_key(key)));
|
||||
|
||||
storage.top.insert(CODE.to_vec(), code.to_vec());
|
||||
|
||||
let offchain_db = TestPersistentOffchainDB::new();
|
||||
|
||||
let backend = (storage, state_version).into();
|
||||
|
||||
TestExternalities {
|
||||
overlay: OverlayedChanges::default(),
|
||||
offchain_db,
|
||||
extensions: Default::default(),
|
||||
backend,
|
||||
state_version,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the overlayed changes.
|
||||
pub fn overlayed_changes(&self) -> &OverlayedChanges<H> {
|
||||
&self.overlay
|
||||
}
|
||||
|
||||
/// Move offchain changes from overlay to the persistent store.
|
||||
pub fn persist_offchain_overlay(&mut self) {
|
||||
self.offchain_db.apply_offchain_changes(self.overlay.offchain_drain_committed());
|
||||
}
|
||||
|
||||
/// A shared reference type around the offchain worker storage.
|
||||
pub fn offchain_db(&self) -> TestPersistentOffchainDB {
|
||||
self.offchain_db.clone()
|
||||
}
|
||||
|
||||
/// Batch insert key/values into backend
|
||||
pub fn batch_insert<I>(&mut self, kvs: I)
|
||||
where
|
||||
I: IntoIterator<Item = (StorageKey, StorageValue)>,
|
||||
{
|
||||
self.backend.insert(
|
||||
Some((None, kvs.into_iter().map(|(k, v)| (k, Some(v))).collect())),
|
||||
self.state_version,
|
||||
);
|
||||
}
|
||||
|
||||
/// Insert key/value into backend
|
||||
pub fn insert(&mut self, k: StorageKey, v: StorageValue) {
|
||||
self.backend.insert(vec![(None, vec![(k, Some(v))])], self.state_version);
|
||||
}
|
||||
|
||||
/// Insert key/value into backend.
|
||||
///
|
||||
/// This only supports inserting keys in child tries.
|
||||
pub fn insert_child(&mut self, c: pezsp_core::storage::ChildInfo, k: StorageKey, v: StorageValue) {
|
||||
self.backend.insert(vec![(Some(c), vec![(k, Some(v))])], self.state_version);
|
||||
}
|
||||
|
||||
/// Registers the given extension for this instance.
|
||||
pub fn register_extension<E: Any + Extension>(&mut self, ext: E) {
|
||||
self.extensions.register(ext);
|
||||
}
|
||||
|
||||
/// Sets raw storage key/values and a root.
|
||||
///
|
||||
/// This can be used as a fast way to restore the storage state from a backup because the trie
|
||||
/// does not need to be computed.
|
||||
pub fn from_raw_snapshot(
|
||||
raw_storage: Vec<(Vec<u8>, (Vec<u8>, i32))>,
|
||||
storage_root: H::Out,
|
||||
state_version: StateVersion,
|
||||
) -> Self {
|
||||
let mut backend = PrefixedMemoryDB::default();
|
||||
|
||||
for (key, (v, ref_count)) in raw_storage {
|
||||
let mut hash = H::Out::default();
|
||||
let hash_len = hash.as_ref().len();
|
||||
|
||||
if key.len() < hash_len {
|
||||
log::warn!("Invalid key in `from_raw_snapshot`: {key:?}");
|
||||
continue;
|
||||
}
|
||||
|
||||
hash.as_mut().copy_from_slice(&key[(key.len() - hash_len)..]);
|
||||
|
||||
// Each time .emplace is called the internal MemoryDb ref count increments.
|
||||
// Repeatedly call emplace to initialise the ref count to the correct value.
|
||||
for _ in 0..ref_count {
|
||||
backend.emplace(hash, (&key[..(key.len() - hash_len)], None), v.clone());
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
backend: TrieBackendBuilder::new(backend, storage_root).build(),
|
||||
overlay: Default::default(),
|
||||
offchain_db: Default::default(),
|
||||
extensions: Default::default(),
|
||||
state_version,
|
||||
}
|
||||
}
|
||||
|
||||
/// Drains the underlying raw storage key/values and returns the root hash.
|
||||
///
|
||||
/// Useful for backing up the storage in a format that can be quickly re-loaded.
|
||||
pub fn into_raw_snapshot(mut self) -> (Vec<(Vec<u8>, (Vec<u8>, i32))>, H::Out) {
|
||||
let raw_key_values = self
|
||||
.backend
|
||||
.backend_storage_mut()
|
||||
.drain()
|
||||
.into_iter()
|
||||
.filter(|(_, (_, r))| *r > 0)
|
||||
.collect::<Vec<(Vec<u8>, (Vec<u8>, i32))>>();
|
||||
|
||||
(raw_key_values, *self.backend.root())
|
||||
}
|
||||
|
||||
/// Return a new backend with all pending changes.
|
||||
///
|
||||
/// In contrast to [`commit_all`](Self::commit_all) this will not panic if there are open
|
||||
/// transactions.
|
||||
pub fn as_backend(&mut self) -> InMemoryBackend<H> {
|
||||
let top: Vec<_> = self
|
||||
.overlay
|
||||
.changes_mut()
|
||||
.map(|(k, v)| (k.clone(), v.value().cloned()))
|
||||
.collect();
|
||||
let mut transaction = vec![(None, top)];
|
||||
|
||||
for (child_changes, child_info) in self.overlay.children_mut() {
|
||||
transaction.push((
|
||||
Some(child_info.clone()),
|
||||
child_changes.map(|(k, v)| (k.clone(), v.value().cloned())).collect(),
|
||||
))
|
||||
}
|
||||
|
||||
self.backend.update(transaction, self.state_version)
|
||||
}
|
||||
|
||||
/// Commit all pending changes to the underlying backend.
|
||||
///
|
||||
/// # Panic
|
||||
///
|
||||
/// This will panic if there are still open transactions.
|
||||
pub fn commit_all(&mut self) -> Result<(), String> {
|
||||
let changes = self.overlay.drain_storage_changes(&self.backend, self.state_version)?;
|
||||
|
||||
self.backend
|
||||
.apply_transaction(changes.transaction_storage_root, changes.transaction);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Execute the given closure while `self` is set as externalities.
|
||||
///
|
||||
/// Returns the result of the given closure.
|
||||
pub fn execute_with<R>(&mut self, execute: impl FnOnce() -> R) -> R {
|
||||
let mut ext = self.ext();
|
||||
pezsp_externalities::set_and_run_with_externalities(&mut ext, execute)
|
||||
}
|
||||
|
||||
/// Execute the given closure while `self`, with `proving_backend` as backend, is set as
|
||||
/// externalities.
|
||||
///
|
||||
/// This implementation will wipe the proof recorded in between calls. Consecutive calls will
|
||||
/// get their own proof from scratch.
|
||||
pub fn execute_and_prove<R>(&mut self, execute: impl FnOnce() -> R) -> (R, StorageProof) {
|
||||
let proving_backend = TrieBackendBuilder::wrap(&self.backend)
|
||||
.with_recorder(Default::default())
|
||||
.build();
|
||||
let mut proving_ext =
|
||||
Ext::new(&mut self.overlay, &proving_backend, Some(&mut self.extensions));
|
||||
|
||||
let outcome = pezsp_externalities::set_and_run_with_externalities(&mut proving_ext, execute);
|
||||
let proof = proving_backend.extract_proof().expect("Failed to extract storage proof");
|
||||
|
||||
(outcome, proof)
|
||||
}
|
||||
|
||||
/// Execute the given closure while `self` set as externalities and the given `proof_recorder`
|
||||
/// enabled.
|
||||
pub fn execute_with_recorder<R>(
|
||||
&mut self,
|
||||
proof_recorder: Recorder<H>,
|
||||
execute: impl FnOnce() -> R,
|
||||
) -> R {
|
||||
let proving_backend =
|
||||
TrieBackendBuilder::wrap(&self.backend).with_recorder(proof_recorder).build();
|
||||
let mut proving_ext =
|
||||
Ext::new(&mut self.overlay, &proving_backend, Some(&mut self.extensions));
|
||||
|
||||
pezsp_externalities::set_and_run_with_externalities(&mut proving_ext, execute)
|
||||
}
|
||||
|
||||
/// Execute the given closure while `self` is set as externalities.
|
||||
///
|
||||
/// Returns the result of the given closure, if no panics occurred.
|
||||
/// Otherwise, returns `Err`.
|
||||
pub fn execute_with_safe<R>(
|
||||
&mut self,
|
||||
f: impl FnOnce() -> R + UnwindSafe,
|
||||
) -> Result<R, String> {
|
||||
let mut ext = AssertUnwindSafe(self.ext());
|
||||
std::panic::catch_unwind(move || {
|
||||
pezsp_externalities::set_and_run_with_externalities(&mut *ext, f)
|
||||
})
|
||||
.map_err(|e| format!("Closure panicked: {:?}", e))
|
||||
}
|
||||
|
||||
/// Resets the overlay to its default state.
|
||||
pub fn reset_overlay(&mut self) {
|
||||
self.overlay = Default::default();
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: Hasher> std::fmt::Debug for TestExternalities<H>
|
||||
where
|
||||
H::Out: Ord + codec::Codec,
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
let pairs: Vec<_> = self
|
||||
.backend
|
||||
.pairs(Default::default())
|
||||
.expect("creating an iterator over all of the pairs doesn't fail in tests")
|
||||
.collect();
|
||||
write!(f, "overlay: {:?}\nbackend: {:?}", self.overlay, pairs)
|
||||
}
|
||||
}
|
||||
|
||||
impl<H> TestExternalities<H>
|
||||
where
|
||||
H: Hasher,
|
||||
H::Out: Ord + 'static + codec::Codec,
|
||||
{
|
||||
/// This doesn't test if they are in the same state, only if they contains the
|
||||
/// same data at this state
|
||||
pub fn eq(&mut self, other: &mut TestExternalities<H>) -> bool {
|
||||
self.as_backend().eq(&other.as_backend())
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: Hasher> Default for TestExternalities<H>
|
||||
where
|
||||
H::Out: Ord + 'static + codec::Codec,
|
||||
{
|
||||
fn default() -> Self {
|
||||
// default to default version.
|
||||
Self::new_with_state_version(Storage::default(), Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: Hasher> From<Storage> for TestExternalities<H>
|
||||
where
|
||||
H::Out: Ord + 'static + codec::Codec,
|
||||
{
|
||||
fn from(storage: Storage) -> Self {
|
||||
Self::new_with_state_version(storage, Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: Hasher> From<(Storage, StateVersion)> for TestExternalities<H>
|
||||
where
|
||||
H::Out: Ord + 'static + codec::Codec,
|
||||
{
|
||||
fn from((storage, state_version): (Storage, StateVersion)) -> Self {
|
||||
Self::new_with_state_version(storage, state_version)
|
||||
}
|
||||
}
|
||||
|
||||
impl<H> pezsp_externalities::ExtensionStore for TestExternalities<H>
|
||||
where
|
||||
H: Hasher,
|
||||
H::Out: Ord + codec::Codec,
|
||||
{
|
||||
fn extension_by_type_id(&mut self, type_id: TypeId) -> Option<&mut dyn Any> {
|
||||
self.extensions.get_mut(type_id)
|
||||
}
|
||||
|
||||
fn register_extension_with_type_id(
|
||||
&mut self,
|
||||
type_id: TypeId,
|
||||
extension: Box<dyn Extension>,
|
||||
) -> Result<(), pezsp_externalities::Error> {
|
||||
self.extensions.register_with_type_id(type_id, extension)
|
||||
}
|
||||
|
||||
fn deregister_extension_by_type_id(
|
||||
&mut self,
|
||||
type_id: TypeId,
|
||||
) -> Result<(), pezsp_externalities::Error> {
|
||||
if self.extensions.deregister(type_id) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(pezsp_externalities::Error::ExtensionIsNotRegistered(type_id))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<H> pezsp_externalities::ExternalitiesExt for TestExternalities<H>
|
||||
where
|
||||
H: Hasher,
|
||||
H::Out: Ord + codec::Codec,
|
||||
{
|
||||
fn extension<T: Any + Extension>(&mut self) -> Option<&mut T> {
|
||||
self.extension_by_type_id(TypeId::of::<T>()).and_then(<dyn Any>::downcast_mut)
|
||||
}
|
||||
|
||||
fn register_extension<T: Extension>(&mut self, ext: T) -> Result<(), pezsp_externalities::Error> {
|
||||
self.register_extension_with_type_id(TypeId::of::<T>(), Box::new(ext))
|
||||
}
|
||||
|
||||
fn deregister_extension<T: Extension>(&mut self) -> Result<(), pezsp_externalities::Error> {
|
||||
self.deregister_extension_by_type_id(TypeId::of::<T>())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use pezsp_core::{storage::ChildInfo, traits::Externalities, H256};
|
||||
use pezsp_runtime::traits::BlakeTwo256;
|
||||
|
||||
#[test]
|
||||
fn commit_should_work() {
|
||||
let storage = Storage::default(); // avoid adding the trie threshold.
|
||||
let mut ext = TestExternalities::<BlakeTwo256>::from((storage, Default::default()));
|
||||
let mut ext = ext.ext();
|
||||
ext.set_storage(b"doe".to_vec(), b"reindeer".to_vec());
|
||||
ext.set_storage(b"dog".to_vec(), b"puppy".to_vec());
|
||||
ext.set_storage(b"dogglesworth".to_vec(), b"cat".to_vec());
|
||||
let root = array_bytes::hex_n_into_unchecked::<_, H256, 32>(
|
||||
"ed4d8c799d996add422395a6abd7545491d40bd838d738afafa1b8a4de625489",
|
||||
);
|
||||
assert_eq!(H256::from_slice(ext.storage_root(Default::default()).as_slice()), root);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn raw_storage_drain_and_restore() {
|
||||
// Create a TestExternalities with some data in it.
|
||||
let mut original_ext =
|
||||
TestExternalities::<BlakeTwo256>::from((Default::default(), Default::default()));
|
||||
original_ext.insert(b"doe".to_vec(), b"reindeer".to_vec());
|
||||
original_ext.insert(b"dog".to_vec(), b"puppy".to_vec());
|
||||
original_ext.insert(b"dogglesworth".to_vec(), b"cat".to_vec());
|
||||
let child_info = ChildInfo::new_default(&b"test_child"[..]);
|
||||
original_ext.insert_child(child_info.clone(), b"cattytown".to_vec(), b"is_dark".to_vec());
|
||||
original_ext.insert_child(child_info.clone(), b"doggytown".to_vec(), b"is_sunny".to_vec());
|
||||
|
||||
// Apply the backend to itself again to increase the ref count of all nodes.
|
||||
original_ext.backend.apply_transaction(
|
||||
*original_ext.backend.root(),
|
||||
original_ext.backend.clone().into_storage(),
|
||||
);
|
||||
|
||||
// Ensure all have the correct ref count
|
||||
assert!(original_ext.backend.backend_storage().keys().values().all(|r| *r == 2));
|
||||
|
||||
// Drain the raw storage and root.
|
||||
let root = *original_ext.backend.root();
|
||||
let (raw_storage, storage_root) = original_ext.into_raw_snapshot();
|
||||
|
||||
// Load the raw storage and root into a new TestExternalities.
|
||||
let recovered_ext = TestExternalities::<BlakeTwo256>::from_raw_snapshot(
|
||||
raw_storage,
|
||||
storage_root,
|
||||
Default::default(),
|
||||
);
|
||||
|
||||
// Check the storage root is the same as the original
|
||||
assert_eq!(root, *recovered_ext.backend.root());
|
||||
|
||||
// Check the original storage key/values were recovered correctly
|
||||
assert_eq!(recovered_ext.backend.storage(b"doe").unwrap(), Some(b"reindeer".to_vec()));
|
||||
assert_eq!(recovered_ext.backend.storage(b"dog").unwrap(), Some(b"puppy".to_vec()));
|
||||
assert_eq!(recovered_ext.backend.storage(b"dogglesworth").unwrap(), Some(b"cat".to_vec()));
|
||||
|
||||
// Check the original child storage key/values were recovered correctly
|
||||
assert_eq!(
|
||||
recovered_ext.backend.child_storage(&child_info, b"cattytown").unwrap(),
|
||||
Some(b"is_dark".to_vec())
|
||||
);
|
||||
assert_eq!(
|
||||
recovered_ext.backend.child_storage(&child_info, b"doggytown").unwrap(),
|
||||
Some(b"is_sunny".to_vec())
|
||||
);
|
||||
|
||||
// Ensure all have the correct ref count after importing
|
||||
assert!(recovered_ext.backend.backend_storage().keys().values().all(|r| *r == 2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_and_retrieve_code() {
|
||||
let mut ext = TestExternalities::<BlakeTwo256>::default();
|
||||
let mut ext = ext.ext();
|
||||
|
||||
let code = vec![1, 2, 3];
|
||||
ext.set_storage(CODE.to_vec(), code.clone());
|
||||
|
||||
assert_eq!(&ext.storage(CODE).unwrap(), &code);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_send() {
|
||||
fn assert_send<T: Send>() {}
|
||||
assert_send::<TestExternalities<BlakeTwo256>>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn commit_all_and_kill_child_storage() {
|
||||
let mut ext = TestExternalities::<BlakeTwo256>::default();
|
||||
let child_info = ChildInfo::new_default(&b"test_child"[..]);
|
||||
|
||||
{
|
||||
let mut ext = ext.ext();
|
||||
ext.place_child_storage(&child_info, b"doe".to_vec(), Some(b"reindeer".to_vec()));
|
||||
ext.place_child_storage(&child_info, b"dog".to_vec(), Some(b"puppy".to_vec()));
|
||||
ext.place_child_storage(&child_info, b"dog2".to_vec(), Some(b"puppy2".to_vec()));
|
||||
}
|
||||
|
||||
ext.commit_all().unwrap();
|
||||
|
||||
{
|
||||
let mut ext = ext.ext();
|
||||
|
||||
assert!(
|
||||
ext.kill_child_storage(&child_info, Some(2), None).maybe_cursor.is_some(),
|
||||
"Should not delete all keys"
|
||||
);
|
||||
|
||||
assert!(ext.child_storage(&child_info, &b"doe"[..]).is_none());
|
||||
assert!(ext.child_storage(&child_info, &b"dog"[..]).is_none());
|
||||
assert!(ext.child_storage(&child_info, &b"dog2"[..]).is_some());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn as_backend_generates_same_backend_as_commit_all() {
|
||||
let mut ext = TestExternalities::<BlakeTwo256>::default();
|
||||
{
|
||||
let mut ext = ext.ext();
|
||||
ext.set_storage(b"doe".to_vec(), b"reindeer".to_vec());
|
||||
ext.set_storage(b"dog".to_vec(), b"puppy".to_vec());
|
||||
ext.set_storage(b"dogglesworth".to_vec(), b"cat".to_vec());
|
||||
}
|
||||
|
||||
let backend = ext.as_backend();
|
||||
|
||||
ext.commit_all().unwrap();
|
||||
assert!(ext.backend.eq(&backend), "Both backend should be equal.");
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,942 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// 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.
|
||||
|
||||
//! Trie-based state machine backend essence used to read values
|
||||
//! from storage.
|
||||
|
||||
use crate::{
|
||||
backend::{IterArgs, StorageIterator},
|
||||
trie_backend::TrieCacheProvider,
|
||||
warn, StorageKey, StorageValue,
|
||||
};
|
||||
#[cfg(feature = "std")]
|
||||
use alloc::sync::Arc;
|
||||
use alloc::{boxed::Box, vec::Vec};
|
||||
use codec::Codec;
|
||||
use core::marker::PhantomData;
|
||||
use hash_db::{self, AsHashDB, HashDB, HashDBRef, Hasher, Prefix};
|
||||
#[cfg(feature = "std")]
|
||||
use parking_lot::RwLock;
|
||||
use pezsp_core::storage::{ChildInfo, ChildType, StateVersion};
|
||||
use pezsp_trie::{
|
||||
child_delta_trie_root, delta_trie_root, empty_child_trie_root,
|
||||
read_child_trie_first_descendant_value, read_child_trie_hash, read_child_trie_value,
|
||||
read_trie_first_descendant_value, read_trie_value,
|
||||
trie_types::{TrieDBBuilder, TrieError},
|
||||
DBValue, KeySpacedDB, MerkleValue, NodeCodec, PrefixedMemoryDB, RandomState, Trie, TrieCache,
|
||||
TrieDBRawIterator, TrieRecorder, TrieRecorderProvider,
|
||||
};
|
||||
#[cfg(feature = "std")]
|
||||
use std::collections::HashMap;
|
||||
// In this module, we only use layout for read operation and empty root,
|
||||
// where V1 and V0 are equivalent.
|
||||
use pezsp_trie::LayoutV1 as Layout;
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
macro_rules! format {
|
||||
( $message:expr, $( $arg:expr )* ) => {
|
||||
{
|
||||
$( let _ = &$arg; )*
|
||||
crate::DefaultError
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
type Result<V> = core::result::Result<V, crate::DefaultError>;
|
||||
|
||||
/// Patricia trie-based storage trait.
|
||||
pub trait Storage<H: Hasher>: Send + Sync {
|
||||
/// Get a trie node.
|
||||
fn get(&self, key: &H::Out, prefix: Prefix) -> Result<Option<DBValue>>;
|
||||
}
|
||||
|
||||
/// Local cache for child root.
|
||||
#[cfg(feature = "std")]
|
||||
pub(crate) struct Cache<H> {
|
||||
pub child_root: HashMap<Vec<u8>, Option<H>>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<H> Cache<H> {
|
||||
fn new() -> Self {
|
||||
Cache { child_root: HashMap::new() }
|
||||
}
|
||||
}
|
||||
|
||||
enum IterState {
|
||||
Pending,
|
||||
FinishedComplete,
|
||||
FinishedIncomplete,
|
||||
}
|
||||
|
||||
/// A raw iterator over the storage.
|
||||
pub struct RawIter<S, H, C, R>
|
||||
where
|
||||
H: Hasher,
|
||||
{
|
||||
stop_on_incomplete_database: bool,
|
||||
skip_if_first: Option<StorageKey>,
|
||||
root: H::Out,
|
||||
child_info: Option<ChildInfo>,
|
||||
trie_iter: TrieDBRawIterator<Layout<H>>,
|
||||
state: IterState,
|
||||
_phantom: PhantomData<(S, C, R)>,
|
||||
}
|
||||
|
||||
impl<S, H, C, R> RawIter<S, H, C, R>
|
||||
where
|
||||
H: Hasher,
|
||||
S: TrieBackendStorage<H>,
|
||||
H::Out: Codec + Ord,
|
||||
C: TrieCacheProvider<H> + Send + Sync,
|
||||
R: TrieRecorderProvider<H> + Send + Sync,
|
||||
{
|
||||
#[inline]
|
||||
fn prepare<RE>(
|
||||
&mut self,
|
||||
backend: &TrieBackendEssence<S, H, C, R>,
|
||||
callback: impl FnOnce(
|
||||
&pezsp_trie::TrieDB<Layout<H>>,
|
||||
&mut TrieDBRawIterator<Layout<H>>,
|
||||
) -> Option<core::result::Result<RE, Box<TrieError<<H as Hasher>::Out>>>>,
|
||||
) -> Option<Result<RE>> {
|
||||
if !matches!(self.state, IterState::Pending) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let result = backend.with_trie_db(self.root, self.child_info.as_ref(), |db| {
|
||||
callback(&db, &mut self.trie_iter)
|
||||
});
|
||||
match result {
|
||||
Some(Ok(key_value)) => Some(Ok(key_value)),
|
||||
None => {
|
||||
self.state = IterState::FinishedComplete;
|
||||
None
|
||||
},
|
||||
Some(Err(error)) => {
|
||||
if matches!(*error, TrieError::IncompleteDatabase(_)) &&
|
||||
self.stop_on_incomplete_database
|
||||
{
|
||||
self.state = IterState::FinishedIncomplete;
|
||||
None
|
||||
} else {
|
||||
Some(Err(format!("TrieDB iteration error: {}", error)))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, H, C, R> Default for RawIter<S, H, C, R>
|
||||
where
|
||||
H: Hasher,
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
stop_on_incomplete_database: false,
|
||||
skip_if_first: None,
|
||||
child_info: None,
|
||||
root: Default::default(),
|
||||
trie_iter: TrieDBRawIterator::empty(),
|
||||
state: IterState::FinishedComplete,
|
||||
_phantom: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, H, C, R> StorageIterator<H> for RawIter<S, H, C, R>
|
||||
where
|
||||
H: Hasher,
|
||||
S: TrieBackendStorage<H>,
|
||||
H::Out: Codec + Ord,
|
||||
C: TrieCacheProvider<H> + Send + Sync,
|
||||
R: TrieRecorderProvider<H> + Send + Sync,
|
||||
{
|
||||
type Backend = crate::TrieBackend<S, H, C, R>;
|
||||
type Error = crate::DefaultError;
|
||||
|
||||
#[inline]
|
||||
fn next_key(&mut self, backend: &Self::Backend) -> Option<Result<StorageKey>> {
|
||||
let skip_if_first = self.skip_if_first.take();
|
||||
self.prepare(&backend.essence, |trie, trie_iter| {
|
||||
let mut result = trie_iter.next_key(&trie);
|
||||
if let Some(skipped_key) = skip_if_first {
|
||||
if let Some(Ok(ref key)) = result {
|
||||
if *key == skipped_key {
|
||||
result = trie_iter.next_key(&trie);
|
||||
}
|
||||
}
|
||||
}
|
||||
result
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn next_pair(&mut self, backend: &Self::Backend) -> Option<Result<(StorageKey, StorageValue)>> {
|
||||
let skip_if_first = self.skip_if_first.take();
|
||||
self.prepare(&backend.essence, |trie, trie_iter| {
|
||||
let mut result = trie_iter.next_item(&trie);
|
||||
if let Some(skipped_key) = skip_if_first {
|
||||
if let Some(Ok((ref key, _))) = result {
|
||||
if *key == skipped_key {
|
||||
result = trie_iter.next_item(&trie);
|
||||
}
|
||||
}
|
||||
}
|
||||
result
|
||||
})
|
||||
}
|
||||
|
||||
fn was_complete(&self) -> bool {
|
||||
matches!(self.state, IterState::FinishedComplete)
|
||||
}
|
||||
}
|
||||
|
||||
/// Patricia trie-based pairs storage essence.
|
||||
pub struct TrieBackendEssence<S: TrieBackendStorage<H>, H: Hasher, C, R> {
|
||||
storage: S,
|
||||
root: H::Out,
|
||||
empty: H::Out,
|
||||
#[cfg(feature = "std")]
|
||||
pub(crate) cache: Arc<RwLock<Cache<H::Out>>>,
|
||||
pub(crate) trie_node_cache: Option<C>,
|
||||
pub(crate) recorder: Option<R>,
|
||||
}
|
||||
|
||||
impl<S: TrieBackendStorage<H>, H: Hasher, C, R> TrieBackendEssence<S, H, C, R> {
|
||||
/// Create new trie-based backend.
|
||||
pub fn new(storage: S, root: H::Out) -> Self {
|
||||
Self::new_with_cache(storage, root, None)
|
||||
}
|
||||
|
||||
/// Create new trie-based backend.
|
||||
pub fn new_with_cache(storage: S, root: H::Out, cache: Option<C>) -> Self {
|
||||
TrieBackendEssence {
|
||||
storage,
|
||||
root,
|
||||
empty: H::hash(&[0u8]),
|
||||
#[cfg(feature = "std")]
|
||||
cache: Arc::new(RwLock::new(Cache::new())),
|
||||
trie_node_cache: cache,
|
||||
recorder: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create new trie-based backend.
|
||||
pub fn new_with_cache_and_recorder(
|
||||
storage: S,
|
||||
root: H::Out,
|
||||
cache: Option<C>,
|
||||
recorder: Option<R>,
|
||||
) -> Self {
|
||||
TrieBackendEssence {
|
||||
storage,
|
||||
root,
|
||||
empty: H::hash(&[0u8]),
|
||||
#[cfg(feature = "std")]
|
||||
cache: Arc::new(RwLock::new(Cache::new())),
|
||||
trie_node_cache: cache,
|
||||
recorder,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get backend storage reference.
|
||||
pub fn backend_storage(&self) -> &S {
|
||||
&self.storage
|
||||
}
|
||||
|
||||
/// Get backend storage mutable reference.
|
||||
pub fn backend_storage_mut(&mut self) -> &mut S {
|
||||
&mut self.storage
|
||||
}
|
||||
|
||||
/// Get trie root.
|
||||
pub fn root(&self) -> &H::Out {
|
||||
&self.root
|
||||
}
|
||||
|
||||
/// Set trie root. This is useful for testing.
|
||||
pub fn set_root(&mut self, root: H::Out) {
|
||||
// If root did change so can have cached content.
|
||||
self.reset_cache();
|
||||
self.root = root;
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
fn reset_cache(&mut self) {
|
||||
self.cache = Arc::new(RwLock::new(Cache::new()));
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
fn reset_cache(&mut self) {}
|
||||
|
||||
/// Consumes self and returns underlying storage.
|
||||
pub fn into_storage(self) -> S {
|
||||
self.storage
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: TrieBackendStorage<H>, H: Hasher, C: TrieCacheProvider<H>, R: TrieRecorderProvider<H>>
|
||||
TrieBackendEssence<S, H, C, R>
|
||||
{
|
||||
/// Call the given closure passing it the recorder and the cache.
|
||||
///
|
||||
/// If the given `storage_root` is `None`, `self.root` will be used.
|
||||
#[inline]
|
||||
fn with_recorder_and_cache<RE>(
|
||||
&self,
|
||||
storage_root: Option<H::Out>,
|
||||
callback: impl FnOnce(
|
||||
Option<&mut dyn TrieRecorder<H::Out>>,
|
||||
Option<&mut dyn TrieCache<NodeCodec<H>>>,
|
||||
) -> RE,
|
||||
) -> RE {
|
||||
let storage_root = storage_root.unwrap_or_else(|| self.root);
|
||||
let mut cache = self.trie_node_cache.as_ref().map(|c| c.as_trie_db_cache(storage_root));
|
||||
let cache = cache.as_mut().map(|c| c as _);
|
||||
|
||||
let mut recorder = self.recorder.as_ref().map(|r| r.as_trie_recorder(storage_root));
|
||||
let recorder = match recorder.as_mut() {
|
||||
Some(recorder) => Some(recorder as &mut dyn TrieRecorder<H::Out>),
|
||||
None => None,
|
||||
};
|
||||
callback(recorder, cache)
|
||||
}
|
||||
|
||||
/// Call the given closure passing it the recorder and the cache.
|
||||
///
|
||||
/// This function must only be used when the operation in `callback` is
|
||||
/// calculating a `storage_root`. It is expected that `callback` returns
|
||||
/// the new storage root. This is required to register the changes in the cache
|
||||
/// for the correct storage root. The given `storage_root` corresponds to the root of the "old"
|
||||
/// trie. If the value is not given, `self.root` is used.
|
||||
fn with_recorder_and_cache_for_storage_root<RE>(
|
||||
&self,
|
||||
storage_root: Option<H::Out>,
|
||||
callback: impl FnOnce(
|
||||
Option<&mut dyn TrieRecorder<H::Out>>,
|
||||
Option<&mut dyn TrieCache<NodeCodec<H>>>,
|
||||
) -> (Option<H::Out>, RE),
|
||||
) -> RE {
|
||||
let storage_root = storage_root.unwrap_or_else(|| self.root);
|
||||
let mut recorder = self.recorder.as_ref().map(|r| r.as_trie_recorder(storage_root));
|
||||
let recorder = match recorder.as_mut() {
|
||||
Some(recorder) => Some(recorder as &mut dyn TrieRecorder<H::Out>),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let result = if let Some(local_cache) = self.trie_node_cache.as_ref() {
|
||||
let mut cache = local_cache.as_trie_db_mut_cache();
|
||||
|
||||
let (new_root, r) = callback(recorder, Some(&mut cache));
|
||||
|
||||
if let Some(new_root) = new_root {
|
||||
local_cache.merge(cache, new_root);
|
||||
}
|
||||
|
||||
r
|
||||
} else {
|
||||
callback(recorder, None).1
|
||||
};
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
S: TrieBackendStorage<H>,
|
||||
H: Hasher,
|
||||
C: TrieCacheProvider<H> + Send + Sync,
|
||||
R: TrieRecorderProvider<H> + Send + Sync,
|
||||
> TrieBackendEssence<S, H, C, R>
|
||||
where
|
||||
H::Out: Codec + Ord,
|
||||
{
|
||||
/// Calls the given closure with a [`TrieDb`] constructed for the given
|
||||
/// storage root and (optionally) child trie.
|
||||
#[inline]
|
||||
fn with_trie_db<RE>(
|
||||
&self,
|
||||
root: H::Out,
|
||||
child_info: Option<&ChildInfo>,
|
||||
callback: impl FnOnce(&pezsp_trie::TrieDB<Layout<H>>) -> RE,
|
||||
) -> RE {
|
||||
let backend = self as &dyn HashDBRef<H, Vec<u8>>;
|
||||
let db = child_info
|
||||
.as_ref()
|
||||
.map(|child_info| KeySpacedDB::new(backend, child_info.keyspace()));
|
||||
let db = db.as_ref().map(|db| db as &dyn HashDBRef<H, Vec<u8>>).unwrap_or(backend);
|
||||
|
||||
self.with_recorder_and_cache(Some(root), |recorder, cache| {
|
||||
let trie = TrieDBBuilder::<H>::new(db, &root)
|
||||
.with_optional_recorder(recorder)
|
||||
.with_optional_cache(cache)
|
||||
.build();
|
||||
|
||||
callback(&trie)
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the next key in the trie i.e. the minimum key that is strictly superior to `key` in
|
||||
/// lexicographic order.
|
||||
///
|
||||
/// Will always traverse the trie from scratch in search of the key, which is slow.
|
||||
/// Used only when debug assertions are enabled to crosscheck the results of finding
|
||||
/// the next key through an iterator.
|
||||
#[cfg(debug_assertions)]
|
||||
pub fn next_storage_key_slow(&self, key: &[u8]) -> Result<Option<StorageKey>> {
|
||||
self.next_storage_key_from_root(&self.root, None, key)
|
||||
}
|
||||
|
||||
/// Access the root of the child storage in its parent trie
|
||||
fn child_root(&self, child_info: &ChildInfo) -> Result<Option<H::Out>> {
|
||||
#[cfg(feature = "std")]
|
||||
{
|
||||
if let Some(result) = self.cache.read().child_root.get(child_info.storage_key()) {
|
||||
return Ok(*result);
|
||||
}
|
||||
}
|
||||
|
||||
let result = self.storage(child_info.prefixed_storage_key().as_slice())?.map(|r| {
|
||||
let mut hash = H::Out::default();
|
||||
|
||||
// root is fetched from DB, not writable by runtime, so it's always valid.
|
||||
hash.as_mut().copy_from_slice(&r[..]);
|
||||
|
||||
hash
|
||||
});
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
{
|
||||
self.cache.write().child_root.insert(child_info.storage_key().to_vec(), result);
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Return the next key in the child trie i.e. the minimum key that is strictly superior to
|
||||
/// `key` in lexicographic order.
|
||||
pub fn next_child_storage_key(
|
||||
&self,
|
||||
child_info: &ChildInfo,
|
||||
key: &[u8],
|
||||
) -> Result<Option<StorageKey>> {
|
||||
let child_root = match self.child_root(child_info)? {
|
||||
Some(child_root) => child_root,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
self.next_storage_key_from_root(&child_root, Some(child_info), key)
|
||||
}
|
||||
|
||||
/// Return next key from main trie or child trie by providing corresponding root.
|
||||
fn next_storage_key_from_root(
|
||||
&self,
|
||||
root: &H::Out,
|
||||
child_info: Option<&ChildInfo>,
|
||||
key: &[u8],
|
||||
) -> Result<Option<StorageKey>> {
|
||||
self.with_trie_db(*root, child_info, |trie| {
|
||||
let mut iter = trie.key_iter().map_err(|e| format!("TrieDB iteration error: {}", e))?;
|
||||
|
||||
// The key just after the one given in input, basically `key++0`.
|
||||
// Note: We are sure this is the next key if:
|
||||
// * size of key has no limit (i.e. we can always add 0 to the path),
|
||||
// * and no keys can be inserted between `key` and `key++0` (this is ensured by sp-io).
|
||||
let mut potential_next_key = Vec::with_capacity(key.len() + 1);
|
||||
potential_next_key.extend_from_slice(key);
|
||||
potential_next_key.push(0);
|
||||
|
||||
iter.seek(&potential_next_key)
|
||||
.map_err(|e| format!("TrieDB iterator seek error: {}", e))?;
|
||||
|
||||
let next_element = iter.next();
|
||||
|
||||
let next_key = if let Some(next_element) = next_element {
|
||||
let next_key =
|
||||
next_element.map_err(|e| format!("TrieDB iterator next error: {}", e))?;
|
||||
Some(next_key)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(next_key)
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the hash value
|
||||
pub fn storage_hash(&self, key: &[u8]) -> Result<Option<H::Out>> {
|
||||
let map_e = |e| format!("Trie lookup error: {}", e);
|
||||
|
||||
self.with_recorder_and_cache(None, |recorder, cache| {
|
||||
TrieDBBuilder::new(self, &self.root)
|
||||
.with_optional_cache(cache)
|
||||
.with_optional_recorder(recorder)
|
||||
.build()
|
||||
.get_hash(key)
|
||||
.map_err(map_e)
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the value of storage at given key.
|
||||
pub fn storage(&self, key: &[u8]) -> Result<Option<StorageValue>> {
|
||||
let map_e = |e| format!("Trie lookup error: {}", e);
|
||||
|
||||
self.with_recorder_and_cache(None, |recorder, cache| {
|
||||
read_trie_value::<Layout<H>, _>(self, &self.root, key, recorder, cache).map_err(map_e)
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the hash value
|
||||
pub fn child_storage_hash(&self, child_info: &ChildInfo, key: &[u8]) -> Result<Option<H::Out>> {
|
||||
let child_root = match self.child_root(child_info)? {
|
||||
Some(root) => root,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
let map_e = |e| format!("Trie lookup error: {}", e);
|
||||
|
||||
self.with_recorder_and_cache(Some(child_root), |recorder, cache| {
|
||||
read_child_trie_hash::<Layout<H>, _>(
|
||||
child_info.keyspace(),
|
||||
self,
|
||||
&child_root,
|
||||
key,
|
||||
recorder,
|
||||
cache,
|
||||
)
|
||||
.map_err(map_e)
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the value of child storage at given key.
|
||||
pub fn child_storage(
|
||||
&self,
|
||||
child_info: &ChildInfo,
|
||||
key: &[u8],
|
||||
) -> Result<Option<StorageValue>> {
|
||||
let child_root = match self.child_root(child_info)? {
|
||||
Some(root) => root,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
let map_e = |e| format!("Trie lookup error: {}", e);
|
||||
|
||||
self.with_recorder_and_cache(Some(child_root), |recorder, cache| {
|
||||
read_child_trie_value::<Layout<H>, _>(
|
||||
child_info.keyspace(),
|
||||
self,
|
||||
&child_root,
|
||||
key,
|
||||
recorder,
|
||||
cache,
|
||||
)
|
||||
.map_err(map_e)
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the closest merkle value at given key.
|
||||
pub fn closest_merkle_value(&self, key: &[u8]) -> Result<Option<MerkleValue<H::Out>>> {
|
||||
let map_e = |e| format!("Trie lookup error: {}", e);
|
||||
|
||||
self.with_recorder_and_cache(None, |recorder, cache| {
|
||||
read_trie_first_descendant_value::<Layout<H>, _>(self, &self.root, key, recorder, cache)
|
||||
.map_err(map_e)
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the child closest merkle value at given key.
|
||||
pub fn child_closest_merkle_value(
|
||||
&self,
|
||||
child_info: &ChildInfo,
|
||||
key: &[u8],
|
||||
) -> Result<Option<MerkleValue<H::Out>>> {
|
||||
let Some(child_root) = self.child_root(child_info)? else { return Ok(None) };
|
||||
|
||||
let map_e = |e| format!("Trie lookup error: {}", e);
|
||||
|
||||
self.with_recorder_and_cache(Some(child_root), |recorder, cache| {
|
||||
read_child_trie_first_descendant_value::<Layout<H>, _>(
|
||||
child_info.keyspace(),
|
||||
self,
|
||||
&child_root,
|
||||
key,
|
||||
recorder,
|
||||
cache,
|
||||
)
|
||||
.map_err(map_e)
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a raw iterator over the storage.
|
||||
pub fn raw_iter(&self, args: IterArgs) -> Result<RawIter<S, H, C, R>> {
|
||||
let root = if let Some(child_info) = args.child_info.as_ref() {
|
||||
let root = match self.child_root(&child_info)? {
|
||||
Some(root) => root,
|
||||
None => return Ok(Default::default()),
|
||||
};
|
||||
root
|
||||
} else {
|
||||
self.root
|
||||
};
|
||||
|
||||
if self.root == Default::default() {
|
||||
// A special-case for an empty storage root.
|
||||
return Ok(Default::default());
|
||||
}
|
||||
|
||||
let trie_iter = self
|
||||
.with_trie_db(root, args.child_info.as_ref(), |db| {
|
||||
let prefix = args.prefix.as_deref().unwrap_or(&[]);
|
||||
if let Some(start_at) = args.start_at {
|
||||
TrieDBRawIterator::new_prefixed_then_seek(db, prefix, &start_at)
|
||||
} else {
|
||||
TrieDBRawIterator::new_prefixed(db, prefix)
|
||||
}
|
||||
})
|
||||
.map_err(|e| format!("TrieDB iteration error: {}", e))?;
|
||||
|
||||
Ok(RawIter {
|
||||
stop_on_incomplete_database: args.stop_on_incomplete_database,
|
||||
skip_if_first: if args.start_at_exclusive {
|
||||
args.start_at.map(|key| key.to_vec())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
child_info: args.child_info,
|
||||
root,
|
||||
trie_iter,
|
||||
state: IterState::Pending,
|
||||
_phantom: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Return the storage root after applying the given `delta`.
|
||||
pub fn storage_root<'a>(
|
||||
&self,
|
||||
delta: impl Iterator<Item = (&'a [u8], Option<&'a [u8]>)>,
|
||||
state_version: StateVersion,
|
||||
) -> (H::Out, PrefixedMemoryDB<H>) {
|
||||
let mut write_overlay = PrefixedMemoryDB::with_hasher(RandomState::default());
|
||||
|
||||
let root = self.with_recorder_and_cache_for_storage_root(None, |recorder, cache| {
|
||||
let mut eph = Ephemeral::new(self.backend_storage(), &mut write_overlay);
|
||||
let res = match state_version {
|
||||
StateVersion::V0 => delta_trie_root::<pezsp_trie::LayoutV0<H>, _, _, _, _, _>(
|
||||
&mut eph, self.root, delta, recorder, cache,
|
||||
),
|
||||
StateVersion::V1 => delta_trie_root::<pezsp_trie::LayoutV1<H>, _, _, _, _, _>(
|
||||
&mut eph, self.root, delta, recorder, cache,
|
||||
),
|
||||
};
|
||||
|
||||
match res {
|
||||
Ok(ret) => (Some(ret), ret),
|
||||
Err(e) => {
|
||||
warn!(target: "trie", "Failed to write to trie: {}", e);
|
||||
(None, self.root)
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
(root, write_overlay)
|
||||
}
|
||||
|
||||
/// Returns the child storage root for the child trie `child_info` after applying the given
|
||||
/// `delta`.
|
||||
pub fn child_storage_root<'a>(
|
||||
&self,
|
||||
child_info: &ChildInfo,
|
||||
delta: impl Iterator<Item = (&'a [u8], Option<&'a [u8]>)>,
|
||||
state_version: StateVersion,
|
||||
) -> (H::Out, bool, PrefixedMemoryDB<H>) {
|
||||
let default_root = match child_info.child_type() {
|
||||
ChildType::ParentKeyId => empty_child_trie_root::<pezsp_trie::LayoutV1<H>>(),
|
||||
};
|
||||
let mut write_overlay = PrefixedMemoryDB::with_hasher(RandomState::default());
|
||||
let child_root = match self.child_root(child_info) {
|
||||
Ok(Some(hash)) => hash,
|
||||
Ok(None) => default_root,
|
||||
Err(e) => {
|
||||
warn!(target: "trie", "Failed to read child storage root: {}", e);
|
||||
default_root
|
||||
},
|
||||
};
|
||||
|
||||
let new_child_root =
|
||||
self.with_recorder_and_cache_for_storage_root(Some(child_root), |recorder, cache| {
|
||||
let mut eph = Ephemeral::new(self.backend_storage(), &mut write_overlay);
|
||||
match match state_version {
|
||||
StateVersion::V0 =>
|
||||
child_delta_trie_root::<pezsp_trie::LayoutV0<H>, _, _, _, _, _, _>(
|
||||
child_info.keyspace(),
|
||||
&mut eph,
|
||||
child_root,
|
||||
delta,
|
||||
recorder,
|
||||
cache,
|
||||
),
|
||||
StateVersion::V1 =>
|
||||
child_delta_trie_root::<pezsp_trie::LayoutV1<H>, _, _, _, _, _, _>(
|
||||
child_info.keyspace(),
|
||||
&mut eph,
|
||||
child_root,
|
||||
delta,
|
||||
recorder,
|
||||
cache,
|
||||
),
|
||||
} {
|
||||
Ok(ret) => (Some(ret), ret),
|
||||
Err(e) => {
|
||||
warn!(target: "trie", "Failed to write to trie: {}", e);
|
||||
(None, child_root)
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
let is_default = new_child_root == default_root;
|
||||
|
||||
(new_child_root, is_default, write_overlay)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct Ephemeral<'a, S: 'a + TrieBackendStorage<H>, H: 'a + Hasher> {
|
||||
storage: &'a S,
|
||||
overlay: &'a mut PrefixedMemoryDB<H>,
|
||||
}
|
||||
|
||||
impl<'a, S: 'a + TrieBackendStorage<H>, H: 'a + Hasher> AsHashDB<H, DBValue>
|
||||
for Ephemeral<'a, S, H>
|
||||
{
|
||||
fn as_hash_db<'b>(&'b self) -> &'b (dyn HashDB<H, DBValue> + 'b) {
|
||||
self
|
||||
}
|
||||
fn as_hash_db_mut<'b>(&'b mut self) -> &'b mut (dyn HashDB<H, DBValue> + 'b) {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S: TrieBackendStorage<H>, H: Hasher> Ephemeral<'a, S, H> {
|
||||
pub fn new(storage: &'a S, overlay: &'a mut PrefixedMemoryDB<H>) -> Self {
|
||||
Ephemeral { storage, overlay }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S: 'a + TrieBackendStorage<H>, H: Hasher> hash_db::HashDB<H, DBValue>
|
||||
for Ephemeral<'a, S, H>
|
||||
{
|
||||
fn get(&self, key: &H::Out, prefix: Prefix) -> Option<DBValue> {
|
||||
HashDB::get(self.overlay, key, prefix).or_else(|| {
|
||||
self.storage.get(key, prefix).unwrap_or_else(|e| {
|
||||
warn!(target: "trie", "Failed to read from DB: {}", e);
|
||||
None
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn contains(&self, key: &H::Out, prefix: Prefix) -> bool {
|
||||
HashDB::get(self, key, prefix).is_some()
|
||||
}
|
||||
|
||||
fn insert(&mut self, prefix: Prefix, value: &[u8]) -> H::Out {
|
||||
HashDB::insert(self.overlay, prefix, value)
|
||||
}
|
||||
|
||||
fn emplace(&mut self, key: H::Out, prefix: Prefix, value: DBValue) {
|
||||
HashDB::emplace(self.overlay, key, prefix, value)
|
||||
}
|
||||
|
||||
fn remove(&mut self, key: &H::Out, prefix: Prefix) {
|
||||
HashDB::remove(self.overlay, key, prefix)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S: 'a + TrieBackendStorage<H>, H: Hasher> HashDBRef<H, DBValue> for Ephemeral<'a, S, H> {
|
||||
fn get(&self, key: &H::Out, prefix: Prefix) -> Option<DBValue> {
|
||||
HashDB::get(self, key, prefix)
|
||||
}
|
||||
|
||||
fn contains(&self, key: &H::Out, prefix: Prefix) -> bool {
|
||||
HashDB::contains(self, key, prefix)
|
||||
}
|
||||
}
|
||||
|
||||
/// Key-value pairs storage that is used by trie backend essence.
|
||||
pub trait TrieBackendStorage<H: Hasher>: Send + Sync {
|
||||
/// Get the value stored at key.
|
||||
fn get(&self, key: &H::Out, prefix: Prefix) -> Result<Option<DBValue>>;
|
||||
}
|
||||
|
||||
impl<T: TrieBackendStorage<H>, H: Hasher> TrieBackendStorage<H> for &T {
|
||||
fn get(&self, key: &H::Out, prefix: Prefix) -> Result<Option<DBValue>> {
|
||||
(*self).get(key, prefix)
|
||||
}
|
||||
}
|
||||
|
||||
// This implementation is used by normal storage trie clients.
|
||||
#[cfg(feature = "std")]
|
||||
impl<H: Hasher> TrieBackendStorage<H> for Arc<dyn Storage<H>> {
|
||||
fn get(&self, key: &H::Out, prefix: Prefix) -> Result<Option<DBValue>> {
|
||||
Storage::<H>::get(std::ops::Deref::deref(self), key, prefix)
|
||||
}
|
||||
}
|
||||
|
||||
impl<H, KF> TrieBackendStorage<H> for pezsp_trie::GenericMemoryDB<H, KF>
|
||||
where
|
||||
H: Hasher,
|
||||
KF: pezsp_trie::KeyFunction<H> + Send + Sync,
|
||||
{
|
||||
fn get(&self, key: &H::Out, prefix: Prefix) -> Result<Option<DBValue>> {
|
||||
Ok(hash_db::HashDB::get(self, key, prefix))
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
S: TrieBackendStorage<H>,
|
||||
H: Hasher,
|
||||
C: TrieCacheProvider<H> + Send + Sync,
|
||||
R: TrieRecorderProvider<H> + Send + Sync,
|
||||
> AsHashDB<H, DBValue> for TrieBackendEssence<S, H, C, R>
|
||||
{
|
||||
fn as_hash_db<'b>(&'b self) -> &'b (dyn HashDB<H, DBValue> + 'b) {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_hash_db_mut<'b>(&'b mut self) -> &'b mut (dyn HashDB<H, DBValue> + 'b) {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
S: TrieBackendStorage<H>,
|
||||
H: Hasher,
|
||||
C: TrieCacheProvider<H> + Send + Sync,
|
||||
R: TrieRecorderProvider<H> + Send + Sync,
|
||||
> HashDB<H, DBValue> for TrieBackendEssence<S, H, C, R>
|
||||
{
|
||||
fn get(&self, key: &H::Out, prefix: Prefix) -> Option<DBValue> {
|
||||
if *key == self.empty {
|
||||
return Some([0u8].to_vec());
|
||||
}
|
||||
match self.storage.get(key, prefix) {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
warn!(target: "trie", "Failed to read from DB: {}", e);
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn contains(&self, key: &H::Out, prefix: Prefix) -> bool {
|
||||
HashDB::get(self, key, prefix).is_some()
|
||||
}
|
||||
|
||||
fn insert(&mut self, _prefix: Prefix, _value: &[u8]) -> H::Out {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn emplace(&mut self, _key: H::Out, _prefix: Prefix, _value: DBValue) {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn remove(&mut self, _key: &H::Out, _prefix: Prefix) {
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
S: TrieBackendStorage<H>,
|
||||
H: Hasher,
|
||||
C: TrieCacheProvider<H> + Send + Sync,
|
||||
R: TrieRecorderProvider<H> + Send + Sync,
|
||||
> HashDBRef<H, DBValue> for TrieBackendEssence<S, H, C, R>
|
||||
{
|
||||
fn get(&self, key: &H::Out, prefix: Prefix) -> Option<DBValue> {
|
||||
HashDB::get(self, key, prefix)
|
||||
}
|
||||
|
||||
fn contains(&self, key: &H::Out, prefix: Prefix) -> bool {
|
||||
HashDB::contains(self, key, prefix)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::{Backend, TrieBackend};
|
||||
use pezsp_core::{Blake2Hasher, H256};
|
||||
use pezsp_trie::{
|
||||
cache::LocalTrieCache, trie_types::TrieDBMutBuilderV1 as TrieDBMutBuilder, KeySpacedDBMut,
|
||||
PrefixedMemoryDB, TrieMut,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn next_storage_key_and_next_child_storage_key_work() {
|
||||
let child_info = ChildInfo::new_default(b"MyChild");
|
||||
let child_info = &child_info;
|
||||
// Contains values
|
||||
let mut root_1 = H256::default();
|
||||
// Contains child trie
|
||||
let mut root_2 = H256::default();
|
||||
|
||||
let mut mdb = PrefixedMemoryDB::<Blake2Hasher>::default();
|
||||
{
|
||||
let mut trie = TrieDBMutBuilder::new(&mut mdb, &mut root_1).build();
|
||||
trie.insert(b"3", &[1]).expect("insert failed");
|
||||
trie.insert(b"4", &[1]).expect("insert failed");
|
||||
trie.insert(b"6", &[1]).expect("insert failed");
|
||||
}
|
||||
{
|
||||
let mut mdb = KeySpacedDBMut::new(&mut mdb, child_info.keyspace());
|
||||
// reuse of root_1 implicitly assert child trie root is same
|
||||
// as top trie (contents must remain the same).
|
||||
let mut trie = TrieDBMutBuilder::new(&mut mdb, &mut root_1).build();
|
||||
trie.insert(b"3", &[1]).expect("insert failed");
|
||||
trie.insert(b"4", &[1]).expect("insert failed");
|
||||
trie.insert(b"6", &[1]).expect("insert failed");
|
||||
}
|
||||
{
|
||||
let mut trie = TrieDBMutBuilder::new(&mut mdb, &mut root_2).build();
|
||||
trie.insert(child_info.prefixed_storage_key().as_slice(), root_1.as_ref())
|
||||
.expect("insert failed");
|
||||
};
|
||||
|
||||
let essence_1 =
|
||||
TrieBackendEssence::<_, _, LocalTrieCache<_>, pezsp_trie::recorder::Recorder<_>>::new(
|
||||
mdb, root_1,
|
||||
);
|
||||
let mdb = essence_1.backend_storage().clone();
|
||||
let essence_1 = TrieBackend::from_essence(essence_1);
|
||||
|
||||
assert_eq!(essence_1.next_storage_key(b"2"), Ok(Some(b"3".to_vec())));
|
||||
assert_eq!(essence_1.next_storage_key(b"3"), Ok(Some(b"4".to_vec())));
|
||||
assert_eq!(essence_1.next_storage_key(b"4"), Ok(Some(b"6".to_vec())));
|
||||
assert_eq!(essence_1.next_storage_key(b"5"), Ok(Some(b"6".to_vec())));
|
||||
assert_eq!(essence_1.next_storage_key(b"6"), Ok(None));
|
||||
|
||||
let essence_2 =
|
||||
TrieBackendEssence::<_, _, LocalTrieCache<_>, pezsp_trie::recorder::Recorder<_>>::new(
|
||||
mdb, root_2,
|
||||
);
|
||||
|
||||
assert_eq!(essence_2.next_child_storage_key(child_info, b"2"), Ok(Some(b"3".to_vec())));
|
||||
assert_eq!(essence_2.next_child_storage_key(child_info, b"3"), Ok(Some(b"4".to_vec())));
|
||||
assert_eq!(essence_2.next_child_storage_key(child_info, b"4"), Ok(Some(b"6".to_vec())));
|
||||
assert_eq!(essence_2.next_child_storage_key(child_info, b"5"), Ok(Some(b"6".to_vec())));
|
||||
assert_eq!(essence_2.next_child_storage_key(child_info, b"6"), Ok(None));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user