From 74c63d028f5eb55515164dedd900acd2696f09fa Mon Sep 17 00:00:00 2001 From: Jim Posen Date: Thu, 10 Oct 2019 12:18:58 +0200 Subject: [PATCH] Logic for checking Substrate proofs from within runtime module. (#3783) --- Cargo.lock | 3 + srml/bridge/Cargo.toml | 4 ++ srml/bridge/src/error.rs | 25 ++++++++ srml/bridge/src/lib.rs | 8 ++- srml/bridge/src/storage_proof.rs | 103 +++++++++++++++++++++++++++++++ 5 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 srml/bridge/src/error.rs create mode 100644 srml/bridge/src/storage_proof.rs diff --git a/Cargo.lock b/Cargo.lock index 28dc2485d3d24..cd5c502f0ea69 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4379,6 +4379,8 @@ dependencies = [ "srml-support 2.0.0", "srml-system 2.0.0", "substrate-primitives 2.0.0", + "substrate-state-machine 2.0.0", + "substrate-trie 2.0.0", ] [[package]] @@ -4480,6 +4482,7 @@ dependencies = [ name = "srml-bridge" version = "0.1.0" dependencies = [ + "hash-db 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)", "parity-scale-codec 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", "sr-io 2.0.0", diff --git a/srml/bridge/Cargo.toml b/srml/bridge/Cargo.toml index 804e35e78e2c8..92c93954bf186 100644 --- a/srml/bridge/Cargo.toml +++ b/srml/bridge/Cargo.toml @@ -8,15 +8,18 @@ edition = "2018" [dependencies] codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false } +hash-db = { version = "0.15.2", default-features = false } session = { package = "srml-session", path = "../session", default-features = false, features = ["historical"] } serde = { version = "1.0", optional = true } sr-primitives = { path = "../../core/sr-primitives", default-features = false } support = { package = "srml-support", path = "../support", default-features = false } system = { package = "srml-system", path = "../system", default-features = false } +trie = { package = "substrate-trie", path = "../../core/trie", default-features = false } [dev-dependencies] primitives = { package = "substrate-primitives", path = "../../core/primitives" } runtime-io = { package = "sr-io", path = "../../core/sr-io", default-features = false } +state-machine = { package = "substrate-state-machine", path = "../../core/state-machine" } [features] default = ["std"] @@ -27,5 +30,6 @@ std = [ "sr-primitives/std", "support/std", "system/std", + "trie/std", "runtime-io/std", ] diff --git a/srml/bridge/src/error.rs b/srml/bridge/src/error.rs new file mode 100644 index 0000000000000..182b0883d1e62 --- /dev/null +++ b/srml/bridge/src/error.rs @@ -0,0 +1,25 @@ +// Copyright 2017-2019 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +//! Common error types for the srml-bridge crate. + +#[derive(PartialEq)] +#[cfg_attr(feature = "std", derive(Debug))] +pub enum Error { + StorageRootMismatch, + StorageValueUnavailable, +} + diff --git a/srml/bridge/src/lib.rs b/srml/bridge/src/lib.rs index f24653a4f7987..625e22915d21c 100644 --- a/srml/bridge/src/lib.rs +++ b/srml/bridge/src/lib.rs @@ -32,6 +32,9 @@ // Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] +mod error; +mod storage_proof; + use codec::{Encode, Decode}; use sr_primitives::traits::{Header, Member}; use support::{ @@ -148,7 +151,7 @@ mod tests { use primitives::{H256, Blake2Hasher}; use sr_primitives::{ - Perbill, traits::IdentityLookup, testing::Header, generic::Digest + Perbill, traits::{Header as HeaderT, IdentityLookup}, testing::Header, generic::Digest, }; use support::{assert_ok, impl_outer_origin, parameter_types}; use runtime_io::with_externalities; @@ -223,6 +226,7 @@ mod tests { extrinsics_root: H256::default(), digest: Digest::default(), }; + let test_hash = test_header.hash(); with_externalities(&mut new_test_ext(), || { assert_eq!(MockBridge::num_bridges(), 0); @@ -242,7 +246,7 @@ mod tests { MockBridge::tracked_bridges(1), Some(BridgeInfo { last_finalized_block_number: 42, - last_finalized_block_hash: test_header.hash(), // FIXME: This is broken + last_finalized_block_hash: test_hash, last_finalized_state_root: dummy_state_root, current_validator_set: vec![1, 2, 3], })); diff --git a/srml/bridge/src/storage_proof.rs b/srml/bridge/src/storage_proof.rs new file mode 100644 index 0000000000000..8217bb3b32e2f --- /dev/null +++ b/srml/bridge/src/storage_proof.rs @@ -0,0 +1,103 @@ +// Copyright 2017-2019 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +//! Logic for checking Substrate storage proofs. + +use hash_db::{Hasher, HashDB, EMPTY_PREFIX}; +use trie::{MemoryDB, Trie, trie_types::TrieDB}; + +use crate::error::Error; + +/// This struct is used to read storage values from a subset of a Merklized database. The "proof" +/// is a subset of the nodes in the Merkle structure of the database, so that it provides +/// authentication against a known Merkle root as well as the values in the database themselves. +pub struct StorageProofChecker + where H: Hasher +{ + root: H::Out, + db: MemoryDB, +} + +impl StorageProofChecker + where H: Hasher +{ + /// Constructs a new storage proof checker. + /// + /// This returns an error if the given proof is invalid with respect to the given root. + pub fn new(root: H::Out, proof: Vec>) -> Result { + let mut db = MemoryDB::default(); + for item in proof { + db.insert(EMPTY_PREFIX, &item); + } + let checker = StorageProofChecker { + root, + db, + }; + // Return error if trie would be invalid. + let _ = checker.trie()?; + Ok(checker) + } + + /// Reads a value from the available subset of storage. If the value cannot be read due to an + /// incomplete or otherwise invalid proof, this returns an error. + pub fn read_value(&self, key: &[u8]) -> Result>, Error> { + self.trie()? + .get(key) + .map(|value| value.map(|value| value.into_vec())) + .map_err(|_| Error::StorageValueUnavailable) + } + + fn trie(&self) -> Result, Error> { + TrieDB::new(&self.db, &self.root) + .map_err(|_| Error::StorageRootMismatch) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use primitives::{Blake2Hasher, H256}; + use state_machine::{prove_read, backend::{Backend, InMemory}}; + // use trie::{PrefixedMemoryDB, TrieDBMut}; + + #[test] + fn storage_proof_check() { + // construct storage proof + let backend = >::from(vec![ + (None, b"key1".to_vec(), Some(b"value1".to_vec())), + (None, b"key2".to_vec(), Some(b"value2".to_vec())), + (None, b"key3".to_vec(), Some(b"value3".to_vec())), + // Value is too big to fit in a branch node + (None, b"key11".to_vec(), Some(vec![0u8; 32])), + ]); + let root = backend.storage_root(std::iter::empty()).0; + let proof = prove_read(backend, &[&b"key1"[..], &b"key2"[..], &b"key22"[..]]).unwrap(); + + // check proof in runtime + let checker = >::new(root, proof.clone()).unwrap(); + assert_eq!(checker.read_value(b"key1"), Ok(Some(b"value1".to_vec()))); + assert_eq!(checker.read_value(b"key2"), Ok(Some(b"value2".to_vec()))); + assert_eq!(checker.read_value(b"key11111"), Err(Error::StorageValueUnavailable)); + assert_eq!(checker.read_value(b"key22"), Ok(None)); + + // checking proof against invalid commitment fails + assert_eq!( + >::new(H256::random(), proof).err(), + Some(Error::StorageRootMismatch) + ); + } +}