Inner hashing of value in state trie (runtime versioning). (#9732)

* starting

* Updated from other branch.

* setting flag

* flag in storage struct

* fix flagging to access and insert.

* added todo to fix

* also missing serialize meta to storage proof

* extract meta.

* Isolate old trie layout.

* failing test that requires storing in meta when old hash scheme is used.

* old hash compatibility

* Db migrate.

* runing tests with both states when interesting.

* fix chain spec test with serde default.

* export state (missing trie function).

* Pending using new branch, lacking genericity on layout resolution.

* extract and set global meta

* Update to branch 4

* fix iterator with root flag (no longer insert node).

* fix trie root hashing of root

* complete basic backend.

* Remove old_hash meta from proof that do not use inner_hashing.

* fix trie test for empty (force layout on empty deltas).

* Root update fix.

* debug on meta

* Use trie key iteration that do not include value in proofs.

* switch default test ext to use inner hash.

* small integration test, and fix tx cache mgmt in ext.
test  failing

* Proof scenario at state-machine level.

* trace for db upgrade

* try different param

* act more like iter_from.

* Bigger batches.

* Update trie dependency.

* drafting codec changes and refact

* before removing unused branch no value alt hashing.
more work todo rename all flag var to alt_hash, and remove extrinsic
replace by storage query at every storage_root call.

* alt hashing only for branch with value.

* fix trie tests

* Hash of value include the encoded size.

* removing fields(broken)

* fix trie_stream to also include value length in inner hash.

* triedbmut only using alt type if inner hashing.

* trie_stream to also only use alt hashing type when actually alt hashing.

* Refactor meta state, logic should work with change of trie treshold.

* Remove NoMeta variant.

* Remove state_hashed trigger specific functions.

* pending switching to using threshold, new storage root api does not
make much sense.

* refactoring to use state from backend (not possible payload changes).

* Applying from previous state

* Remove default from storage, genesis need a special build.

* rem empty space

* Catch problem: when using triedb with default: we should not revert
nodes: otherwhise thing as trie codec cannot decode-encode without
changing state.

* fix compilation

* Right logic to avoid switch on reencode when default layout.

* Clean up some todos

* remove trie meta from root upstream

* update upstream and fix benches.

* split some long lines.

* UPdate trie crate to work with new design.

* Finish update to refactored upstream.

* update to latest triedb changes.

* Clean up.

* fix executor test.

* rust fmt from master.

* rust format.

* rustfmt

* fix

* start host function driven versioning

* update state-machine part

* still need access to state version from runtime

* state hash in mem: wrong

* direction likely correct, but passing call to code exec for genesis
init seem awkward.

* state version serialize in runtime, wrong approach, just initialize it
with no threshold for core api < 4 seems more proper.

* stateversion from runtime version (core api >= 4).

* update trie, fix tests

* unused import

* clean some TODOs

* Require RuntimeVersionOf for executor

* use RuntimeVersionOf to resolve genesis state version.

* update runtime version test

* fix state-machine tests

* TODO

* Use runtime version from storage wasm with fast sync.

* rustfmt

* fmt

* fix test

* revert useless changes.

* clean some unused changes

* fmt

* removing useless trait function.

* remove remaining reference to state_hash

* fix some imports

* Follow chain state version management.

* trie update, fix and constant threshold for trie layouts.

* update deps

* Update to latest trie pr changes.

* fix benches

* Verify proof requires right layout.

* update trie_root

* Update trie deps to  latest

* Update to latest trie versioning

* Removing patch

* update lock

* extrinsic for sc-service-test using layout v0.

* Adding RuntimeVersionOf to CallExecutor works.

* fmt

* error when resolving version and no wasm in storage.

* use existing utils to instantiate runtime code.

* Patch to delay runtime switch.

* Revert "Patch to delay runtime switch."

This reverts commit 67e55fee468f1a0cda853f5362b22e0d775786da.

* useless closure

* remove remaining state_hash variables.

* Remove outdated comment

* useless inner hash

* fmt

* fmt and opt-in feature to apply state change.

* feature gate core version, use new test feature for node and test node

* Use a 'State' api version instead of Core one.

* fix merge of test function

* use blake macro.

* Fix state api (require declaring the api in runtime).

* Opt out feature, fix macro for io to select a given version
instead of latest.

* run test nodes on new state.

* fix

* Apply review change (docs and error).

* fmt

* use explicit runtime_interface in doc test

* fix ui test

* fix doc test

* fmt

* use default for path and specname when resolving version.

* small review related changes.

* doc value size requirement.

* rename old_state feature

* Remove macro changes

* feature rename

* state version as host function parameter

* remove flag for client api

* fix tests

* switch storage chain proof to V1

* host functions, pass by state version enum

* use WrappedRuntimeCode

* start

* state_version in runtime version

* rust fmt

* Update storage proof of max size.

* fix runtime version rpc test

* right intent of convert from compat

* fix doc test

* fix doc test

* split proof

* decode without replay, and remove some reexports.

* Decode with compatibility by default.

* switch state_version to u8. And remove RuntimeVersionBasis.

* test

* use api when reading embedded version

* fix decode with apis

* extract core version instead

* test fix

* unused import

* review changes.

Co-authored-by: kianenigma <kian@parity.io>
This commit is contained in:
cheme
2021-12-24 09:54:07 +01:00
committed by GitHub
parent ea30c739ea
commit 4c651637f2
78 changed files with 1814 additions and 782 deletions
+166 -76
View File
@@ -45,31 +45,43 @@ pub use trie_db::proof::VerifyError;
use trie_db::proof::{generate_proof, verify_proof};
/// Various re-exports from the `trie-db` crate.
pub use trie_db::{
nibble_ops, CError, DBValue, Query, Recorder, Trie, TrieConfiguration, TrieDBIterator,
nibble_ops,
node::{NodePlan, ValuePlan},
CError, DBValue, Query, Recorder, Trie, TrieConfiguration, TrieDBIterator, TrieDBKeyIterator,
TrieLayout, TrieMut,
};
/// The Substrate format implementation of `TrieStream`.
pub use trie_stream::TrieStream;
#[derive(Default)]
/// substrate trie layout
pub struct Layout<H>(sp_std::marker::PhantomData<H>);
pub struct LayoutV0<H>(sp_std::marker::PhantomData<H>);
impl<H: Hasher> TrieLayout for Layout<H> {
/// substrate trie layout, with external value nodes.
pub struct LayoutV1<H>(sp_std::marker::PhantomData<H>);
impl<H> TrieLayout for LayoutV0<H>
where
H: Hasher,
{
const USE_EXTENSION: bool = false;
const ALLOW_EMPTY: bool = true;
const MAX_INLINE_VALUE: Option<u32> = None;
type Hash = H;
type Codec = NodeCodec<Self::Hash>;
}
impl<H: Hasher> TrieConfiguration for Layout<H> {
impl<H> TrieConfiguration for LayoutV0<H>
where
H: Hasher,
{
fn trie_root<I, A, B>(input: I) -> <Self::Hash as Hasher>::Out
where
I: IntoIterator<Item = (A, B)>,
A: AsRef<[u8]> + Ord,
B: AsRef<[u8]>,
{
trie_root::trie_root_no_extension::<H, TrieStream, _, _, _>(input)
trie_root::trie_root_no_extension::<H, TrieStream, _, _, _>(input, Self::MAX_INLINE_VALUE)
}
fn trie_root_unhashed<I, A, B>(input: I) -> Vec<u8>
@@ -78,7 +90,52 @@ impl<H: Hasher> TrieConfiguration for Layout<H> {
A: AsRef<[u8]> + Ord,
B: AsRef<[u8]>,
{
trie_root::unhashed_trie_no_extension::<H, TrieStream, _, _, _>(input)
trie_root::unhashed_trie_no_extension::<H, TrieStream, _, _, _>(
input,
Self::MAX_INLINE_VALUE,
)
}
fn encode_index(input: u32) -> Vec<u8> {
codec::Encode::encode(&codec::Compact(input))
}
}
impl<H> TrieLayout for LayoutV1<H>
where
H: Hasher,
{
const USE_EXTENSION: bool = false;
const ALLOW_EMPTY: bool = true;
const MAX_INLINE_VALUE: Option<u32> = Some(sp_core::storage::TRIE_VALUE_NODE_THRESHOLD);
type Hash = H;
type Codec = NodeCodec<Self::Hash>;
}
impl<H> TrieConfiguration for LayoutV1<H>
where
H: Hasher,
{
fn trie_root<I, A, B>(input: I) -> <Self::Hash as Hasher>::Out
where
I: IntoIterator<Item = (A, B)>,
A: AsRef<[u8]> + Ord,
B: AsRef<[u8]>,
{
trie_root::trie_root_no_extension::<H, TrieStream, _, _, _>(input, Self::MAX_INLINE_VALUE)
}
fn trie_root_unhashed<I, A, B>(input: I) -> Vec<u8>
where
I: IntoIterator<Item = (A, B)>,
A: AsRef<[u8]> + Ord,
B: AsRef<[u8]>,
{
trie_root::unhashed_trie_no_extension::<H, TrieStream, _, _, _>(
input,
Self::MAX_INLINE_VALUE,
)
}
fn encode_index(input: u32) -> Vec<u8> {
@@ -118,17 +175,22 @@ pub type TrieDBMut<'a, L> = trie_db::TrieDBMut<'a, L>;
pub type Lookup<'a, L, Q> = trie_db::Lookup<'a, L, Q>;
/// Hash type for a trie layout.
pub type TrieHash<L> = <<L as TrieLayout>::Hash as Hasher>::Out;
/// This module is for non generic definition of trie type.
/// Only the `Hasher` trait is generic in this case.
pub mod trie_types {
pub type Layout<H> = super::Layout<H>;
use super::*;
/// Persistent trie database read-access interface for the a given hasher.
pub type TrieDB<'a, H> = super::TrieDB<'a, Layout<H>>;
/// Read only V1 and V0 are compatible, thus we always use V1.
pub type TrieDB<'a, H> = super::TrieDB<'a, LayoutV1<H>>;
/// Persistent trie database write-access interface for the a given hasher.
pub type TrieDBMut<'a, H> = super::TrieDBMut<'a, Layout<H>>;
pub type TrieDBMutV0<'a, H> = super::TrieDBMut<'a, LayoutV0<H>>;
/// Persistent trie database write-access interface for the a given hasher.
pub type TrieDBMutV1<'a, H> = super::TrieDBMut<'a, LayoutV1<H>>;
/// Querying interface, as in `trie_db` but less generic.
pub type Lookup<'a, H, Q> = trie_db::Lookup<'a, Layout<H>, Q>;
pub type LookupV0<'a, H, Q> = trie_db::Lookup<'a, LayoutV0<H>, Q>;
/// Querying interface, as in `trie_db` but less generic.
pub type LookupV1<'a, H, Q> = trie_db::Lookup<'a, LayoutV1<H>, Q>;
/// As in `trie_db`, but less generic, error type for the crate.
pub type TrieError<H> = trie_db::TrieError<H, super::Error>;
}
@@ -141,16 +203,18 @@ pub mod trie_types {
/// For a key `K` that is included in the `db` a proof of inclusion is generated.
/// For a key `K` that is not included in the `db` a proof of non-inclusion is generated.
/// These can be later checked in `verify_trie_proof`.
pub fn generate_trie_proof<'a, L: TrieConfiguration, I, K, DB>(
pub fn generate_trie_proof<'a, L, I, K, DB>(
db: &DB,
root: TrieHash<L>,
keys: I,
) -> Result<Vec<Vec<u8>>, Box<TrieError<L>>>
where
L: TrieConfiguration,
I: IntoIterator<Item = &'a K>,
K: 'a + AsRef<[u8]>,
DB: hash_db::HashDBRef<L::Hash, trie_db::DBValue>,
{
// Can use default layout (read only).
let trie = TrieDB::<L>::new(db, &root)?;
generate_proof(&trie, keys)
}
@@ -163,17 +227,18 @@ where
/// checked for inclusion in the proof.
/// If the value is omitted (`(key, None)`), this key will be checked for non-inclusion in the
/// proof.
pub fn verify_trie_proof<'a, L: TrieConfiguration, I, K, V>(
pub fn verify_trie_proof<'a, L, I, K, V>(
root: &TrieHash<L>,
proof: &[Vec<u8>],
items: I,
) -> Result<(), VerifyError<TrieHash<L>, error::Error>>
) -> Result<(), VerifyError<TrieHash<L>, CError<L>>>
where
L: TrieConfiguration,
I: IntoIterator<Item = &'a (K, Option<V>)>,
K: 'a + AsRef<[u8]>,
V: 'a + AsRef<[u8]>,
{
verify_proof::<Layout<L::Hash>, _, _, _>(root, proof, items)
verify_proof::<L, _, _, _>(root, proof, items)
}
/// Determine a trie root given a hash DB and delta values.
@@ -207,28 +272,33 @@ where
}
/// Read a value from the trie.
pub fn read_trie_value<L: TrieConfiguration, DB: hash_db::HashDBRef<L::Hash, trie_db::DBValue>>(
pub fn read_trie_value<L, DB>(
db: &DB,
root: &TrieHash<L>,
key: &[u8],
) -> Result<Option<Vec<u8>>, Box<TrieError<L>>> {
TrieDB::<L>::new(&*db, root)?.get(key).map(|x| x.map(|val| val.to_vec()))
) -> Result<Option<Vec<u8>>, Box<TrieError<L>>>
where
L: TrieConfiguration,
DB: hash_db::HashDBRef<L::Hash, trie_db::DBValue>,
{
Ok(TrieDB::<L>::new(&*db, root)?.get(key).map(|x| x.map(|val| val.to_vec()))?)
}
/// Read a value from the trie with given Query.
pub fn read_trie_value_with<
L: TrieConfiguration,
Q: Query<L::Hash, Item = DBValue>,
DB: hash_db::HashDBRef<L::Hash, trie_db::DBValue>,
>(
pub fn read_trie_value_with<L, Q, DB>(
db: &DB,
root: &TrieHash<L>,
key: &[u8],
query: Q,
) -> Result<Option<Vec<u8>>, Box<TrieError<L>>> {
TrieDB::<L>::new(&*db, root)?
) -> Result<Option<Vec<u8>>, Box<TrieError<L>>>
where
L: TrieConfiguration,
Q: Query<L::Hash, Item = DBValue>,
DB: hash_db::HashDBRef<L::Hash, trie_db::DBValue>,
{
Ok(TrieDB::<L>::new(&*db, root)?
.get_with(key, query)
.map(|x| x.map(|val| val.to_vec()))
.map(|x| x.map(|val| val.to_vec()))?)
}
/// Determine the empty trie root.
@@ -319,7 +389,7 @@ where
}
/// Read a value from the child trie with given query.
pub fn read_child_trie_value_with<L: TrieConfiguration, Q: Query<L::Hash, Item = DBValue>, DB>(
pub fn read_child_trie_value_with<L, Q, DB>(
keyspace: &[u8],
db: &DB,
root_slice: &[u8],
@@ -327,6 +397,8 @@ pub fn read_child_trie_value_with<L: TrieConfiguration, Q: Query<L::Hash, Item =
query: Q,
) -> Result<Option<Vec<u8>>, Box<TrieError<L>>>
where
L: TrieConfiguration,
Q: Query<L::Hash, Item = DBValue>,
DB: hash_db::HashDBRef<L::Hash, trie_db::DBValue>,
{
let mut root = TrieHash::<L>::default();
@@ -444,11 +516,15 @@ where
/// Constants used into trie simplification codec.
mod trie_constants {
pub const EMPTY_TRIE: u8 = 0;
pub const NIBBLE_SIZE_BOUND: usize = u16::MAX as usize;
const FIRST_PREFIX: u8 = 0b_00 << 6;
pub const NIBBLE_SIZE_BOUND: usize = u16::max_value() as usize;
pub const LEAF_PREFIX_MASK: u8 = 0b_01 << 6;
pub const BRANCH_WITHOUT_MASK: u8 = 0b_10 << 6;
pub const BRANCH_WITH_MASK: u8 = 0b_11 << 6;
pub const EMPTY_TRIE: u8 = FIRST_PREFIX | (0b_00 << 4);
pub const ALT_HASHING_LEAF_PREFIX_MASK: u8 = FIRST_PREFIX | (0b_1 << 5);
pub const ALT_HASHING_BRANCH_WITH_MASK: u8 = FIRST_PREFIX | (0b_01 << 4);
pub const ESCAPE_COMPACT_HEADER: u8 = EMPTY_TRIE | 0b_00_01;
}
#[cfg(test)]
@@ -461,7 +537,11 @@ mod tests {
use trie_db::{DBValue, NodeCodec as NodeCodecT, Trie, TrieMut};
use trie_standardmap::{Alphabet, StandardMap, ValueMode};
type Layout = super::Layout<Blake2Hasher>;
type LayoutV0 = super::LayoutV0<Blake2Hasher>;
type LayoutV1 = super::LayoutV1<Blake2Hasher>;
type MemoryDBMeta<H> =
memory_db::MemoryDB<H, memory_db::HashKey<H>, trie_db::DBValue, MemTracker>;
fn hashed_null_node<T: TrieConfiguration>() -> TrieHash<T> {
<T::Codec as NodeCodecT>::hashed_null_node()
@@ -473,7 +553,7 @@ mod tests {
let d = T::trie_root_unhashed(input.clone());
println!("Data: {:#x?}, {:#x?}", d, Blake2Hasher::hash(&d[..]));
let persistent = {
let mut memdb = MemoryDB::default();
let mut memdb = MemoryDBMeta::default();
let mut root = Default::default();
let mut t = TrieDBMut::<T>::new(&mut memdb, &mut root);
for (x, y) in input.iter().rev() {
@@ -486,7 +566,7 @@ mod tests {
}
fn check_iteration<T: TrieConfiguration>(input: &Vec<(&[u8], &[u8])>) {
let mut memdb = MemoryDB::default();
let mut memdb = MemoryDBMeta::default();
let mut root = Default::default();
{
let mut t = TrieDBMut::<T>::new(&mut memdb, &mut root);
@@ -506,14 +586,21 @@ mod tests {
}
}
fn check_input(input: &Vec<(&[u8], &[u8])>) {
check_equivalent::<LayoutV0>(input);
check_iteration::<LayoutV0>(input);
check_equivalent::<LayoutV1>(input);
check_iteration::<LayoutV1>(input);
}
#[test]
fn default_trie_root() {
let mut db = MemoryDB::default();
let mut root = TrieHash::<Layout>::default();
let mut empty = TrieDBMut::<Layout>::new(&mut db, &mut root);
let mut root = TrieHash::<LayoutV1>::default();
let mut empty = TrieDBMut::<LayoutV1>::new(&mut db, &mut root);
empty.commit();
let root1 = empty.root().as_ref().to_vec();
let root2: Vec<u8> = Layout::trie_root::<_, Vec<u8>, Vec<u8>>(std::iter::empty())
let root2: Vec<u8> = LayoutV1::trie_root::<_, Vec<u8>, Vec<u8>>(std::iter::empty())
.as_ref()
.iter()
.cloned()
@@ -525,31 +612,27 @@ mod tests {
#[test]
fn empty_is_equivalent() {
let input: Vec<(&[u8], &[u8])> = vec![];
check_equivalent::<Layout>(&input);
check_iteration::<Layout>(&input);
check_input(&input);
}
#[test]
fn leaf_is_equivalent() {
let input: Vec<(&[u8], &[u8])> = vec![(&[0xaa][..], &[0xbb][..])];
check_equivalent::<Layout>(&input);
check_iteration::<Layout>(&input);
check_input(&input);
}
#[test]
fn branch_is_equivalent() {
let input: Vec<(&[u8], &[u8])> =
vec![(&[0xaa][..], &[0x10][..]), (&[0xba][..], &[0x11][..])];
check_equivalent::<Layout>(&input);
check_iteration::<Layout>(&input);
check_input(&input);
}
#[test]
fn extension_and_branch_is_equivalent() {
let input: Vec<(&[u8], &[u8])> =
vec![(&[0xaa][..], &[0x10][..]), (&[0xab][..], &[0x11][..])];
check_equivalent::<Layout>(&input);
check_iteration::<Layout>(&input);
check_input(&input);
}
#[test]
@@ -564,8 +647,7 @@ mod tests {
let mut d = st.make();
d.sort_by(|&(ref a, _), &(ref b, _)| a.cmp(b));
let dr = d.iter().map(|v| (&v.0[..], &v.1[..])).collect();
check_equivalent::<Layout>(&dr);
check_iteration::<Layout>(&dr);
check_input(&dr);
}
#[test]
@@ -575,8 +657,7 @@ mod tests {
(&[0xaa, 0xaa][..], &[0xaa][..]),
(&[0xaa, 0xbb][..], &[0xab][..]),
];
check_equivalent::<Layout>(&input);
check_iteration::<Layout>(&input);
check_input(&input);
}
#[test]
@@ -589,8 +670,7 @@ mod tests {
(&[0xbb, 0xbb][..], &[0xbb][..]),
(&[0xbb, 0xcc][..], &[0xbc][..]),
];
check_equivalent::<Layout>(&input);
check_iteration::<Layout>(&input);
check_input(&input);
}
#[test]
@@ -602,8 +682,7 @@ mod tests {
),
(&[0xba][..], &[0x11][..]),
];
check_equivalent::<Layout>(&input);
check_iteration::<Layout>(&input);
check_input(&input);
}
#[test]
@@ -618,15 +697,17 @@ mod tests {
&b"ABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABC"[..],
),
];
check_equivalent::<Layout>(&input);
check_iteration::<Layout>(&input);
check_input(&input);
}
fn populate_trie<'db, T: TrieConfiguration>(
fn populate_trie<'db, T>(
db: &'db mut dyn HashDB<T::Hash, DBValue>,
root: &'db mut TrieHash<T>,
v: &[(Vec<u8>, Vec<u8>)],
) -> TrieDBMut<'db, T> {
) -> TrieDBMut<'db, T>
where
T: TrieConfiguration,
{
let mut t = TrieDBMut::<T>::new(db, root);
for i in 0..v.len() {
let key: &[u8] = &v[i].0;
@@ -648,8 +729,12 @@ mod tests {
#[test]
fn random_should_work() {
random_should_work_inner::<LayoutV1>();
random_should_work_inner::<LayoutV0>();
}
fn random_should_work_inner<L: TrieConfiguration>() {
let mut seed = <Blake2Hasher as Hasher>::Out::zero();
for test_i in 0..10000 {
for test_i in 0..10_000 {
if test_i % 50 == 0 {
println!("{:?} of 10000 stress tests done", test_i);
}
@@ -662,10 +747,11 @@ mod tests {
}
.make_with(seed.as_fixed_bytes_mut());
let real = Layout::trie_root(x.clone());
let real = L::trie_root(x.clone());
let mut memdb = MemoryDB::default();
let mut root = Default::default();
let mut memtrie = populate_trie::<Layout>(&mut memdb, &mut root, &x);
let mut memtrie = populate_trie::<L>(&mut memdb, &mut root, &x);
memtrie.commit();
if *memtrie.root() != real {
@@ -677,9 +763,9 @@ mod tests {
}
}
assert_eq!(*memtrie.root(), real);
unpopulate_trie::<Layout>(&mut memtrie, &x);
unpopulate_trie::<L>(&mut memtrie, &x);
memtrie.commit();
let hashed_null_node = hashed_null_node::<Layout>();
let hashed_null_node = hashed_null_node::<L>();
if *memtrie.root() != hashed_null_node {
println!("- TRIE MISMATCH");
println!("");
@@ -699,7 +785,7 @@ mod tests {
#[test]
fn codec_trie_empty() {
let input: Vec<(&[u8], &[u8])> = vec![];
let trie = Layout::trie_root_unhashed::<_, _, _>(input);
let trie = LayoutV1::trie_root_unhashed(input);
println!("trie: {:#x?}", trie);
assert_eq!(trie, vec![0x0]);
}
@@ -707,7 +793,7 @@ mod tests {
#[test]
fn codec_trie_single_tuple() {
let input = vec![(vec![0xaa], vec![0xbb])];
let trie = Layout::trie_root_unhashed::<_, _, _>(input);
let trie = LayoutV1::trie_root_unhashed(input);
println!("trie: {:#x?}", trie);
assert_eq!(
trie,
@@ -723,7 +809,7 @@ mod tests {
#[test]
fn codec_trie_two_tuples_disjoint_keys() {
let input = vec![(&[0x48, 0x19], &[0xfe]), (&[0x13, 0x14], &[0xff])];
let trie = Layout::trie_root_unhashed::<_, _, _>(input);
let trie = LayoutV1::trie_root_unhashed(input);
println!("trie: {:#x?}", trie);
let mut ex = Vec::<u8>::new();
ex.push(0x80); // branch, no value (0b_10..) no nibble
@@ -747,6 +833,10 @@ mod tests {
#[test]
fn iterator_works() {
iterator_works_inner::<LayoutV1>();
iterator_works_inner::<LayoutV0>();
}
fn iterator_works_inner<Layout: TrieConfiguration>() {
let pairs = vec![
(hex!("0103000000000000000464").to_vec(), hex!("0400000000").to_vec()),
(hex!("0103000000000000000469").to_vec(), hex!("0401000000").to_vec()),
@@ -777,15 +867,15 @@ mod tests {
let mut memdb = MemoryDB::default();
let mut root = Default::default();
populate_trie::<Layout>(&mut memdb, &mut root, &pairs);
populate_trie::<LayoutV1>(&mut memdb, &mut root, &pairs);
let non_included_key: Vec<u8> = hex!("0909").to_vec();
let proof =
generate_trie_proof::<Layout, _, _, _>(&memdb, root, &[non_included_key.clone()])
generate_trie_proof::<LayoutV1, _, _, _>(&memdb, root, &[non_included_key.clone()])
.unwrap();
// Verifying that the K was not included into the trie should work.
assert!(verify_trie_proof::<Layout, _, _, Vec<u8>>(
assert!(verify_trie_proof::<LayoutV1, _, _, Vec<u8>>(
&root,
&proof,
&[(non_included_key.clone(), None)],
@@ -793,7 +883,7 @@ mod tests {
.is_ok());
// Verifying that the K was included into the trie should fail.
assert!(verify_trie_proof::<Layout, _, _, Vec<u8>>(
assert!(verify_trie_proof::<LayoutV1, _, _, Vec<u8>>(
&root,
&proof,
&[(non_included_key, Some(hex!("1010").to_vec()))],
@@ -810,13 +900,13 @@ mod tests {
let mut memdb = MemoryDB::default();
let mut root = Default::default();
populate_trie::<Layout>(&mut memdb, &mut root, &pairs);
populate_trie::<LayoutV1>(&mut memdb, &mut root, &pairs);
let proof =
generate_trie_proof::<Layout, _, _, _>(&memdb, root, &[pairs[0].0.clone()]).unwrap();
generate_trie_proof::<LayoutV1, _, _, _>(&memdb, root, &[pairs[0].0.clone()]).unwrap();
// Check that a K, V included into the proof are verified.
assert!(verify_trie_proof::<Layout, _, _, _>(
assert!(verify_trie_proof::<LayoutV1, _, _, _>(
&root,
&proof,
&[(pairs[0].0.clone(), Some(pairs[0].1.clone()))]
@@ -824,7 +914,7 @@ mod tests {
.is_ok());
// Absence of the V is not verified with the proof that has K, V included.
assert!(verify_trie_proof::<Layout, _, _, Vec<u8>>(
assert!(verify_trie_proof::<LayoutV1, _, _, Vec<u8>>(
&root,
&proof,
&[(pairs[0].0.clone(), None)]
@@ -832,7 +922,7 @@ mod tests {
.is_err());
// K not included into the trie is not verified.
assert!(verify_trie_proof::<Layout, _, _, _>(
assert!(verify_trie_proof::<LayoutV1, _, _, _>(
&root,
&proof,
&[(hex!("4242").to_vec(), Some(pairs[0].1.clone()))]
@@ -840,7 +930,7 @@ mod tests {
.is_err());
// K included into the trie but not included into the proof is not verified.
assert!(verify_trie_proof::<Layout, _, _, _>(
assert!(verify_trie_proof::<LayoutV1, _, _, _>(
&root,
&proof,
&[(pairs[1].0.clone(), Some(pairs[1].1.clone()))]
@@ -865,13 +955,13 @@ mod tests {
.unwrap();
let proof_db = proof.into_memory_db::<Blake2Hasher>();
let first_storage_root = delta_trie_root::<Layout, _, _, _, _, _>(
let first_storage_root = delta_trie_root::<LayoutV0, _, _, _, _, _>(
&mut proof_db.clone(),
storage_root,
valid_delta,
)
.unwrap();
let second_storage_root = delta_trie_root::<Layout, _, _, _, _, _>(
let second_storage_root = delta_trie_root::<LayoutV0, _, _, _, _, _>(
&mut proof_db.clone(),
storage_root,
invalid_delta,
+87 -27
View File
@@ -24,7 +24,7 @@ use hash_db::Hasher;
use sp_std::{borrow::Borrow, marker::PhantomData, ops::Range, vec::Vec};
use trie_db::{
self, nibble_ops,
node::{NibbleSlicePlan, NodeHandlePlan, NodePlan},
node::{NibbleSlicePlan, NodeHandlePlan, NodePlan, Value, ValuePlan},
ChildReference, NodeCodec as NodeCodecT, Partial,
};
@@ -80,7 +80,11 @@ impl<'a> Input for ByteSliceInput<'a> {
#[derive(Default, Clone)]
pub struct NodeCodec<H>(PhantomData<H>);
impl<H: Hasher> NodeCodecT for NodeCodec<H> {
impl<H> NodeCodecT for NodeCodec<H>
where
H: Hasher,
{
const ESCAPE_HEADER: Option<u8> = Some(trie_constants::ESCAPE_COMPACT_HEADER);
type Error = Error;
type HashOut = H::Out;
@@ -88,11 +92,22 @@ impl<H: Hasher> NodeCodecT for NodeCodec<H> {
H::hash(<Self as NodeCodecT>::empty_node())
}
fn decode_plan(data: &[u8]) -> sp_std::result::Result<NodePlan, Self::Error> {
fn decode_plan(data: &[u8]) -> Result<NodePlan, Self::Error> {
let mut input = ByteSliceInput::new(data);
match NodeHeader::decode(&mut input)? {
let header = NodeHeader::decode(&mut input)?;
let contains_hash = header.contains_hash_of_value();
let branch_has_value = if let NodeHeader::Branch(has_value, _) = &header {
*has_value
} else {
// hashed_value_branch
true
};
match header {
NodeHeader::Null => Ok(NodePlan::Empty),
NodeHeader::Branch(has_value, nibble_count) => {
NodeHeader::HashedValueBranch(nibble_count) | NodeHeader::Branch(_, nibble_count) => {
let padding = nibble_count % nibble_ops::NIBBLE_PER_BYTE != 0;
// check that the padding is valid (if any)
if padding && nibble_ops::pad_left(data[input.offset]) != 0 {
@@ -105,9 +120,13 @@ impl<H: Hasher> NodeCodecT for NodeCodec<H> {
let partial_padding = nibble_ops::number_padding(nibble_count);
let bitmap_range = input.take(BITMAP_LENGTH)?;
let bitmap = Bitmap::decode(&data[bitmap_range])?;
let value = if has_value {
let count = <Compact<u32>>::decode(&mut input)?.0 as usize;
Some(input.take(count)?)
let value = if branch_has_value {
Some(if contains_hash {
ValuePlan::Node(input.take(H::LENGTH)?)
} else {
let count = <Compact<u32>>::decode(&mut input)?.0 as usize;
ValuePlan::Inline(input.take(count)?)
})
} else {
None
};
@@ -132,7 +151,7 @@ impl<H: Hasher> NodeCodecT for NodeCodec<H> {
children,
})
},
NodeHeader::Leaf(nibble_count) => {
NodeHeader::HashedValueLeaf(nibble_count) | NodeHeader::Leaf(nibble_count) => {
let padding = nibble_count % nibble_ops::NIBBLE_PER_BYTE != 0;
// check that the padding is valid (if any)
if padding && nibble_ops::pad_left(data[input.offset]) != 0 {
@@ -143,10 +162,16 @@ impl<H: Hasher> NodeCodecT for NodeCodec<H> {
nibble_ops::NIBBLE_PER_BYTE,
)?;
let partial_padding = nibble_ops::number_padding(nibble_count);
let count = <Compact<u32>>::decode(&mut input)?.0 as usize;
let value = if contains_hash {
ValuePlan::Node(input.take(H::LENGTH)?)
} else {
let count = <Compact<u32>>::decode(&mut input)?.0 as usize;
ValuePlan::Inline(input.take(count)?)
};
Ok(NodePlan::Leaf {
partial: NibbleSlicePlan::new(partial, partial_padding),
value: input.take(count)?,
value,
})
},
}
@@ -160,9 +185,23 @@ impl<H: Hasher> NodeCodecT for NodeCodec<H> {
&[trie_constants::EMPTY_TRIE]
}
fn leaf_node(partial: Partial, value: &[u8]) -> Vec<u8> {
let mut output = partial_encode(partial, NodeKind::Leaf);
value.encode_to(&mut output);
fn leaf_node(partial: Partial, value: Value) -> Vec<u8> {
let contains_hash = matches!(&value, Value::Node(..));
let mut output = if contains_hash {
partial_encode(partial, NodeKind::HashedValueLeaf)
} else {
partial_encode(partial, NodeKind::Leaf)
};
match value {
Value::Inline(value) => {
Compact(value.len() as u32).encode_to(&mut output);
output.extend_from_slice(value);
},
Value::Node(hash, _) => {
debug_assert!(hash.len() == H::LENGTH);
output.extend_from_slice(hash);
},
}
output
}
@@ -171,33 +210,46 @@ impl<H: Hasher> NodeCodecT for NodeCodec<H> {
_nbnibble: usize,
_child: ChildReference<<H as Hasher>::Out>,
) -> Vec<u8> {
unreachable!()
unreachable!("No extension codec.")
}
fn branch_node(
_children: impl Iterator<Item = impl Borrow<Option<ChildReference<<H as Hasher>::Out>>>>,
_maybe_value: Option<&[u8]>,
_maybe_value: Option<Value>,
) -> Vec<u8> {
unreachable!()
unreachable!("No extension codec.")
}
fn branch_node_nibbled(
partial: impl Iterator<Item = u8>,
number_nibble: usize,
children: impl Iterator<Item = impl Borrow<Option<ChildReference<<H as Hasher>::Out>>>>,
maybe_value: Option<&[u8]>,
value: Option<Value>,
) -> Vec<u8> {
let mut output = if maybe_value.is_some() {
partial_from_iterator_encode(partial, number_nibble, NodeKind::BranchWithValue)
} else {
partial_from_iterator_encode(partial, number_nibble, NodeKind::BranchNoValue)
let contains_hash = matches!(&value, Some(Value::Node(..)));
let mut output = match (&value, contains_hash) {
(&None, _) =>
partial_from_iterator_encode(partial, number_nibble, NodeKind::BranchNoValue),
(_, false) =>
partial_from_iterator_encode(partial, number_nibble, NodeKind::BranchWithValue),
(_, true) =>
partial_from_iterator_encode(partial, number_nibble, NodeKind::HashedValueBranch),
};
let bitmap_index = output.len();
let mut bitmap: [u8; BITMAP_LENGTH] = [0; BITMAP_LENGTH];
(0..BITMAP_LENGTH).for_each(|_| output.push(0));
if let Some(value) = maybe_value {
value.encode_to(&mut output);
};
match value {
Some(Value::Inline(value)) => {
Compact(value.len() as u32).encode_to(&mut output);
output.extend_from_slice(value);
},
Some(Value::Node(hash, _)) => {
debug_assert!(hash.len() == H::LENGTH);
output.extend_from_slice(hash);
},
None => (),
}
Bitmap::encode(
children.map(|maybe_child| match maybe_child.borrow() {
Some(ChildReference::Hash(h)) => {
@@ -229,11 +281,15 @@ fn partial_from_iterator_encode<I: Iterator<Item = u8>>(
) -> Vec<u8> {
let nibble_count = sp_std::cmp::min(trie_constants::NIBBLE_SIZE_BOUND, nibble_count);
let mut output = Vec::with_capacity(3 + (nibble_count / nibble_ops::NIBBLE_PER_BYTE));
let mut output = Vec::with_capacity(4 + (nibble_count / nibble_ops::NIBBLE_PER_BYTE));
match node_kind {
NodeKind::Leaf => NodeHeader::Leaf(nibble_count).encode_to(&mut output),
NodeKind::BranchWithValue => NodeHeader::Branch(true, nibble_count).encode_to(&mut output),
NodeKind::BranchNoValue => NodeHeader::Branch(false, nibble_count).encode_to(&mut output),
NodeKind::HashedValueLeaf =>
NodeHeader::HashedValueLeaf(nibble_count).encode_to(&mut output),
NodeKind::HashedValueBranch =>
NodeHeader::HashedValueBranch(nibble_count).encode_to(&mut output),
};
output.extend(partial);
output
@@ -247,11 +303,15 @@ fn partial_encode(partial: Partial, node_kind: NodeKind) -> Vec<u8> {
let nibble_count = sp_std::cmp::min(trie_constants::NIBBLE_SIZE_BOUND, nibble_count);
let mut output = Vec::with_capacity(3 + partial.1.len());
let mut output = Vec::with_capacity(4 + partial.1.len());
match node_kind {
NodeKind::Leaf => NodeHeader::Leaf(nibble_count).encode_to(&mut output),
NodeKind::BranchWithValue => NodeHeader::Branch(true, nibble_count).encode_to(&mut output),
NodeKind::BranchNoValue => NodeHeader::Branch(false, nibble_count).encode_to(&mut output),
NodeKind::HashedValueLeaf =>
NodeHeader::HashedValueLeaf(nibble_count).encode_to(&mut output),
NodeKind::HashedValueBranch =>
NodeHeader::HashedValueBranch(nibble_count).encode_to(&mut output),
};
if number_nibble_encoded > 0 {
output.push(nibble_ops::pad_right((partial.0).1));
+74 -19
View File
@@ -25,8 +25,20 @@ use sp_std::iter::once;
#[derive(Copy, Clone, PartialEq, Eq, sp_core::RuntimeDebug)]
pub(crate) enum NodeHeader {
Null,
// contains wether there is a value and nibble count
Branch(bool, usize),
// contains nibble count
Leaf(usize),
// contains nibble count.
HashedValueBranch(usize),
// contains nibble count.
HashedValueLeaf(usize),
}
impl NodeHeader {
pub(crate) fn contains_hash_of_value(&self) -> bool {
matches!(self, NodeHeader::HashedValueBranch(_) | NodeHeader::HashedValueLeaf(_))
}
}
/// NodeHeader without content
@@ -34,6 +46,8 @@ pub(crate) enum NodeKind {
Leaf,
BranchNoValue,
BranchWithValue,
HashedValueLeaf,
HashedValueBranch,
}
impl Encode for NodeHeader {
@@ -41,11 +55,27 @@ impl Encode for NodeHeader {
match self {
NodeHeader::Null => output.push_byte(trie_constants::EMPTY_TRIE),
NodeHeader::Branch(true, nibble_count) =>
encode_size_and_prefix(*nibble_count, trie_constants::BRANCH_WITH_MASK, output),
NodeHeader::Branch(false, nibble_count) =>
encode_size_and_prefix(*nibble_count, trie_constants::BRANCH_WITHOUT_MASK, output),
encode_size_and_prefix(*nibble_count, trie_constants::BRANCH_WITH_MASK, 2, output),
NodeHeader::Branch(false, nibble_count) => encode_size_and_prefix(
*nibble_count,
trie_constants::BRANCH_WITHOUT_MASK,
2,
output,
),
NodeHeader::Leaf(nibble_count) =>
encode_size_and_prefix(*nibble_count, trie_constants::LEAF_PREFIX_MASK, output),
encode_size_and_prefix(*nibble_count, trie_constants::LEAF_PREFIX_MASK, 2, output),
NodeHeader::HashedValueBranch(nibble_count) => encode_size_and_prefix(
*nibble_count,
trie_constants::ALT_HASHING_BRANCH_WITH_MASK,
4,
output,
),
NodeHeader::HashedValueLeaf(nibble_count) => encode_size_and_prefix(
*nibble_count,
trie_constants::ALT_HASHING_LEAF_PREFIX_MASK,
3,
output,
),
}
}
}
@@ -59,13 +89,22 @@ impl Decode for NodeHeader {
return Ok(NodeHeader::Null)
}
match i & (0b11 << 6) {
trie_constants::LEAF_PREFIX_MASK => Ok(NodeHeader::Leaf(decode_size(i, input)?)),
trie_constants::BRANCH_WITHOUT_MASK =>
Ok(NodeHeader::Branch(false, decode_size(i, input)?)),
trie_constants::LEAF_PREFIX_MASK => Ok(NodeHeader::Leaf(decode_size(i, input, 2)?)),
trie_constants::BRANCH_WITH_MASK =>
Ok(NodeHeader::Branch(true, decode_size(i, input)?)),
// do not allow any special encoding
_ => Err("Unallowed encoding".into()),
Ok(NodeHeader::Branch(true, decode_size(i, input, 2)?)),
trie_constants::BRANCH_WITHOUT_MASK =>
Ok(NodeHeader::Branch(false, decode_size(i, input, 2)?)),
trie_constants::EMPTY_TRIE => {
if i & (0b111 << 5) == trie_constants::ALT_HASHING_LEAF_PREFIX_MASK {
Ok(NodeHeader::HashedValueLeaf(decode_size(i, input, 3)?))
} else if i & (0b1111 << 4) == trie_constants::ALT_HASHING_BRANCH_WITH_MASK {
Ok(NodeHeader::HashedValueBranch(decode_size(i, input, 4)?))
} else {
// do not allow any special encoding
Err("Unallowed encoding".into())
}
},
_ => unreachable!(),
}
}
}
@@ -73,12 +112,20 @@ impl Decode for NodeHeader {
/// Returns an iterator over encoded bytes for node header and size.
/// Size encoding allows unlimited, length inefficient, representation, but
/// is bounded to 16 bit maximum value to avoid possible DOS.
pub(crate) fn size_and_prefix_iterator(size: usize, prefix: u8) -> impl Iterator<Item = u8> {
pub(crate) fn size_and_prefix_iterator(
size: usize,
prefix: u8,
prefix_mask: usize,
) -> impl Iterator<Item = u8> {
let size = sp_std::cmp::min(trie_constants::NIBBLE_SIZE_BOUND, size);
let l1 = sp_std::cmp::min(62, size);
let (first_byte, mut rem) =
if size == l1 { (once(prefix + l1 as u8), 0) } else { (once(prefix + 63), size - l1) };
let max_value = 255u8 >> prefix_mask;
let l1 = sp_std::cmp::min((max_value as usize).saturating_sub(1), size);
let (first_byte, mut rem) = if size == l1 {
(once(prefix + l1 as u8), 0)
} else {
(once(prefix + max_value as u8), size - l1)
};
let next_bytes = move || {
if rem > 0 {
if rem < 256 {
@@ -97,16 +144,24 @@ pub(crate) fn size_and_prefix_iterator(size: usize, prefix: u8) -> impl Iterator
}
/// Encodes size and prefix to a stream output.
fn encode_size_and_prefix<W: Output + ?Sized>(size: usize, prefix: u8, out: &mut W) {
for b in size_and_prefix_iterator(size, prefix) {
fn encode_size_and_prefix<W>(size: usize, prefix: u8, prefix_mask: usize, out: &mut W)
where
W: Output + ?Sized,
{
for b in size_and_prefix_iterator(size, prefix, prefix_mask) {
out.push_byte(b)
}
}
/// Decode size only from stream input and header byte.
fn decode_size(first: u8, input: &mut impl Input) -> Result<usize, codec::Error> {
let mut result = (first & 255u8 >> 2) as usize;
if result < 63 {
fn decode_size(
first: u8,
input: &mut impl Input,
prefix_mask: usize,
) -> Result<usize, codec::Error> {
let max_value = 255u8 >> prefix_mask;
let mut result = (first & max_value) as usize;
if result < max_value as usize {
return Ok(result)
}
result -= 1;
@@ -19,6 +19,9 @@ use codec::{Decode, Encode};
use hash_db::{HashDB, Hasher};
use scale_info::TypeInfo;
use sp_std::vec::Vec;
// Note that `LayoutV1` usage here (proof compaction) is compatible
// with `LayoutV0`.
use crate::LayoutV1 as Layout;
/// A proof that some set of key-value pairs are included in the storage trie. The proof contains
/// the storage values so that the partial storage backend can be reconstructed by a verifier that
@@ -95,8 +98,8 @@ impl StorageProof {
pub fn into_compact_proof<H: Hasher>(
self,
root: H::Out,
) -> Result<CompactProof, crate::CompactProofError<crate::Layout<H>>> {
crate::encode_compact::<crate::Layout<H>>(self, root)
) -> Result<CompactProof, crate::CompactProofError<Layout<H>>> {
crate::encode_compact::<Layout<H>>(self, root)
}
/// Returns the estimated encoded size of the compact proof.
@@ -124,9 +127,9 @@ impl CompactProof {
pub fn to_storage_proof<H: Hasher>(
&self,
expected_root: Option<&H::Out>,
) -> Result<(StorageProof, H::Out), crate::CompactProofError<crate::Layout<H>>> {
) -> Result<(StorageProof, H::Out), crate::CompactProofError<Layout<H>>> {
let mut db = crate::MemoryDB::<H>::new(&[]);
let root = crate::decode_compact::<crate::Layout<H>, _, _>(
let root = crate::decode_compact::<Layout<H>, _, _>(
&mut db,
self.iter_compact_encoded_nodes(),
expected_root,
+2 -4
View File
@@ -112,8 +112,7 @@ where
I: IntoIterator<Item = &'a [u8]>,
{
let mut nodes_iter = encoded.into_iter();
let (top_root, _nb_used) =
trie_db::decode_compact_from_iter::<L, _, _, _>(db, &mut nodes_iter)?;
let (top_root, _nb_used) = trie_db::decode_compact_from_iter::<L, _, _>(db, &mut nodes_iter)?;
// Only check root if expected root is passed as argument.
if let Some(expected_root) = expected_root {
@@ -164,8 +163,7 @@ where
let mut nodes_iter = nodes_iter.peekable();
for child_root in child_tries.into_iter() {
if previous_extracted_child_trie.is_none() && nodes_iter.peek().is_some() {
let (top_root, _) =
trie_db::decode_compact_from_iter::<L, _, _, _>(db, &mut nodes_iter)?;
let (top_root, _) = trie_db::decode_compact_from_iter::<L, _, _>(db, &mut nodes_iter)?;
previous_extracted_child_trie = Some(top_root);
}
+44 -37
View File
@@ -18,21 +18,18 @@
//! `TrieStream` implementation for Substrate's trie format.
use crate::{
node_codec::Bitmap,
node_header::{size_and_prefix_iterator, NodeKind},
trie_constants,
};
use codec::Encode;
use codec::{Compact, Encode};
use hash_db::Hasher;
use sp_std::vec::Vec;
use trie_root;
const BRANCH_NODE_NO_VALUE: u8 = 254;
const BRANCH_NODE_WITH_VALUE: u8 = 255;
#[derive(Default, Clone)]
/// Codec-flavored TrieStream.
pub struct TrieStream {
/// Current node buffer.
buffer: Vec<u8>,
}
@@ -60,51 +57,76 @@ fn fuse_nibbles_node<'a>(nibbles: &'a [u8], kind: NodeKind) -> impl Iterator<Ite
let size = sp_std::cmp::min(trie_constants::NIBBLE_SIZE_BOUND, nibbles.len());
let iter_start = match kind {
NodeKind::Leaf => size_and_prefix_iterator(size, trie_constants::LEAF_PREFIX_MASK),
NodeKind::Leaf => size_and_prefix_iterator(size, trie_constants::LEAF_PREFIX_MASK, 2),
NodeKind::BranchNoValue =>
size_and_prefix_iterator(size, trie_constants::BRANCH_WITHOUT_MASK),
size_and_prefix_iterator(size, trie_constants::BRANCH_WITHOUT_MASK, 2),
NodeKind::BranchWithValue =>
size_and_prefix_iterator(size, trie_constants::BRANCH_WITH_MASK),
size_and_prefix_iterator(size, trie_constants::BRANCH_WITH_MASK, 2),
NodeKind::HashedValueLeaf =>
size_and_prefix_iterator(size, trie_constants::ALT_HASHING_LEAF_PREFIX_MASK, 3),
NodeKind::HashedValueBranch =>
size_and_prefix_iterator(size, trie_constants::ALT_HASHING_BRANCH_WITH_MASK, 4),
};
iter_start
.chain(if nibbles.len() % 2 == 1 { Some(nibbles[0]) } else { None })
.chain(nibbles[nibbles.len() % 2..].chunks(2).map(|ch| ch[0] << 4 | ch[1]))
}
use trie_root::Value as TrieStreamValue;
impl trie_root::TrieStream for TrieStream {
fn new() -> Self {
TrieStream { buffer: Vec::new() }
Self { buffer: Vec::new() }
}
fn append_empty_data(&mut self) {
self.buffer.push(trie_constants::EMPTY_TRIE);
}
fn append_leaf(&mut self, key: &[u8], value: &[u8]) {
self.buffer.extend(fuse_nibbles_node(key, NodeKind::Leaf));
value.encode_to(&mut self.buffer);
fn append_leaf(&mut self, key: &[u8], value: TrieStreamValue) {
let kind = match &value {
TrieStreamValue::Inline(..) => NodeKind::Leaf,
TrieStreamValue::Node(..) => NodeKind::HashedValueLeaf,
};
self.buffer.extend(fuse_nibbles_node(key, kind));
match &value {
TrieStreamValue::Inline(value) => {
Compact(value.len() as u32).encode_to(&mut self.buffer);
self.buffer.extend_from_slice(value);
},
TrieStreamValue::Node(hash) => {
self.buffer.extend_from_slice(hash.as_slice());
},
};
}
fn begin_branch(
&mut self,
maybe_partial: Option<&[u8]>,
maybe_value: Option<&[u8]>,
maybe_value: Option<TrieStreamValue>,
has_children: impl Iterator<Item = bool>,
) {
if let Some(partial) = maybe_partial {
if maybe_value.is_some() {
self.buffer.extend(fuse_nibbles_node(partial, NodeKind::BranchWithValue));
} else {
self.buffer.extend(fuse_nibbles_node(partial, NodeKind::BranchNoValue));
}
let kind = match &maybe_value {
None => NodeKind::BranchNoValue,
Some(TrieStreamValue::Inline(..)) => NodeKind::BranchWithValue,
Some(TrieStreamValue::Node(..)) => NodeKind::HashedValueBranch,
};
self.buffer.extend(fuse_nibbles_node(partial, kind));
let bm = branch_node_bit_mask(has_children);
self.buffer.extend([bm.0, bm.1].iter());
} else {
debug_assert!(false, "trie stream codec only for no extension trie");
self.buffer.extend(&branch_node(maybe_value.is_some(), has_children));
unreachable!("trie stream codec only for no extension trie");
}
if let Some(value) = maybe_value {
value.encode_to(&mut self.buffer);
match maybe_value {
None => (),
Some(TrieStreamValue::Inline(value)) => {
Compact(value.len() as u32).encode_to(&mut self.buffer);
self.buffer.extend_from_slice(value);
},
Some(TrieStreamValue::Node(hash)) => {
self.buffer.extend_from_slice(hash.as_slice());
},
}
}
@@ -124,18 +146,3 @@ impl trie_root::TrieStream for TrieStream {
self.buffer
}
}
fn branch_node(has_value: bool, has_children: impl Iterator<Item = bool>) -> [u8; 3] {
let mut result = [0, 0, 0];
branch_node_buffered(has_value, has_children, &mut result[..]);
result
}
fn branch_node_buffered<I>(has_value: bool, has_children: I, output: &mut [u8])
where
I: Iterator<Item = bool>,
{
let first = if has_value { BRANCH_NODE_WITH_VALUE } else { BRANCH_NODE_NO_VALUE };
output[0] = first;
Bitmap::encode(has_children, &mut output[1..]);
}