Implement nested storage transactions (#6269)

* Add transactional storage functionality to OverlayChanges

A collection already has a natural None state. No need to
wrap it with an option.

* Add storage transactions runtime interface

* Add frame support for transactions

* Fix committed typo

* Rename 'changes' variable to 'overlay'

* Fix renaming change

* Fixed strange line break

* Rename clear to clear_where

* Add comment regarding delete value on mutation

* Add comment which changes are covered by a transaction

* Do force the arg to with_transaction return a Result

* Use rust doc comments on every documentable place

* Fix wording of insert_diry doc

* Improve doc on start_transaction

* Rename value to overlayed in close_transaction

* Inline negation

* Improve wording of close_transaction comments

* Get rid of an expect by using get_or_insert_with

* Remove trailing whitespace

* Rename should to expected in tests

* Rolling back a transaction must mark the overlay as dirty

* Protect client initiated storage tx from being droped by runtime

* Review nits

* Return Err when entering or exiting runtime fails

* Documentation fixup

* Remove close type

* Move enter/exit runtime to excute_aux in the state-machine

* Rename Discard -> Rollback

* Move child changeset creation to constructor

* Move child spawning into the closure

* Apply suggestions from code review

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>

* Fixup for code suggestion

* Unify re-exports

* Rename overlay_changes to mod.rs and move into subdir

* Change proof wording

* Adapt a new test from master to storage-tx

* Suggestions from the latest round of review

* Fix warning message

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>
This commit is contained in:
Alexander Theißen
2020-06-23 11:17:53 +02:00
committed by GitHub
parent 6aa8965f33
commit bb2df2122e
16 changed files with 1383 additions and 532 deletions
@@ -0,0 +1,752 @@
// This file is part of Substrate.
// Copyright (C) 2020 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
//! Houses the code that implements the transactional overlay storage.
use super::{StorageKey, StorageValue};
use itertools::Itertools;
use std::collections::{HashSet, BTreeMap, BTreeSet};
use smallvec::SmallVec;
use log::warn;
const PROOF_OVERLAY_NON_EMPTY: &str = "\
An OverlayValue is always created with at least one transaction and dropped as soon
as the last transaction is removed; qed";
type DirtyKeysSets = SmallVec<[HashSet<StorageKey>; 5]>;
type Transactions = SmallVec<[InnerValue; 5]>;
/// Error returned when trying to commit or rollback while no transaction is open or
/// when the runtime is trying to close a transaction started by the client.
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub struct NoOpenTransaction;
/// Error when calling `enter_runtime` when already being in runtime execution mode.
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub struct AlreadyInRuntime;
/// Error when calling `exit_runtime` when not being in runtime exection mdde.
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub struct NotInRuntime;
/// Describes in which mode the node is currently executing.
#[derive(Debug, Clone, Copy)]
pub enum ExecutionMode {
/// Exeuting in client mode: Removal of all transactions possible.
Client,
/// Executing in runtime mode: Transactions started by the client are protected.
Runtime,
}
#[derive(Debug, Default, Clone)]
#[cfg_attr(test, derive(PartialEq))]
struct InnerValue {
/// Current value. None if value has been deleted.
value: Option<StorageValue>,
/// The set of extrinsic indices where the values has been changed.
/// Is filled only if runtime has announced changes trie support.
extrinsics: BTreeSet<u32>,
}
/// An overlay that contains all versions of a value for a specific key.
#[derive(Debug, Default, Clone)]
#[cfg_attr(test, derive(PartialEq))]
pub struct OverlayedValue {
/// The individual versions of that value.
/// One entry per transactions during that the value was actually written.
transactions: Transactions,
}
/// Holds a set of changes with the ability modify them using nested transactions.
#[derive(Debug, Default, Clone)]
pub struct OverlayedChangeSet {
/// Stores the changes that this overlay constitutes.
changes: BTreeMap<StorageKey, OverlayedValue>,
/// Stores which keys are dirty per transaction. Needed in order to determine which
/// values to merge into the parent transaction on commit. The length of this vector
/// therefore determines how many nested transactions are currently open (depth).
dirty_keys: DirtyKeysSets,
/// The number of how many transactions beginning from the first transactions are started
/// by the client. Those transactions are protected against close (commit, rollback)
/// when in runtime mode.
num_client_transactions: usize,
/// Determines whether the node is using the overlay from the client or the runtime.
execution_mode: ExecutionMode,
}
impl Default for ExecutionMode {
fn default() -> Self {
Self::Client
}
}
impl OverlayedValue {
/// The value as seen by the current transaction.
pub fn value(&self) -> Option<&StorageValue> {
self.transactions.last().expect(PROOF_OVERLAY_NON_EMPTY).value.as_ref()
}
/// Unique list of extrinsic indices which modified the value.
pub fn extrinsics(&self) -> impl Iterator<Item=&u32> {
self.transactions.iter().flat_map(|t| t.extrinsics.iter()).unique()
}
/// Mutable reference to the most recent version.
fn value_mut(&mut self) -> &mut Option<StorageValue> {
&mut self.transactions.last_mut().expect(PROOF_OVERLAY_NON_EMPTY).value
}
/// Remove the last version and return it.
fn pop_transaction(&mut self) -> InnerValue {
self.transactions.pop().expect(PROOF_OVERLAY_NON_EMPTY)
}
/// Mutable reference to the set which holds the indices for the **current transaction only**.
fn transaction_extrinsics_mut(&mut self) -> &mut BTreeSet<u32> {
&mut self.transactions.last_mut().expect(PROOF_OVERLAY_NON_EMPTY).extrinsics
}
/// Writes a new version of a value.
///
/// This makes sure that the old version is not overwritten and can be properly
/// rolled back when required.
fn set(
&mut self,
value: Option<StorageValue>,
first_write_in_tx: bool,
at_extrinsic: Option<u32>,
) {
if first_write_in_tx || self.transactions.is_empty() {
self.transactions.push(InnerValue {
value,
.. Default::default()
});
} else {
*self.value_mut() = value;
}
if let Some(extrinsic) = at_extrinsic {
self.transaction_extrinsics_mut().insert(extrinsic);
}
}
}
/// Inserts a key into the dirty set.
///
/// Returns true iff we are currently have at least one open transaction and if this
/// is the first write to the given key that transaction.
fn insert_dirty(set: &mut DirtyKeysSets, key: StorageKey) -> bool {
set.last_mut().map(|dk| dk.insert(key)).unwrap_or_default()
}
impl OverlayedChangeSet {
/// Create a new changeset at the same transaction state but without any contents.
///
/// This changeset might be created when there are already open transactions.
/// We need to catch up here so that the child is at the same transaction depth.
pub fn spawn_child(&self) -> Self {
use std::iter::repeat;
Self {
dirty_keys: repeat(HashSet::new()).take(self.transaction_depth()).collect(),
num_client_transactions: self.num_client_transactions,
execution_mode: self.execution_mode,
.. Default::default()
}
}
/// True if no changes at all are contained in the change set.
pub fn is_empty(&self) -> bool {
self.changes.is_empty()
}
/// Get an optional reference to the value stored for the specified key.
pub fn get(&self, key: &[u8]) -> Option<&OverlayedValue> {
self.changes.get(key)
}
/// Set a new value for the specified key.
///
/// Can be rolled back or committed when called inside a transaction.
pub fn set(
&mut self,
key: StorageKey,
value: Option<StorageValue>,
at_extrinsic: Option<u32>,
) {
let overlayed = self.changes.entry(key.clone()).or_default();
overlayed.set(value, insert_dirty(&mut self.dirty_keys, key), at_extrinsic);
}
/// Get a mutable reference for a value.
///
/// Can be rolled back or committed when called inside a transaction.
#[must_use = "A change was registered, so this value MUST be modified."]
pub fn modify(
&mut self,
key: StorageKey,
init: impl Fn() -> StorageValue,
at_extrinsic: Option<u32>,
) -> &mut Option<StorageValue> {
let overlayed = self.changes.entry(key.clone()).or_default();
let first_write_in_tx = insert_dirty(&mut self.dirty_keys, key);
let clone_into_new_tx = if let Some(tx) = overlayed.transactions.last() {
if first_write_in_tx {
Some(tx.value.clone())
} else {
None
}
} else {
Some(Some(init()))
};
if let Some(cloned) = clone_into_new_tx {
overlayed.set(cloned, first_write_in_tx, at_extrinsic);
}
overlayed.value_mut()
}
/// Set all values to deleted which are matched by the predicate.
///
/// Can be rolled back or committed when called inside a transaction.
pub fn clear_where(
&mut self,
predicate: impl Fn(&[u8], &OverlayedValue) -> bool,
at_extrinsic: Option<u32>,
) {
for (key, val) in self.changes.iter_mut().filter(|(k, v)| predicate(k, v)) {
val.set(None, insert_dirty(&mut self.dirty_keys, key.to_owned()), at_extrinsic);
}
}
/// Get a list of all changes as seen by current transaction.
pub fn changes(&self) -> impl Iterator<Item=(&StorageKey, &OverlayedValue)> {
self.changes.iter()
}
/// Get the change that is next to the supplied key.
pub fn next_change(&self, key: &[u8]) -> Option<(&[u8], &OverlayedValue)> {
use std::ops::Bound;
let range = (Bound::Excluded(key), Bound::Unbounded);
self.changes.range::<[u8], _>(range).next().map(|(k, v)| (&k[..], v))
}
/// Consume this changeset and return all committed changes.
///
/// Panics:
/// Panics if there are open transactions: `transaction_depth() > 0`
pub fn drain_commited(self) -> impl Iterator<Item=(StorageKey, Option<StorageValue>)> {
assert!(self.transaction_depth() == 0, "Drain is not allowed with open transactions.");
self.changes.into_iter().map(|(k, mut v)| (k, v.pop_transaction().value))
}
/// Returns the current nesting depth of the transaction stack.
///
/// A value of zero means that no transaction is open and changes are committed on write.
pub fn transaction_depth(&self) -> usize {
self.dirty_keys.len()
}
/// Call this before transfering control to the runtime.
///
/// This protects all existing transactions from being removed by the runtime.
/// Calling this while already inside the runtime will return an error.
pub fn enter_runtime(&mut self) -> Result<(), AlreadyInRuntime> {
if let ExecutionMode::Runtime = self.execution_mode {
return Err(AlreadyInRuntime);
}
self.execution_mode = ExecutionMode::Runtime;
self.num_client_transactions = self.transaction_depth();
Ok(())
}
/// Call this when control returns from the runtime.
///
/// This commits all dangling transaction left open by the runtime.
/// Calling this while already outside the runtime will return an error.
pub fn exit_runtime(&mut self) -> Result<(), NotInRuntime> {
if let ExecutionMode::Client = self.execution_mode {
return Err(NotInRuntime);
}
self.execution_mode = ExecutionMode::Client;
if self.has_open_runtime_transactions() {
warn!(
"{} storage transactions are left open by the runtime. Those will be rolled back.",
self.transaction_depth() - self.num_client_transactions,
);
}
while self.has_open_runtime_transactions() {
self.rollback_transaction()
.expect("The loop condition checks that the transaction depth is > 0; qed");
}
Ok(())
}
/// Start a new nested transaction.
///
/// This allows to either commit or roll back all changes that were made while this
/// transaction was open. Any transaction must be closed by either `commit_transaction`
/// or `rollback_transaction` before this overlay can be converted into storage changes.
///
/// Changes made without any open transaction are committed immediately.
pub fn start_transaction(&mut self) {
self.dirty_keys.push(Default::default());
}
/// Rollback the last transaction started by `start_transaction`.
///
/// Any changes made during that transaction are discarded. Returns an error if
/// there is no open transaction that can be rolled back.
pub fn rollback_transaction(&mut self) -> Result<(), NoOpenTransaction> {
self.close_transaction(true)
}
/// Commit the last transaction started by `start_transaction`.
///
/// Any changes made during that transaction are committed. Returns an error if
/// there is no open transaction that can be committed.
pub fn commit_transaction(&mut self) -> Result<(), NoOpenTransaction> {
self.close_transaction(false)
}
fn close_transaction(&mut self, rollback: bool) -> Result<(), NoOpenTransaction> {
// runtime is not allowed to close transactions started by the client
if let ExecutionMode::Runtime = self.execution_mode {
if !self.has_open_runtime_transactions() {
return Err(NoOpenTransaction)
}
}
for key in self.dirty_keys.pop().ok_or(NoOpenTransaction)? {
let overlayed = self.changes.get_mut(&key).expect("\
A write to an OverlayedValue is recorded in the dirty key set. Before an
OverlayedValue is removed, its containing dirty set is removed. This
function is only called for keys that are in the dirty set. qed\
");
if rollback {
overlayed.pop_transaction();
// We need to remove the key as an `OverlayValue` with no transactions
// violates its invariant of always having at least one transaction.
if overlayed.transactions.is_empty() {
self.changes.remove(&key);
}
} else {
let has_predecessor = if let Some(dirty_keys) = self.dirty_keys.last_mut() {
// Not the last tx: Did the previous tx write to this key?
!dirty_keys.insert(key)
} else {
// Last tx: Is there already a value in the committed set?
// Check against one rather than empty because the current tx is still
// in the list as it is popped later in this function.
overlayed.transactions.len() > 1
};
// We only need to merge if there is an pre-existing value. It may be a value from
// the previous transaction or a value committed without any open transaction.
if has_predecessor {
let dropped_tx = overlayed.pop_transaction();
*overlayed.value_mut() = dropped_tx.value;
overlayed.transaction_extrinsics_mut().extend(dropped_tx.extrinsics);
}
}
}
Ok(())
}
fn has_open_runtime_transactions(&self) -> bool {
self.transaction_depth() > self.num_client_transactions
}
}
#[cfg(test)]
mod test {
use super::*;
use pretty_assertions::assert_eq;
type Changes<'a> = Vec<(&'a [u8], (Option<&'a [u8]>, Vec<u32>))>;
type Drained<'a> = Vec<(&'a [u8], Option<&'a [u8]>)>;
fn assert_changes(is: &OverlayedChangeSet, expected: &Changes) {
let is: Changes = is.changes().map(|(k, v)| {
(k.as_ref(), (v.value().map(AsRef::as_ref), v.extrinsics().cloned().collect()))
}).collect();
assert_eq!(&is, expected);
}
fn assert_drained_changes(is: OverlayedChangeSet, expected: Changes) {
let is = is.drain_commited().collect::<Vec<_>>();
let expected = expected
.iter()
.map(|(k, v)| (k.to_vec(), v.0.map(From::from))).collect::<Vec<_>>();
assert_eq!(is, expected);
}
fn assert_drained(is: OverlayedChangeSet, expected: Drained) {
let is = is.drain_commited().collect::<Vec<_>>();
let expected = expected
.iter()
.map(|(k, v)| (k.to_vec(), v.map(From::from))).collect::<Vec<_>>();
assert_eq!(is, expected);
}
#[test]
fn no_transaction_works() {
let mut changeset = OverlayedChangeSet::default();
assert_eq!(changeset.transaction_depth(), 0);
changeset.set(b"key0".to_vec(), Some(b"val0".to_vec()), Some(1));
changeset.set(b"key1".to_vec(), Some(b"val1".to_vec()), Some(2));
changeset.set(b"key0".to_vec(), Some(b"val0-1".to_vec()), Some(9));
assert_drained(changeset, vec![
(b"key0", Some(b"val0-1")),
(b"key1", Some(b"val1")),
]);
}
#[test]
fn transaction_works() {
let mut changeset = OverlayedChangeSet::default();
assert_eq!(changeset.transaction_depth(), 0);
// no transaction: committed on set
changeset.set(b"key0".to_vec(), Some(b"val0".to_vec()), Some(1));
changeset.set(b"key1".to_vec(), Some(b"val1".to_vec()), Some(1));
changeset.set(b"key0".to_vec(), Some(b"val0-1".to_vec()), Some(10));
changeset.start_transaction();
assert_eq!(changeset.transaction_depth(), 1);
// we will commit that later
changeset.set(b"key42".to_vec(), Some(b"val42".to_vec()), Some(42));
changeset.set(b"key99".to_vec(), Some(b"val99".to_vec()), Some(99));
changeset.start_transaction();
assert_eq!(changeset.transaction_depth(), 2);
// we will roll that back
changeset.set(b"key42".to_vec(), Some(b"val42-rolled".to_vec()), Some(421));
changeset.set(b"key7".to_vec(), Some(b"val7-rolled".to_vec()), Some(77));
changeset.set(b"key0".to_vec(), Some(b"val0-rolled".to_vec()), Some(1000));
changeset.set(b"key5".to_vec(), Some(b"val5-rolled".to_vec()), None);
// changes contain all changes not only the commmited ones.
let all_changes: Changes = vec![
(b"key0", (Some(b"val0-rolled"), vec![1, 10, 1000])),
(b"key1", (Some(b"val1"), vec![1])),
(b"key42", (Some(b"val42-rolled"), vec![42, 421])),
(b"key5", (Some(b"val5-rolled"), vec![])),
(b"key7", (Some(b"val7-rolled"), vec![77])),
(b"key99", (Some(b"val99"), vec![99])),
];
assert_changes(&changeset, &all_changes);
// this should be no-op
changeset.start_transaction();
assert_eq!(changeset.transaction_depth(), 3);
changeset.start_transaction();
assert_eq!(changeset.transaction_depth(), 4);
changeset.rollback_transaction().unwrap();
assert_eq!(changeset.transaction_depth(), 3);
changeset.commit_transaction().unwrap();
assert_eq!(changeset.transaction_depth(), 2);
assert_changes(&changeset, &all_changes);
// roll back our first transactions that actually contains something
changeset.rollback_transaction().unwrap();
assert_eq!(changeset.transaction_depth(), 1);
let rolled_back: Changes = vec![
(b"key0", (Some(b"val0-1"), vec![1, 10])),
(b"key1", (Some(b"val1"), vec![1])),
(b"key42", (Some(b"val42"), vec![42])),
(b"key99", (Some(b"val99"), vec![99])),
];
assert_changes(&changeset, &rolled_back);
changeset.commit_transaction().unwrap();
assert_eq!(changeset.transaction_depth(), 0);
assert_changes(&changeset, &rolled_back);
assert_drained_changes(changeset, rolled_back);
}
#[test]
fn transaction_commit_then_rollback_works() {
let mut changeset = OverlayedChangeSet::default();
assert_eq!(changeset.transaction_depth(), 0);
changeset.set(b"key0".to_vec(), Some(b"val0".to_vec()), Some(1));
changeset.set(b"key1".to_vec(), Some(b"val1".to_vec()), Some(1));
changeset.set(b"key0".to_vec(), Some(b"val0-1".to_vec()), Some(10));
changeset.start_transaction();
assert_eq!(changeset.transaction_depth(), 1);
changeset.set(b"key42".to_vec(), Some(b"val42".to_vec()), Some(42));
changeset.set(b"key99".to_vec(), Some(b"val99".to_vec()), Some(99));
changeset.start_transaction();
assert_eq!(changeset.transaction_depth(), 2);
changeset.set(b"key42".to_vec(), Some(b"val42-rolled".to_vec()), Some(421));
changeset.set(b"key7".to_vec(), Some(b"val7-rolled".to_vec()), Some(77));
changeset.set(b"key0".to_vec(), Some(b"val0-rolled".to_vec()), Some(1000));
changeset.set(b"key5".to_vec(), Some(b"val5-rolled".to_vec()), None);
let all_changes: Changes = vec![
(b"key0", (Some(b"val0-rolled"), vec![1, 10, 1000])),
(b"key1", (Some(b"val1"), vec![1])),
(b"key42", (Some(b"val42-rolled"), vec![42, 421])),
(b"key5", (Some(b"val5-rolled"), vec![])),
(b"key7", (Some(b"val7-rolled"), vec![77])),
(b"key99", (Some(b"val99"), vec![99])),
];
assert_changes(&changeset, &all_changes);
// this should be no-op
changeset.start_transaction();
assert_eq!(changeset.transaction_depth(), 3);
changeset.start_transaction();
assert_eq!(changeset.transaction_depth(), 4);
changeset.rollback_transaction().unwrap();
assert_eq!(changeset.transaction_depth(), 3);
changeset.commit_transaction().unwrap();
assert_eq!(changeset.transaction_depth(), 2);
assert_changes(&changeset, &all_changes);
changeset.commit_transaction().unwrap();
assert_eq!(changeset.transaction_depth(), 1);
assert_changes(&changeset, &all_changes);
changeset.rollback_transaction().unwrap();
assert_eq!(changeset.transaction_depth(), 0);
let rolled_back: Changes = vec![
(b"key0", (Some(b"val0-1"), vec![1, 10])),
(b"key1", (Some(b"val1"), vec![1])),
];
assert_changes(&changeset, &rolled_back);
assert_drained_changes(changeset, rolled_back);
}
#[test]
fn modify_works() {
let mut changeset = OverlayedChangeSet::default();
assert_eq!(changeset.transaction_depth(), 0);
let init = || b"valinit".to_vec();
// committed set
changeset.set(b"key0".to_vec(), Some(b"val0".to_vec()), Some(0));
changeset.set(b"key1".to_vec(), None, Some(1));
let val = changeset.modify(b"key3".to_vec(), init, Some(3));
assert_eq!(val, &Some(b"valinit".to_vec()));
val.as_mut().unwrap().extend_from_slice(b"-modified");
changeset.start_transaction();
assert_eq!(changeset.transaction_depth(), 1);
changeset.start_transaction();
assert_eq!(changeset.transaction_depth(), 2);
// non existing value -> init value should be returned
let val = changeset.modify(b"key2".to_vec(), init, Some(2));
assert_eq!(val, &Some(b"valinit".to_vec()));
val.as_mut().unwrap().extend_from_slice(b"-modified");
// existing value should be returned by modify
let val = changeset.modify(b"key0".to_vec(), init, Some(10));
assert_eq!(val, &Some(b"val0".to_vec()));
val.as_mut().unwrap().extend_from_slice(b"-modified");
// should work for deleted keys
let val = changeset.modify(b"key1".to_vec(), init, Some(20));
assert_eq!(val, &None);
*val = Some(b"deleted-modified".to_vec());
let all_changes: Changes = vec![
(b"key0", (Some(b"val0-modified"), vec![0, 10])),
(b"key1", (Some(b"deleted-modified"), vec![1, 20])),
(b"key2", (Some(b"valinit-modified"), vec![2])),
(b"key3", (Some(b"valinit-modified"), vec![3])),
];
assert_changes(&changeset, &all_changes);
changeset.commit_transaction().unwrap();
assert_eq!(changeset.transaction_depth(), 1);
assert_changes(&changeset, &all_changes);
changeset.rollback_transaction().unwrap();
assert_eq!(changeset.transaction_depth(), 0);
let rolled_back: Changes = vec![
(b"key0", (Some(b"val0"), vec![0])),
(b"key1", (None, vec![1])),
(b"key3", (Some(b"valinit-modified"), vec![3])),
];
assert_changes(&changeset, &rolled_back);
assert_drained_changes(changeset, rolled_back);
}
#[test]
fn clear_works() {
let mut changeset = OverlayedChangeSet::default();
changeset.set(b"key0".to_vec(), Some(b"val0".to_vec()), Some(1));
changeset.set(b"key1".to_vec(), Some(b"val1".to_vec()), Some(2));
changeset.set(b"del1".to_vec(), Some(b"delval1".to_vec()), Some(3));
changeset.set(b"del2".to_vec(), Some(b"delval2".to_vec()), Some(4));
changeset.start_transaction();
changeset.clear_where(|k, _| k.starts_with(b"del"), Some(5));
assert_changes(&changeset, &vec![
(b"del1", (None, vec![3, 5])),
(b"del2", (None, vec![4, 5])),
(b"key0", (Some(b"val0"), vec![1])),
(b"key1", (Some(b"val1"), vec![2])),
]);
changeset.rollback_transaction().unwrap();
assert_changes(&changeset, &vec![
(b"del1", (Some(b"delval1"), vec![3])),
(b"del2", (Some(b"delval2"), vec![4])),
(b"key0", (Some(b"val0"), vec![1])),
(b"key1", (Some(b"val1"), vec![2])),
]);
}
#[test]
fn next_change_works() {
let mut changeset = OverlayedChangeSet::default();
changeset.set(b"key0".to_vec(), Some(b"val0".to_vec()), Some(0));
changeset.set(b"key1".to_vec(), Some(b"val1".to_vec()), Some(1));
changeset.set(b"key2".to_vec(), Some(b"val2".to_vec()), Some(2));
changeset.start_transaction();
changeset.set(b"key3".to_vec(), Some(b"val3".to_vec()), Some(3));
changeset.set(b"key4".to_vec(), Some(b"val4".to_vec()), Some(4));
changeset.set(b"key11".to_vec(), Some(b"val11".to_vec()), Some(11));
assert_eq!(changeset.next_change(b"key0").unwrap().0, b"key1");
assert_eq!(changeset.next_change(b"key0").unwrap().1.value(), Some(&b"val1".to_vec()));
assert_eq!(changeset.next_change(b"key1").unwrap().0, b"key11");
assert_eq!(changeset.next_change(b"key1").unwrap().1.value(), Some(&b"val11".to_vec()));
assert_eq!(changeset.next_change(b"key11").unwrap().0, b"key2");
assert_eq!(changeset.next_change(b"key11").unwrap().1.value(), Some(&b"val2".to_vec()));
assert_eq!(changeset.next_change(b"key2").unwrap().0, b"key3");
assert_eq!(changeset.next_change(b"key2").unwrap().1.value(), Some(&b"val3".to_vec()));
assert_eq!(changeset.next_change(b"key3").unwrap().0, b"key4");
assert_eq!(changeset.next_change(b"key3").unwrap().1.value(), Some(&b"val4".to_vec()));
assert_eq!(changeset.next_change(b"key4"), None);
changeset.rollback_transaction().unwrap();
assert_eq!(changeset.next_change(b"key0").unwrap().0, b"key1");
assert_eq!(changeset.next_change(b"key0").unwrap().1.value(), Some(&b"val1".to_vec()));
assert_eq!(changeset.next_change(b"key1").unwrap().0, b"key2");
assert_eq!(changeset.next_change(b"key1").unwrap().1.value(), Some(&b"val2".to_vec()));
assert_eq!(changeset.next_change(b"key11").unwrap().0, b"key2");
assert_eq!(changeset.next_change(b"key11").unwrap().1.value(), Some(&b"val2".to_vec()));
assert_eq!(changeset.next_change(b"key2"), None);
assert_eq!(changeset.next_change(b"key3"), None);
assert_eq!(changeset.next_change(b"key4"), None);
}
#[test]
fn no_open_tx_commit_errors() {
let mut changeset = OverlayedChangeSet::default();
assert_eq!(changeset.transaction_depth(), 0);
assert_eq!(changeset.commit_transaction(), Err(NoOpenTransaction));
}
#[test]
fn no_open_tx_rollback_errors() {
let mut changeset = OverlayedChangeSet::default();
assert_eq!(changeset.transaction_depth(), 0);
assert_eq!(changeset.rollback_transaction(), Err(NoOpenTransaction));
}
#[test]
fn unbalanced_transactions_errors() {
let mut changeset = OverlayedChangeSet::default();
changeset.start_transaction();
changeset.commit_transaction().unwrap();
assert_eq!(changeset.commit_transaction(), Err(NoOpenTransaction));
}
#[test]
#[should_panic]
fn drain_with_open_transaction_panics() {
let mut changeset = OverlayedChangeSet::default();
changeset.start_transaction();
let _ = changeset.drain_commited();
}
#[test]
fn runtime_cannot_close_client_tx() {
let mut changeset = OverlayedChangeSet::default();
changeset.start_transaction();
changeset.enter_runtime().unwrap();
changeset.start_transaction();
changeset.commit_transaction().unwrap();
assert_eq!(changeset.commit_transaction(), Err(NoOpenTransaction));
assert_eq!(changeset.rollback_transaction(), Err(NoOpenTransaction));
}
#[test]
fn exit_runtime_closes_runtime_tx() {
let mut changeset = OverlayedChangeSet::default();
changeset.start_transaction();
changeset.set(b"key0".to_vec(), Some(b"val0".to_vec()), Some(1));
changeset.enter_runtime().unwrap();
changeset.start_transaction();
changeset.set(b"key1".to_vec(), Some(b"val1".to_vec()), Some(2));
changeset.exit_runtime().unwrap();
changeset.commit_transaction().unwrap();
assert_eq!(changeset.transaction_depth(), 0);
assert_drained(changeset, vec![
(b"key0", Some(b"val0")),
]);
}
#[test]
fn enter_exit_runtime_fails_when_already_in_requested_mode() {
let mut changeset = OverlayedChangeSet::default();
assert_eq!(changeset.exit_runtime(), Err(NotInRuntime));
assert_eq!(changeset.enter_runtime(), Ok(()));
assert_eq!(changeset.enter_runtime(), Err(AlreadyInRuntime));
assert_eq!(changeset.exit_runtime(), Ok(()));
assert_eq!(changeset.exit_runtime(), Err(NotInRuntime));
}
}
@@ -0,0 +1,775 @@
// This file is part of Substrate.
// Copyright (C) 2017-2020 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.
//! The overlayed changes to state.
mod changeset;
use crate::{
backend::Backend, ChangesTrieTransaction,
changes_trie::{
NO_EXTRINSIC_INDEX, BlockNumber, build_changes_trie,
State as ChangesTrieState,
},
stats::StateMachineStats,
};
use self::changeset::OverlayedChangeSet;
use std::collections::HashMap;
use codec::{Decode, Encode};
use sp_core::storage::{well_known_keys::EXTRINSIC_INDEX, ChildInfo};
use sp_core::offchain::storage::OffchainOverlayedChanges;
use hash_db::Hasher;
pub use self::changeset::{OverlayedValue, NoOpenTransaction, AlreadyInRuntime, NotInRuntime};
/// Storage key.
pub type StorageKey = Vec<u8>;
/// Storage value.
pub type StorageValue = Vec<u8>;
/// In memory array of storage values.
pub type StorageCollection = Vec<(StorageKey, Option<StorageValue>)>;
/// In memory arrays of storage values for multiple child tries.
pub type ChildStorageCollection = Vec<(StorageKey, StorageCollection)>;
/// The set of changes that are overlaid onto the backend.
///
/// It allows changes to be modified using nestable transactions.
#[derive(Debug, Default, Clone)]
pub struct OverlayedChanges {
/// Top level storage changes.
top: OverlayedChangeSet,
/// Child storage changes. The map key is the child storage key without the common prefix.
children: HashMap<StorageKey, (OverlayedChangeSet, ChildInfo)>,
/// True if extrinsics stats must be collected.
collect_extrinsics: bool,
/// Collect statistic on this execution.
stats: StateMachineStats,
}
/// A storage changes structure that can be generated by the data collected in [`OverlayedChanges`].
///
/// This contains all the changes to the storage and transactions to apply theses changes to the
/// backend.
pub struct StorageChanges<Transaction, H: Hasher, N: BlockNumber> {
/// All changes to the main storage.
///
/// A value of `None` means that it was deleted.
pub main_storage_changes: StorageCollection,
/// All changes to the child storages.
pub child_storage_changes: ChildStorageCollection,
/// Offchain state changes to write to the offchain database.
pub offchain_storage_changes: OffchainOverlayedChanges,
/// A transaction for the backend that contains all changes from
/// [`main_storage_changes`](StorageChanges::main_storage_changes) and from
/// [`child_storage_changes`](StorageChanges::child_storage_changes).
/// [`offchain_storage_changes`](StorageChanges::offchain_storage_changes).
pub transaction: Transaction,
/// The storage root after applying the transaction.
pub transaction_storage_root: H::Out,
/// Contains the transaction for the backend for the changes trie.
///
/// If changes trie is disabled the value is set to `None`.
pub changes_trie_transaction: Option<ChangesTrieTransaction<H, N>>,
}
impl<Transaction, H: Hasher, N: BlockNumber> StorageChanges<Transaction, H, N> {
/// Deconstruct into the inner values
pub fn into_inner(self) -> (
StorageCollection,
ChildStorageCollection,
OffchainOverlayedChanges,
Transaction,
H::Out,
Option<ChangesTrieTransaction<H, N>>,
) {
(
self.main_storage_changes,
self.child_storage_changes,
self.offchain_storage_changes,
self.transaction,
self.transaction_storage_root,
self.changes_trie_transaction,
)
}
}
/// The storage transaction are calculated as part of the `storage_root` and
/// `changes_trie_storage_root`. These transactions can be reused for importing the block into the
/// storage. So, we cache them to not require a recomputation of those transactions.
pub struct StorageTransactionCache<Transaction, H: Hasher, N: BlockNumber> {
/// Contains the changes for the main and the child storages as one transaction.
pub(crate) transaction: Option<Transaction>,
/// The storage root after applying the transaction.
pub(crate) transaction_storage_root: Option<H::Out>,
/// Contains the changes trie transaction.
pub(crate) changes_trie_transaction: Option<Option<ChangesTrieTransaction<H, N>>>,
/// The storage root after applying the changes trie transaction.
pub(crate) changes_trie_transaction_storage_root: Option<Option<H::Out>>,
}
impl<Transaction, H: Hasher, N: BlockNumber> StorageTransactionCache<Transaction, H, N> {
/// Reset the cached transactions.
pub fn reset(&mut self) {
*self = Self::default();
}
}
impl<Transaction, H: Hasher, N: BlockNumber> Default for StorageTransactionCache<Transaction, H, N> {
fn default() -> Self {
Self {
transaction: None,
transaction_storage_root: None,
changes_trie_transaction: None,
changes_trie_transaction_storage_root: None,
}
}
}
impl<Transaction: Default, H: Hasher, N: BlockNumber> Default for StorageChanges<Transaction, H, N> {
fn default() -> Self {
Self {
main_storage_changes: Default::default(),
child_storage_changes: Default::default(),
offchain_storage_changes: Default::default(),
transaction: Default::default(),
transaction_storage_root: Default::default(),
changes_trie_transaction: None,
}
}
}
impl OverlayedChanges {
/// Whether no changes are contained in the top nor in any of the child changes.
pub fn is_empty(&self) -> bool {
self.top.is_empty() && self.children.is_empty()
}
/// Ask to collect/not to collect extrinsics indices where key(s) has been changed.
pub fn set_collect_extrinsics(&mut self, collect_extrinsics: bool) {
self.collect_extrinsics = collect_extrinsics;
}
/// Returns a double-Option: None if the key is unknown (i.e. and the query should be referred
/// to the backend); Some(None) if the key has been deleted. Some(Some(...)) for a key whose
/// value has been set.
pub fn storage(&self, key: &[u8]) -> Option<Option<&[u8]>> {
self.top.get(key).map(|x| {
let value = x.value();
let size_read = value.map(|x| x.len() as u64).unwrap_or(0);
self.stats.tally_read_modified(size_read);
value.map(AsRef::as_ref)
})
}
/// Returns mutable reference to current value.
/// If there is no value in the overlay, the given callback is used to initiate the value.
/// Warning this function registers a change, so the mutable reference MUST be modified.
///
/// Can be rolled back or committed when called inside a transaction.
#[must_use = "A change was registered, so this value MUST be modified."]
pub fn value_mut_or_insert_with(
&mut self,
key: &[u8],
init: impl Fn() -> StorageValue,
) -> &mut StorageValue {
let value = self.top.modify(key.to_owned(), init, self.extrinsic_index());
// if the value was deleted initialise it back with an empty vec
value.get_or_insert_with(StorageValue::default)
}
/// Returns a double-Option: None if the key is unknown (i.e. and the query should be referred
/// to the backend); Some(None) if the key has been deleted. Some(Some(...)) for a key whose
/// value has been set.
pub fn child_storage(&self, child_info: &ChildInfo, key: &[u8]) -> Option<Option<&[u8]>> {
let map = self.children.get(child_info.storage_key())?;
let value = map.0.get(key)?.value();
let size_read = value.map(|x| x.len() as u64).unwrap_or(0);
self.stats.tally_read_modified(size_read);
Some(value.map(AsRef::as_ref))
}
/// Set a new value for the specified key.
///
/// Can be rolled back or committed when called inside a transaction.
pub(crate) fn set_storage(&mut self, key: StorageKey, val: Option<StorageValue>) {
let size_write = val.as_ref().map(|x| x.len() as u64).unwrap_or(0);
self.stats.tally_write_overlay(size_write);
self.top.set(key, val, self.extrinsic_index());
}
/// Set a new value for the specified key and child.
///
/// `None` can be used to delete a value specified by the given key.
///
/// Can be rolled back or committed when called inside a transaction.
pub(crate) fn set_child_storage(
&mut self,
child_info: &ChildInfo,
key: StorageKey,
val: Option<StorageValue>,
) {
let extrinsic_index = self.extrinsic_index();
let size_write = val.as_ref().map(|x| x.len() as u64).unwrap_or(0);
self.stats.tally_write_overlay(size_write);
let storage_key = child_info.storage_key().to_vec();
let top = &self.top;
let (changeset, info) = self.children.entry(storage_key).or_insert_with(||
(
top.spawn_child(),
child_info.to_owned()
)
);
let updatable = info.try_update(child_info);
debug_assert!(updatable);
changeset.set(key, val, extrinsic_index);
}
/// Clear child storage of given storage key.
///
/// Can be rolled back or committed when called inside a transaction.
pub(crate) fn clear_child_storage(
&mut self,
child_info: &ChildInfo,
) {
let extrinsic_index = self.extrinsic_index();
let storage_key = child_info.storage_key().to_vec();
let top = &self.top;
let (changeset, info) = self.children.entry(storage_key).or_insert_with(||
(
top.spawn_child(),
child_info.to_owned()
)
);
let updatable = info.try_update(child_info);
debug_assert!(updatable);
changeset.clear_where(|_, _| true, extrinsic_index);
}
/// Removes all key-value pairs which keys share the given prefix.
///
/// Can be rolled back or committed when called inside a transaction.
pub(crate) fn clear_prefix(&mut self, prefix: &[u8]) {
self.top.clear_where(|key, _| key.starts_with(prefix), self.extrinsic_index());
}
/// Removes all key-value pairs which keys share the given prefix.
///
/// Can be rolled back or committed when called inside a transaction
pub(crate) fn clear_child_prefix(
&mut self,
child_info: &ChildInfo,
prefix: &[u8],
) {
let extrinsic_index = self.extrinsic_index();
let storage_key = child_info.storage_key().to_vec();
let top = &self.top;
let (changeset, info) = self.children.entry(storage_key).or_insert_with(||
(
top.spawn_child(),
child_info.to_owned()
)
);
let updatable = info.try_update(child_info);
debug_assert!(updatable);
changeset.clear_where(|key, _| key.starts_with(prefix), extrinsic_index);
}
/// Returns the current nesting depth of the transaction stack.
///
/// A value of zero means that no transaction is open and changes are committed on write.
pub fn transaction_depth(&self) -> usize {
// The top changeset and all child changesets transact in lockstep and are
// therefore always at the same transaction depth.
self.top.transaction_depth()
}
/// Start a new nested transaction.
///
/// This allows to either commit or roll back all changes that where made while this
/// transaction was open. Any transaction must be closed by either `rollback_transaction` or
/// `commit_transaction` before this overlay can be converted into storage changes.
///
/// Changes made without any open transaction are committed immediatly.
pub fn start_transaction(&mut self) {
self.top.start_transaction();
for (_, (changeset, _)) in self.children.iter_mut() {
changeset.start_transaction();
}
}
/// Rollback the last transaction started by `start_transaction`.
///
/// Any changes made during that transaction are discarded. Returns an error if
/// there is no open transaction that can be rolled back.
pub fn rollback_transaction(&mut self) -> Result<(), NoOpenTransaction> {
self.top.rollback_transaction()?;
self.children.retain(|_, (changeset, _)| {
changeset.rollback_transaction()
.expect("Top and children changesets are started in lockstep; qed");
!changeset.is_empty()
});
Ok(())
}
/// Commit the last transaction started by `start_transaction`.
///
/// Any changes made during that transaction are committed. Returns an error if there
/// is no open transaction that can be committed.
pub fn commit_transaction(&mut self) -> Result<(), NoOpenTransaction> {
self.top.commit_transaction()?;
for (_, (changeset, _)) in self.children.iter_mut() {
changeset.commit_transaction()
.expect("Top and children changesets are started in lockstep; qed");
}
Ok(())
}
/// Call this before transfering control to the runtime.
///
/// This protects all existing transactions from being removed by the runtime.
/// Calling this while already inside the runtime will return an error.
pub fn enter_runtime(&mut self) -> Result<(), AlreadyInRuntime> {
self.top.enter_runtime()?;
for (_, (changeset, _)) in self.children.iter_mut() {
changeset.enter_runtime()
.expect("Top and children changesets are entering runtime in lockstep; qed")
}
Ok(())
}
/// Call this when control returns from the runtime.
///
/// This commits all dangling transaction left open by the runtime.
/// Calling this while outside the runtime will return an error.
pub fn exit_runtime(&mut self) -> Result<(), NotInRuntime> {
self.top.exit_runtime()?;
for (_, (changeset, _)) in self.children.iter_mut() {
changeset.exit_runtime()
.expect("Top and children changesets are entering runtime in lockstep; qed");
}
Ok(())
}
/// Consume all changes (top + children) and return them.
///
/// After calling this function no more changes are contained in this changeset.
///
/// Panics:
/// Panics if `transaction_depth() > 0`
fn drain_committed(&mut self) -> (
impl Iterator<Item=(StorageKey, Option<StorageValue>)>,
impl Iterator<Item=(StorageKey, (impl Iterator<Item=(StorageKey, Option<StorageValue>)>, ChildInfo))>,
) {
use std::mem::take;
(
take(&mut self.top).drain_commited(),
take(&mut self.children).into_iter()
.map(|(key, (val, info))| (
key,
(val.drain_commited(), info)
)
),
)
}
/// Get an iterator over all child changes as seen by the current transaction.
pub fn children(&self)
-> impl Iterator<Item=(impl Iterator<Item=(&StorageKey, &OverlayedValue)>, &ChildInfo)> {
self.children.iter().map(|(_, v)| (v.0.changes(), &v.1))
}
/// Get an iterator over all top changes as been by the current transaction.
pub fn changes(&self) -> impl Iterator<Item=(&StorageKey, &OverlayedValue)> {
self.top.changes()
}
/// Get an optional iterator over all child changes stored under the supplied key.
pub fn child_changes(&self, key: &[u8])
-> Option<(impl Iterator<Item=(&StorageKey, &OverlayedValue)>, &ChildInfo)> {
self.children.get(key).map(|(overlay, info)| (overlay.changes(), info))
}
/// Convert this instance with all changes into a [`StorageChanges`] instance.
pub fn into_storage_changes<
B: Backend<H>, H: Hasher, N: BlockNumber
>(
mut self,
backend: &B,
changes_trie_state: Option<&ChangesTrieState<H, N>>,
parent_hash: H::Out,
mut cache: StorageTransactionCache<B::Transaction, H, N>,
) -> Result<StorageChanges<B::Transaction, H, N>, String> where H::Out: Ord + Encode + 'static {
self.drain_storage_changes(backend, changes_trie_state, parent_hash, &mut cache)
}
/// Drain all changes into a [`StorageChanges`] instance. Leave empty overlay in place.
pub fn drain_storage_changes<B: Backend<H>, H: Hasher, N: BlockNumber>(
&mut self,
backend: &B,
changes_trie_state: Option<&ChangesTrieState<H, N>>,
parent_hash: H::Out,
mut cache: &mut StorageTransactionCache<B::Transaction, H, N>,
) -> Result<StorageChanges<B::Transaction, H, N>, String> where H::Out: Ord + Encode + 'static {
// If the transaction does not exist, we generate it.
if cache.transaction.is_none() {
self.storage_root(backend, &mut cache);
}
let (transaction, transaction_storage_root) = cache.transaction.take()
.and_then(|t| cache.transaction_storage_root.take().map(|tr| (t, tr)))
.expect("Transaction was be generated as part of `storage_root`; qed");
// If the transaction does not exist, we generate it.
if cache.changes_trie_transaction.is_none() {
self.changes_trie_root(
backend,
changes_trie_state,
parent_hash,
false,
&mut cache,
).map_err(|_| "Failed to generate changes trie transaction")?;
}
let changes_trie_transaction = cache.changes_trie_transaction
.take()
.expect("Changes trie transaction was generated by `changes_trie_root`; qed");
let offchain_storage_changes = Default::default();
let (main_storage_changes, child_storage_changes) = self.drain_committed();
Ok(StorageChanges {
main_storage_changes: main_storage_changes.collect(),
child_storage_changes: child_storage_changes.map(|(sk, it)| (sk, it.0.collect())).collect(),
offchain_storage_changes,
transaction,
transaction_storage_root,
changes_trie_transaction,
})
}
/// Inserts storage entry responsible for current extrinsic index.
#[cfg(test)]
pub(crate) fn set_extrinsic_index(&mut self, extrinsic_index: u32) {
self.top.set(EXTRINSIC_INDEX.to_vec(), Some(extrinsic_index.encode()), None);
}
/// Returns current extrinsic index to use in changes trie construction.
/// None is returned if it is not set or changes trie config is not set.
/// Persistent value (from the backend) can be ignored because runtime must
/// set this index before first and unset after last extrinsic is executed.
/// Changes that are made outside of extrinsics, are marked with
/// `NO_EXTRINSIC_INDEX` index.
fn extrinsic_index(&self) -> Option<u32> {
match self.collect_extrinsics {
true => Some(
self.storage(EXTRINSIC_INDEX)
.and_then(|idx| idx.and_then(|idx| Decode::decode(&mut &*idx).ok()))
.unwrap_or(NO_EXTRINSIC_INDEX)),
false => None,
}
}
/// Generate the storage root using `backend` and all changes
/// as seen by the current transaction.
///
/// Returns the storage root and caches storage transaction in the given `cache`.
pub fn storage_root<H: Hasher, N: BlockNumber, B: Backend<H>>(
&self,
backend: &B,
cache: &mut StorageTransactionCache<B::Transaction, H, N>,
) -> H::Out
where H::Out: Ord + Encode,
{
let delta = self.changes().map(|(k, v)| (&k[..], v.value().map(|v| &v[..])));
let child_delta = self.children()
.map(|(changes, info)| (info, changes.map(
|(k, v)| (&k[..], v.value().map(|v| &v[..]))
)));
let (root, transaction) = backend.full_storage_root(delta, child_delta);
cache.transaction = Some(transaction);
cache.transaction_storage_root = Some(root);
root
}
/// Generate the changes trie root.
///
/// Returns the changes trie root and caches the storage transaction into the given `cache`.
///
/// # Panics
///
/// Panics on storage error, when `panic_on_storage_error` is set.
pub fn changes_trie_root<'a, H: Hasher, N: BlockNumber, B: Backend<H>>(
&self,
backend: &B,
changes_trie_state: Option<&'a ChangesTrieState<'a, H, N>>,
parent_hash: H::Out,
panic_on_storage_error: bool,
cache: &mut StorageTransactionCache<B::Transaction, H, N>,
) -> Result<Option<H::Out>, ()> where H::Out: Ord + Encode + 'static {
build_changes_trie::<_, H, N>(
backend,
changes_trie_state,
self,
parent_hash,
panic_on_storage_error,
).map(|r| {
let root = r.as_ref().map(|r| r.1).clone();
cache.changes_trie_transaction = Some(r.map(|(db, _, cache)| (db, cache)));
cache.changes_trie_transaction_storage_root = Some(root);
root
})
}
/// Returns the next (in lexicographic order) storage key in the overlayed alongside its value.
/// If no value is next then `None` is returned.
pub fn next_storage_key_change(&self, key: &[u8]) -> Option<(&[u8], &OverlayedValue)> {
self.top.next_change(key)
}
/// Returns the next (in lexicographic order) child storage key in the overlayed alongside its
/// value. If no value is next then `None` is returned.
pub fn next_child_storage_key_change(
&self,
storage_key: &[u8],
key: &[u8]
) -> Option<(&[u8], &OverlayedValue)> {
self.children
.get(storage_key)
.and_then(|(overlay, _)|
overlay.next_change(key)
)
}
}
#[cfg(test)]
mod tests {
use hex_literal::hex;
use sp_core::{Blake2Hasher, traits::Externalities};
use crate::InMemoryBackend;
use crate::ext::Ext;
use super::*;
use std::collections::BTreeMap;
fn assert_extrinsics(
overlay: &OverlayedChangeSet,
key: impl AsRef<[u8]>,
expected: Vec<u32>,
) {
assert_eq!(
overlay.get(key.as_ref()).unwrap().extrinsics().cloned().collect::<Vec<_>>(),
expected
)
}
#[test]
fn overlayed_storage_works() {
let mut overlayed = OverlayedChanges::default();
let key = vec![42, 69, 169, 142];
assert!(overlayed.storage(&key).is_none());
overlayed.start_transaction();
overlayed.set_storage(key.clone(), Some(vec![1, 2, 3]));
assert_eq!(overlayed.storage(&key).unwrap(), Some(&[1, 2, 3][..]));
overlayed.commit_transaction().unwrap();
assert_eq!(overlayed.storage(&key).unwrap(), Some(&[1, 2, 3][..]));
overlayed.start_transaction();
overlayed.set_storage(key.clone(), Some(vec![]));
assert_eq!(overlayed.storage(&key).unwrap(), Some(&[][..]));
overlayed.set_storage(key.clone(), None);
assert!(overlayed.storage(&key).unwrap().is_none());
overlayed.rollback_transaction().unwrap();
assert_eq!(overlayed.storage(&key).unwrap(), Some(&[1, 2, 3][..]));
overlayed.set_storage(key.clone(), None);
assert!(overlayed.storage(&key).unwrap().is_none());
}
#[test]
fn overlayed_storage_root_works() {
let initial: BTreeMap<_, _> = vec![
(b"doe".to_vec(), b"reindeer".to_vec()),
(b"dog".to_vec(), b"puppyXXX".to_vec()),
(b"dogglesworth".to_vec(), b"catXXX".to_vec()),
(b"doug".to_vec(), b"notadog".to_vec()),
].into_iter().collect();
let backend = InMemoryBackend::<Blake2Hasher>::from(initial);
let mut overlay = OverlayedChanges::default();
overlay.set_collect_extrinsics(false);
overlay.start_transaction();
overlay.set_storage(b"dog".to_vec(), Some(b"puppy".to_vec()));
overlay.set_storage(b"dogglesworth".to_vec(), Some(b"catYYY".to_vec()));
overlay.set_storage(b"doug".to_vec(), Some(vec![]));
overlay.commit_transaction().unwrap();
overlay.start_transaction();
overlay.set_storage(b"dogglesworth".to_vec(), Some(b"cat".to_vec()));
overlay.set_storage(b"doug".to_vec(), None);
let mut offchain_overlay = Default::default();
let mut cache = StorageTransactionCache::default();
let mut ext = Ext::new(
&mut overlay,
&mut offchain_overlay,
&mut cache,
&backend,
crate::changes_trie::disabled_state::<_, u64>(),
None,
);
const ROOT: [u8; 32] = hex!("39245109cef3758c2eed2ccba8d9b370a917850af3824bc8348d505df2c298fa");
assert_eq!(&ext.storage_root()[..], &ROOT);
}
#[test]
fn extrinsic_changes_are_collected() {
let mut overlay = OverlayedChanges::default();
overlay.set_collect_extrinsics(true);
overlay.start_transaction();
overlay.set_storage(vec![100], Some(vec![101]));
overlay.set_extrinsic_index(0);
overlay.set_storage(vec![1], Some(vec![2]));
overlay.set_extrinsic_index(1);
overlay.set_storage(vec![3], Some(vec![4]));
overlay.set_extrinsic_index(2);
overlay.set_storage(vec![1], Some(vec![6]));
assert_extrinsics(&overlay.top, vec![1], vec![0, 2]);
assert_extrinsics(&overlay.top, vec![3], vec![1]);
assert_extrinsics(&overlay.top, vec![100], vec![NO_EXTRINSIC_INDEX]);
overlay.start_transaction();
overlay.set_extrinsic_index(3);
overlay.set_storage(vec![3], Some(vec![7]));
overlay.set_extrinsic_index(4);
overlay.set_storage(vec![1], Some(vec![8]));
assert_extrinsics(&overlay.top, vec![1], vec![0, 2, 4]);
assert_extrinsics(&overlay.top, vec![3], vec![1, 3]);
assert_extrinsics(&overlay.top, vec![100], vec![NO_EXTRINSIC_INDEX]);
overlay.rollback_transaction().unwrap();
assert_extrinsics(&overlay.top, vec![1], vec![0, 2]);
assert_extrinsics(&overlay.top, vec![3], vec![1]);
assert_extrinsics(&overlay.top, vec![100], vec![NO_EXTRINSIC_INDEX]);
}
#[test]
fn next_storage_key_change_works() {
let mut overlay = OverlayedChanges::default();
overlay.start_transaction();
overlay.set_storage(vec![20], Some(vec![20]));
overlay.set_storage(vec![30], Some(vec![30]));
overlay.set_storage(vec![40], Some(vec![40]));
overlay.commit_transaction().unwrap();
overlay.set_storage(vec![10], Some(vec![10]));
overlay.set_storage(vec![30], None);
// next_prospective < next_committed
let next_to_5 = overlay.next_storage_key_change(&[5]).unwrap();
assert_eq!(next_to_5.0.to_vec(), vec![10]);
assert_eq!(next_to_5.1.value(), Some(&vec![10]));
// next_committed < next_prospective
let next_to_10 = overlay.next_storage_key_change(&[10]).unwrap();
assert_eq!(next_to_10.0.to_vec(), vec![20]);
assert_eq!(next_to_10.1.value(), Some(&vec![20]));
// next_committed == next_prospective
let next_to_20 = overlay.next_storage_key_change(&[20]).unwrap();
assert_eq!(next_to_20.0.to_vec(), vec![30]);
assert_eq!(next_to_20.1.value(), None);
// next_committed, no next_prospective
let next_to_30 = overlay.next_storage_key_change(&[30]).unwrap();
assert_eq!(next_to_30.0.to_vec(), vec![40]);
assert_eq!(next_to_30.1.value(), Some(&vec![40]));
overlay.set_storage(vec![50], Some(vec![50]));
// next_prospective, no next_committed
let next_to_40 = overlay.next_storage_key_change(&[40]).unwrap();
assert_eq!(next_to_40.0.to_vec(), vec![50]);
assert_eq!(next_to_40.1.value(), Some(&vec![50]));
}
#[test]
fn next_child_storage_key_change_works() {
let child_info = ChildInfo::new_default(b"Child1");
let child_info = &child_info;
let child = child_info.storage_key();
let mut overlay = OverlayedChanges::default();
overlay.start_transaction();
overlay.set_child_storage(child_info, vec![20], Some(vec![20]));
overlay.set_child_storage(child_info, vec![30], Some(vec![30]));
overlay.set_child_storage(child_info, vec![40], Some(vec![40]));
overlay.commit_transaction().unwrap();
overlay.set_child_storage(child_info, vec![10], Some(vec![10]));
overlay.set_child_storage(child_info, vec![30], None);
// next_prospective < next_committed
let next_to_5 = overlay.next_child_storage_key_change(child, &[5]).unwrap();
assert_eq!(next_to_5.0.to_vec(), vec![10]);
assert_eq!(next_to_5.1.value(), Some(&vec![10]));
// next_committed < next_prospective
let next_to_10 = overlay.next_child_storage_key_change(child, &[10]).unwrap();
assert_eq!(next_to_10.0.to_vec(), vec![20]);
assert_eq!(next_to_10.1.value(), Some(&vec![20]));
// next_committed == next_prospective
let next_to_20 = overlay.next_child_storage_key_change(child, &[20]).unwrap();
assert_eq!(next_to_20.0.to_vec(), vec![30]);
assert_eq!(next_to_20.1.value(), None);
// next_committed, no next_prospective
let next_to_30 = overlay.next_child_storage_key_change(child, &[30]).unwrap();
assert_eq!(next_to_30.0.to_vec(), vec![40]);
assert_eq!(next_to_30.1.value(), Some(&vec![40]));
overlay.set_child_storage(child_info, vec![50], Some(vec![50]));
// next_prospective, no next_committed
let next_to_40 = overlay.next_child_storage_key_change(child, &[40]).unwrap();
assert_eq!(next_to_40.0.to_vec(), vec![50]);
assert_eq!(next_to_40.1.value(), Some(&vec![50]));
}
}