Skip to content

Commit

Permalink
Append overlay optimization. (#1223)
Browse files Browse the repository at this point in the history
This branch propose to avoid clones in append by storing offset and size
in previous overlay depth.
That way on rollback we can just truncate and change size of existing
value.
To avoid copy it also means that :

- append on new overlay layer if there is an existing value: create a
new Append entry with previous offsets, and take memory of previous
overlay value.
- rollback on append: restore value by applying offsets and put it back
in previous overlay value
- commit on append: appended value overwrite previous value (is an empty
vec as the memory was taken). offsets of commited layer are dropped, if
there is offset in previous overlay layer they are maintained.
- set value (or remove) when append offsets are present: current
appended value is moved back to previous overlay value with offset
applied and current empty entry is overwrite (no offsets kept).

The modify mechanism is not needed anymore.
This branch lacks testing and break some existing genericity (bit of
duplicated code), but good to have to check direction.

Generally I am not sure if it is worth or we just should favor
differents directions (transients blob storage for instance), as the
current append mechanism is a bit tricky (having a variable length in
first position means we sometime need to insert in front of a vector).

Fix #30.

---------

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Co-authored-by: EgorPopelyaev <egor@parity.io>
Co-authored-by: Alexandru Vasile <60601340+lexnv@users.noreply.github.com>
Co-authored-by: Bastian Köcher <git@kchr.de>
Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>
Co-authored-by: Liam Aharon <liam.aharon@hotmail.com>
Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>
Co-authored-by: Branislav Kontur <bkontur@gmail.com>
Co-authored-by: Bastian Köcher <info@kchr.de>
Co-authored-by: Sebastian Kunert <skunert49@gmail.com>
  • Loading branch information
11 people committed Jun 12, 2024
1 parent 47ef2a0 commit e5ceba5
Show file tree
Hide file tree
Showing 20 changed files with 1,352 additions and 230 deletions.
15 changes: 15 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 6 additions & 6 deletions polkadot/node/core/pvf/common/src/executor_interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,19 +215,19 @@ type HostFunctions = (
struct ValidationExternalities(sp_externalities::Extensions);

impl sp_externalities::Externalities for ValidationExternalities {
fn storage(&self, _: &[u8]) -> Option<Vec<u8>> {
fn storage(&mut self, _: &[u8]) -> Option<Vec<u8>> {
panic!("storage: unsupported feature for parachain validation")
}

fn storage_hash(&self, _: &[u8]) -> Option<Vec<u8>> {
fn storage_hash(&mut self, _: &[u8]) -> Option<Vec<u8>> {
panic!("storage_hash: unsupported feature for parachain validation")
}

fn child_storage_hash(&self, _: &ChildInfo, _: &[u8]) -> Option<Vec<u8>> {
fn child_storage_hash(&mut self, _: &ChildInfo, _: &[u8]) -> Option<Vec<u8>> {
panic!("child_storage_hash: unsupported feature for parachain validation")
}

fn child_storage(&self, _: &ChildInfo, _: &[u8]) -> Option<Vec<u8>> {
fn child_storage(&mut self, _: &ChildInfo, _: &[u8]) -> Option<Vec<u8>> {
panic!("child_storage: unsupported feature for parachain validation")
}

Expand Down Expand Up @@ -275,11 +275,11 @@ impl sp_externalities::Externalities for ValidationExternalities {
panic!("child_storage_root: unsupported feature for parachain validation")
}

fn next_child_storage_key(&self, _: &ChildInfo, _: &[u8]) -> Option<Vec<u8>> {
fn next_child_storage_key(&mut self, _: &ChildInfo, _: &[u8]) -> Option<Vec<u8>> {
panic!("next_child_storage_key: unsupported feature for parachain validation")
}

fn next_storage_key(&self, _: &[u8]) -> Option<Vec<u8>> {
fn next_storage_key(&mut self, _: &[u8]) -> Option<Vec<u8>> {
panic!("next_storage_key: unsupported feature for parachain validation")
}

Expand Down
13 changes: 13 additions & 0 deletions prdoc/pr_1223.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
title: Optimize storage append operation

doc:
- audience: [Node Dev, Runtime Dev]
description: |
This pull request optimizes the storage append operation in the `OverlayedChanges`.
Before the internal buffer was cloned every time a new transaction was created. Cloning
the internal buffer is now only done when there is no other possibility. This should
improve the performance in situations like when depositing events from batched calls.

crates:
- name: sp-state-machine
bump: major
8 changes: 4 additions & 4 deletions substrate/client/executor/src/integration_tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,15 +178,15 @@ fn storage_should_work(wasm_method: WasmExecutionMethod) {
assert_eq!(output, b"all ok!".to_vec().encode());
}

let expected = TestExternalities::new(sp_core::storage::Storage {
let mut expected = TestExternalities::new(sp_core::storage::Storage {
top: map![
b"input".to_vec() => value,
b"foo".to_vec() => b"bar".to_vec(),
b"baz".to_vec() => b"bar".to_vec()
],
children_default: map![],
});
assert_eq!(ext, expected);
assert!(ext.eq(&mut expected));
}

test_wasm_execution!(clear_prefix_should_work);
Expand All @@ -208,15 +208,15 @@ fn clear_prefix_should_work(wasm_method: WasmExecutionMethod) {
assert_eq!(output, b"all ok!".to_vec().encode());
}

let expected = TestExternalities::new(sp_core::storage::Storage {
let mut expected = TestExternalities::new(sp_core::storage::Storage {
top: map![
b"aaa".to_vec() => b"1".to_vec(),
b"aab".to_vec() => b"2".to_vec(),
b"bbb".to_vec() => b"5".to_vec()
],
children_default: map![],
});
assert_eq!(expected, ext);
assert!(expected.eq(&mut ext));
}

test_wasm_execution!(blake2_256_should_work);
Expand Down
16 changes: 8 additions & 8 deletions substrate/primitives/externalities/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,24 +83,24 @@ pub trait Externalities: ExtensionStore {
fn set_offchain_storage(&mut self, key: &[u8], value: Option<&[u8]>);

/// Read runtime storage.
fn storage(&self, key: &[u8]) -> Option<Vec<u8>>;
fn storage(&mut self, key: &[u8]) -> Option<Vec<u8>>;

/// Get storage value hash.
///
/// This may be optimized for large values.
fn storage_hash(&self, key: &[u8]) -> Option<Vec<u8>>;
fn storage_hash(&mut self, key: &[u8]) -> Option<Vec<u8>>;

/// Get child storage value hash.
///
/// This may be optimized for large values.
///
/// Returns an `Option` that holds the SCALE encoded hash.
fn child_storage_hash(&self, child_info: &ChildInfo, key: &[u8]) -> Option<Vec<u8>>;
fn child_storage_hash(&mut self, child_info: &ChildInfo, key: &[u8]) -> Option<Vec<u8>>;

/// Read child runtime storage.
///
/// Returns an `Option` that holds the SCALE encoded hash.
fn child_storage(&self, child_info: &ChildInfo, key: &[u8]) -> Option<Vec<u8>>;
fn child_storage(&mut self, child_info: &ChildInfo, key: &[u8]) -> Option<Vec<u8>>;

/// Set storage entry `key` of current contract being called (effective immediately).
fn set_storage(&mut self, key: Vec<u8>, value: Vec<u8>) {
Expand All @@ -124,20 +124,20 @@ pub trait Externalities: ExtensionStore {
}

/// Whether a storage entry exists.
fn exists_storage(&self, key: &[u8]) -> bool {
fn exists_storage(&mut self, key: &[u8]) -> bool {
self.storage(key).is_some()
}

/// Whether a child storage entry exists.
fn exists_child_storage(&self, child_info: &ChildInfo, key: &[u8]) -> bool {
fn exists_child_storage(&mut self, child_info: &ChildInfo, key: &[u8]) -> bool {
self.child_storage(child_info, key).is_some()
}

/// Returns the key immediately following the given key, if it exists.
fn next_storage_key(&self, key: &[u8]) -> Option<Vec<u8>>;
fn next_storage_key(&mut self, key: &[u8]) -> Option<Vec<u8>>;

/// Returns the key immediately following the given key, if it exists, in child storage.
fn next_child_storage_key(&self, child_info: &ChildInfo, key: &[u8]) -> Option<Vec<u8>>;
fn next_child_storage_key(&mut self, child_info: &ChildInfo, key: &[u8]) -> Option<Vec<u8>>;

/// Clear an entire child storage.
///
Expand Down
12 changes: 6 additions & 6 deletions substrate/primitives/io/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ impl From<MultiRemovalResults> for KillStorageResult {
#[runtime_interface]
pub trait Storage {
/// Returns the data for `key` in the storage or `None` if the key can not be found.
fn get(&self, key: &[u8]) -> Option<bytes::Bytes> {
fn get(&mut self, key: &[u8]) -> Option<bytes::Bytes> {
self.storage(key).map(bytes::Bytes::from)
}

Expand All @@ -190,7 +190,7 @@ pub trait Storage {
/// doesn't exist at all.
/// If `value_out` length is smaller than the returned length, only `value_out` length bytes
/// are copied into `value_out`.
fn read(&self, key: &[u8], value_out: &mut [u8], value_offset: u32) -> Option<u32> {
fn read(&mut self, key: &[u8], value_out: &mut [u8], value_offset: u32) -> Option<u32> {
self.storage(key).map(|value| {
let value_offset = value_offset as usize;
let data = &value[value_offset.min(value.len())..];
Expand All @@ -211,7 +211,7 @@ pub trait Storage {
}

/// Check whether the given `key` exists in storage.
fn exists(&self, key: &[u8]) -> bool {
fn exists(&mut self, key: &[u8]) -> bool {
self.exists_storage(key)
}

Expand Down Expand Up @@ -387,7 +387,7 @@ pub trait DefaultChildStorage {
///
/// Parameter `storage_key` is the unprefixed location of the root of the child trie in the
/// parent trie. Result is `None` if the value for `key` in the child storage can not be found.
fn get(&self, storage_key: &[u8], key: &[u8]) -> Option<Vec<u8>> {
fn get(&mut self, storage_key: &[u8], key: &[u8]) -> Option<Vec<u8>> {
let child_info = ChildInfo::new_default(storage_key);
self.child_storage(&child_info, key).map(|s| s.to_vec())
}
Expand All @@ -400,7 +400,7 @@ pub trait DefaultChildStorage {
/// If `value_out` length is smaller than the returned length, only `value_out` length bytes
/// are copied into `value_out`.
fn read(
&self,
&mut self,
storage_key: &[u8],
key: &[u8],
value_out: &mut [u8],
Expand Down Expand Up @@ -478,7 +478,7 @@ pub trait DefaultChildStorage {
/// Check a child storage key.
///
/// Check whether the given `key` exists in default child defined at `storage_key`.
fn exists(&self, storage_key: &[u8], key: &[u8]) -> bool {
fn exists(&mut self, storage_key: &[u8], key: &[u8]) -> bool {
let child_info = ChildInfo::new_default(storage_key);
self.exists_child_storage(&child_info, key)
}
Expand Down
3 changes: 3 additions & 0 deletions substrate/primitives/state-machine/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,19 @@ sp-externalities = { path = "../externalities", default-features = false }
sp-panic-handler = { path = "../panic-handler", optional = true }
sp-trie = { path = "../trie", default-features = false }
trie-db = { version = "0.29.0", default-features = false }
arbitrary = { version = "1", features = ["derive"], optional = true }

[dev-dependencies]
array-bytes = "6.2.2"
pretty_assertions = "1.2.1"
rand = "0.8.5"
sp-runtime = { path = "../runtime" }
assert_matches = "1.5"
arbitrary = { version = "1", features = ["derive"] }

[features]
default = ["std"]
fuzzing = ["arbitrary"]
std = [
"codec/std",
"hash-db/std",
Expand Down
30 changes: 30 additions & 0 deletions substrate/primitives/state-machine/fuzz/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[package]
name = "sp-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"
sp-runtime = { path = "../../runtime" }

[dependencies.sp-state-machine]
path = ".."
features = ["fuzzing"]

# 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// This file is part of Substrate.

// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#![no_main]

use libfuzzer_sys::fuzz_target;
use sp_state_machine::fuzzing::{fuzz_append, FuzzAppendPayload};
use sp_runtime::traits::BlakeTwo256;

fuzz_target!(|data: FuzzAppendPayload| {
fuzz_append::<BlakeTwo256>(data);
});
Loading

0 comments on commit e5ceba5

Please sign in to comment.