Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Commit

Permalink
Compact proof utilities in sp_trie. (#8574)
Browse files Browse the repository at this point in the history
* validation extension in sp_io

* need paths

* arc impl

* missing host function in executor

* io to pkdot

* decode function.

* encode primitive.

* trailing tab

* multiple patch

* fix child trie logic

* restore master versionning

* bench compact proof size

* trie-db 22.3 is needed

* line width

* split line

* fixes for bench (additional root may not be needed as original issue was
with empty proof).

* revert compact from block size calculation.

* New error type for compression.

* Adding test (incomplete (failing)).
Also lacking real proof checking (no good primitives in sp-trie crate).

* There is currently no proof recording utility in sp_trie, removing
test.

* small test of child root in proof without a child proof.

* remove empty test.

* remove non compact proof size

* Missing revert.

* proof method to encode decode.
  • Loading branch information
cheme authored and s3krit committed Jun 8, 2021
1 parent 37bb3ae commit 48e3ec0
Show file tree
Hide file tree
Showing 8 changed files with 407 additions and 9 deletions.
31 changes: 28 additions & 3 deletions client/db/src/bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ pub struct BenchmarkingState<B: BlockT> {
read_write_tracker: RefCell<ReadWriteTracker>,
whitelist: RefCell<Vec<TrackedStorageKey>>,
proof_recorder: Option<ProofRecorder<B::Hash>>,
proof_recorder_root: Cell<B::Hash>,
}

impl<B: BlockT> BenchmarkingState<B> {
Expand All @@ -129,7 +130,7 @@ impl<B: BlockT> BenchmarkingState<B> {
let mut state = BenchmarkingState {
state: RefCell::new(None),
db: Cell::new(None),
root: Cell::new(root),
root: Cell::new(root.clone()),
genesis: Default::default(),
genesis_root: Default::default(),
record: Default::default(),
Expand All @@ -139,6 +140,7 @@ impl<B: BlockT> BenchmarkingState<B> {
read_write_tracker: Default::default(),
whitelist: Default::default(),
proof_recorder: record_proof.then(Default::default),
proof_recorder_root: Cell::new(root.clone()),
};

state.add_whitelist_to_tracker();
Expand Down Expand Up @@ -166,7 +168,10 @@ impl<B: BlockT> BenchmarkingState<B> {
None => Arc::new(kvdb_memorydb::create(1)),
};
self.db.set(Some(db.clone()));
self.proof_recorder.as_ref().map(|r| r.reset());
if let Some(recorder) = &self.proof_recorder {
recorder.reset();
self.proof_recorder_root.set(self.root.get());
}
let storage_db = Arc::new(StorageDb::<B> {
db,
proof_recorder: self.proof_recorder.clone(),
Expand Down Expand Up @@ -516,7 +521,27 @@ impl<B: BlockT> StateBackend<HashFor<B>> for BenchmarkingState<B> {
}

fn proof_size(&self) -> Option<u32> {
self.proof_recorder.as_ref().map(|recorder| recorder.estimate_encoded_size() as u32)
self.proof_recorder.as_ref().map(|recorder| {
let proof_size = recorder.estimate_encoded_size() as u32;
let proof = recorder.to_storage_proof();
let proof_recorder_root = self.proof_recorder_root.get();
if proof_recorder_root == Default::default() || proof_size == 1 {
// empty trie
proof_size
} else {
if let Some(size) = proof.encoded_compact_size::<HashFor<B>>(proof_recorder_root) {
size as u32
} else {
panic!(
"proof rec root {:?}, root {:?}, genesis {:?}, rec_len {:?}",
self.proof_recorder_root.get(),
self.root.get(),
self.genesis_root,
proof_size,
);
}
}
})
}
}

Expand Down
57 changes: 55 additions & 2 deletions primitives/state-machine/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1402,14 +1402,22 @@ mod tests {
}
}

fn test_compact(remote_proof: StorageProof, remote_root: &sp_core::H256) -> StorageProof {
let compact_remote_proof = remote_proof.into_compact_proof::<BlakeTwo256>(
remote_root.clone(),
).unwrap();
compact_remote_proof.to_storage_proof::<BlakeTwo256>(Some(remote_root)).unwrap().0
}

#[test]
fn prove_read_and_proof_check_works() {
let child_info = ChildInfo::new_default(b"sub1");
let child_info = &child_info;
// fetch read proof from 'remote' full node
let remote_backend = trie_backend::tests::test_trie();
let remote_root = remote_backend.storage_root(::std::iter::empty()).0;
let remote_root = remote_backend.storage_root(std::iter::empty()).0;
let remote_proof = prove_read(remote_backend, &[b"value2"]).unwrap();
let remote_proof = test_compact(remote_proof, &remote_root);
// check proof locally
let local_result1 = read_proof_check::<BlakeTwo256, _>(
remote_root,
Expand All @@ -1429,12 +1437,13 @@ mod tests {
assert_eq!(local_result2, false);
// on child trie
let remote_backend = trie_backend::tests::test_trie();
let remote_root = remote_backend.storage_root(::std::iter::empty()).0;
let remote_root = remote_backend.storage_root(std::iter::empty()).0;
let remote_proof = prove_child_read(
remote_backend,
child_info,
&[b"value3"],
).unwrap();
let remote_proof = test_compact(remote_proof, &remote_root);
let local_result1 = read_child_proof_check::<BlakeTwo256, _>(
remote_root,
remote_proof.clone(),
Expand All @@ -1457,6 +1466,50 @@ mod tests {
);
}

#[test]
fn compact_multiple_child_trie() {
// this root will be queried
let child_info1 = ChildInfo::new_default(b"sub1");
// this root will not be include in proof
let child_info2 = ChildInfo::new_default(b"sub2");
// this root will be include in proof
let child_info3 = ChildInfo::new_default(b"sub");
let mut remote_backend = trie_backend::tests::test_trie();
let (remote_root, transaction) = remote_backend.full_storage_root(
std::iter::empty(),
vec![
(&child_info1, vec![
(&b"key1"[..], Some(&b"val2"[..])),
(&b"key2"[..], Some(&b"val3"[..])),
].into_iter()),
(&child_info2, vec![
(&b"key3"[..], Some(&b"val4"[..])),
(&b"key4"[..], Some(&b"val5"[..])),
].into_iter()),
(&child_info3, vec![
(&b"key5"[..], Some(&b"val6"[..])),
(&b"key6"[..], Some(&b"val7"[..])),
].into_iter()),
].into_iter(),
);
remote_backend.backend_storage_mut().consolidate(transaction);
remote_backend.essence.set_root(remote_root.clone());
let remote_proof = prove_child_read(
remote_backend,
&child_info1,
&[b"key1"],
).unwrap();
let remote_proof = test_compact(remote_proof, &remote_root);
let local_result1 = read_child_proof_check::<BlakeTwo256, _>(
remote_root,
remote_proof.clone(),
&child_info1,
&[b"key1"],
).unwrap();
assert_eq!(local_result1.len(), 1);
assert_eq!(local_result1.get(&b"key1"[..]), Some(&Some(b"val2".to_vec())));
}

#[test]
fn child_storage_uuid() {

Expand Down
2 changes: 1 addition & 1 deletion primitives/trie/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ harness = false
codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false }
sp-std = { version = "3.0.0", default-features = false, path = "../std" }
hash-db = { version = "0.15.2", default-features = false }
trie-db = { version = "0.22.2", default-features = false }
trie-db = { version = "0.22.3", default-features = false }
trie-root = { version = "0.16.0", default-features = false }
memory-db = { version = "0.26.0", default-features = false }
sp-core = { version = "3.0.0", default-features = false, path = "../core" }
Expand Down
2 changes: 1 addition & 1 deletion primitives/trie/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ pub enum Error {
/// Bad format.
BadFormat,
/// Decoding error.
Decode(codec::Error)
Decode(codec::Error),
}

impl From<codec::Error> for Error {
Expand Down
6 changes: 5 additions & 1 deletion primitives/trie/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ mod error;
mod node_header;
mod node_codec;
mod storage_proof;
mod trie_codec;
mod trie_stream;

use sp_std::{boxed::Box, marker::PhantomData, vec::Vec, borrow::Borrow};
Expand All @@ -35,7 +36,7 @@ pub use error::Error;
pub use trie_stream::TrieStream;
/// The Substrate format implementation of `NodeCodec`.
pub use node_codec::NodeCodec;
pub use storage_proof::StorageProof;
pub use storage_proof::{StorageProof, CompactProof};
/// Various re-exports from the `trie-db` crate.
pub use trie_db::{
Trie, TrieMut, DBValue, Recorder, CError, Query, TrieLayout, TrieConfiguration, nibble_ops, TrieDBIterator,
Expand All @@ -45,6 +46,9 @@ pub use memory_db::KeyFunction;
pub use memory_db::prefixed_key;
/// Various re-exports from the `hash-db` crate.
pub use hash_db::{HashDB as HashDBT, EMPTY_PREFIX};
/// Trie codec reexport, mainly child trie support
/// for trie compact proof.
pub use trie_codec::{decode_compact, encode_compact, Error as CompactProofError};

#[derive(Default)]
/// substrate trie layout
Expand Down
56 changes: 56 additions & 0 deletions primitives/trie/src/storage_proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ pub struct StorageProof {
trie_nodes: Vec<Vec<u8>>,
}

/// Storage proof in compact form.
#[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)]
pub struct CompactProof {
pub encoded_nodes: Vec<Vec<u8>>,
}

impl StorageProof {
/// Constructs a storage proof from a subset of encoded trie nodes in a storage backend.
pub fn new(trie_nodes: Vec<Vec<u8>>) -> Self {
Expand Down Expand Up @@ -79,6 +85,56 @@ impl StorageProof {

Self { trie_nodes }
}

/// Encode as a compact proof with default
/// trie layout.
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)
}

/// Returns the estimated encoded size of the compact proof.
///
/// Runing this operation is a slow operation (build the whole compact proof) and should only be
/// in non sensitive path.
/// Return `None` on error.
pub fn encoded_compact_size<H: Hasher>(self, root: H::Out) -> Option<usize> {
let compact_proof = self.into_compact_proof::<H>(root);
compact_proof.ok().map(|p| p.encoded_size())
}

}

impl CompactProof {
/// Return an iterator on the compact encoded nodes.
pub fn iter_compact_encoded_nodes(&self) -> impl Iterator<Item = &[u8]> {
self.encoded_nodes.iter().map(Vec::as_slice)
}

/// Decode to a full storage_proof.
///
/// Method use a temporary `HashDB`, and `sp_trie::decode_compact`
/// is often better.
pub fn to_storage_proof<H: Hasher>(
&self,
expected_root: Option<&H::Out>,
) -> Result<(StorageProof, H::Out), crate::CompactProofError<crate::Layout<H>>> {
let mut db = crate::MemoryDB::<H>::new(&[]);
let root = crate::decode_compact::<crate::Layout<H>, _, _>(
&mut db,
self.iter_compact_encoded_nodes(),
expected_root,
)?;
Ok((StorageProof::new(db.drain().into_iter().filter_map(|kv|
if (kv.1).1 > 0 {
Some((kv.1).0)
} else {
None
}
).collect()), root))
}
}

/// An iterator over trie nodes constructed from a storage proof. The nodes are not guaranteed to
Expand Down
Loading

0 comments on commit 48e3ec0

Please sign in to comment.