Move Storage Parser from Bridge Pallet (#793)

* Move storage proof checker to runtime primtives

* Add method for parsing storage proofs

* Use finality-verifier pallet in runtime-common

* Get bridge pallet compiling again

* Use storage prover from bp-runtime in a few more places

* Don't leak `std` items from proof helper into `no-std` builds

* Fix benchmarking compilation

* Remove unused import in fuzzer
This commit is contained in:
Hernando Castano
2021-03-08 17:13:25 -05:00
committed by Bastian Köcher
parent 80533af331
commit 51db99ea79
9 changed files with 128 additions and 60 deletions
@@ -25,6 +25,7 @@ frame-system = { git = "https://github.com/paritytech/substrate.git", branch = "
sp-finality-grandpa = { git = "https://github.com/paritytech/substrate.git", branch = "master" , default-features = false }
sp-runtime = { git = "https://github.com/paritytech/substrate.git", branch = "master" , default-features = false }
sp-std = { git = "https://github.com/paritytech/substrate.git", branch = "master" , default-features = false }
sp-trie = { git = "https://github.com/paritytech/substrate.git", branch = "master" , default-features = false }
[dev-dependencies]
bp-test-utils = {path = "../../primitives/test-utils" }
@@ -46,4 +47,6 @@ std = [
"sp-finality-grandpa/std",
"sp-runtime/std",
"sp-std/std",
"sp-trie/std",
]
runtime-benchmarks = []
+66 -2
View File
@@ -56,7 +56,7 @@ pub type BridgedBlockNumber<T> = BlockNumberOf<<T as Config>::BridgedChain>;
/// Block hash of the bridged chain.
pub type BridgedBlockHash<T> = HashOf<<T as Config>::BridgedChain>;
/// Hasher of the bridged chain.
pub type _BridgedBlockHasher<T> = HasherOf<<T as Config>::BridgedChain>;
pub type BridgedBlockHasher<T> = HasherOf<<T as Config>::BridgedChain>;
/// Header of the bridged chain.
pub type BridgedHeader<T> = HeaderOf<<T as Config>::BridgedChain>;
@@ -335,6 +335,8 @@ pub mod pallet {
TooManyRequests,
/// The header being imported is older than the best finalized header known to the pallet.
OldHeader,
/// The header is unknown to the pallet.
UnknownHeader,
/// The scheduled authority set change found in the header is unsupported by the pallet.
///
/// This is the case for non-standard (e.g forced) authority set changes.
@@ -343,6 +345,8 @@ pub mod pallet {
AlreadyInitialized,
/// All pallet operations are halted.
Halted,
/// The storage proof doesn't contains storage root. So it is invalid for given header.
StorageRootMismatch,
}
/// Import the given header to the pallet's storage.
@@ -387,7 +391,7 @@ pub mod pallet {
/// Since this writes to storage with no real checks this should only be used in functions that
/// were called by a trusted origin.
fn initialize_bridge<T: Config>(init_params: super::InitializationData<BridgedHeader<T>>) {
pub(crate) fn initialize_bridge<T: Config>(init_params: super::InitializationData<BridgedHeader<T>>) {
let super::InitializationData {
header,
authority_list,
@@ -447,6 +451,21 @@ impl<T: Config> Pallet<T> {
pub fn is_known_header(hash: BridgedBlockHash<T>) -> bool {
<ImportedHeaders<T>>::contains_key(hash)
}
/// Verify that the passed storage proof is valid, given it is crafted using
/// known finalized header. If the proof is valid, then the `parse` callback
/// is called and the function returns its result.
pub fn parse_finalized_storage_proof<R>(
hash: BridgedBlockHash<T>,
storage_proof: sp_trie::StorageProof,
parse: impl FnOnce(bp_runtime::StorageProofChecker<BridgedBlockHasher<T>>) -> R,
) -> Result<R, sp_runtime::DispatchError> {
let header = <ImportedHeaders<T>>::get(hash).ok_or(Error::<T>::UnknownHeader)?;
let storage_proof_checker = bp_runtime::StorageProofChecker::new(*header.state_root(), storage_proof)
.map_err(|_| Error::<T>::StorageRootMismatch)?;
Ok(parse(storage_proof_checker))
}
}
/// Data required for initializing the bridge pallet.
@@ -499,6 +518,17 @@ pub(crate) fn find_forced_change<H: HeaderT>(
header.digest().convert_first(|l| l.try_to(id).and_then(filter_log))
}
/// (Re)initialize bridge with given header for using it in external benchmarks.
#[cfg(feature = "runtime-benchmarks")]
pub fn initialize_for_benchmarks<T: Config>(header: BridgedHeader<T>) {
initialize_bridge::<T>(InitializationData {
header,
authority_list: Vec::new(), // we don't verify any proofs in external benchmarks
set_id: 0,
is_halted: false,
});
}
#[cfg(test)]
mod tests {
use super::*;
@@ -864,6 +894,40 @@ mod tests {
);
})
}
#[test]
fn parse_finalized_storage_proof_rejects_proof_on_unknown_header() {
run_test(|| {
assert_noop!(
Module::<TestRuntime>::parse_finalized_storage_proof(
Default::default(),
sp_trie::StorageProof::new(vec![]),
|_| (),
),
Error::<TestRuntime>::UnknownHeader,
);
});
}
#[test]
fn parse_finalized_storage_accepts_valid_proof() {
run_test(|| {
let (state_root, storage_proof) = bp_runtime::craft_valid_storage_proof();
let mut header = test_header(2);
header.set_state_root(state_root);
let hash = header.hash();
<BestFinalized<TestRuntime>>::put(hash);
<ImportedHeaders<TestRuntime>>::insert(hash, header);
assert_ok!(
Module::<TestRuntime>::parse_finalized_storage_proof(hash, storage_proof, |_| (),),
(),
);
});
}
#[test]
fn rate_limiter_disallows_imports_once_limit_is_hit_in_single_block() {
run_test(|| {
+4 -7
View File
@@ -46,10 +46,7 @@ use sp_trie::StorageProof;
// Re-export since the node uses these when configuring genesis
pub use storage::{InitializationData, ScheduledChange};
pub use storage_proof::StorageProofChecker;
mod storage;
mod storage_proof;
mod verifier;
#[cfg(test)]
@@ -355,7 +352,7 @@ impl<T: Config> Module<T> {
pub fn parse_finalized_storage_proof<R>(
finalized_header_hash: BridgedBlockHash<T>,
storage_proof: StorageProof,
parse: impl FnOnce(StorageProofChecker<BridgedBlockHasher<T>>) -> R,
parse: impl FnOnce(bp_runtime::StorageProofChecker<BridgedBlockHasher<T>>) -> R,
) -> Result<R, sp_runtime::DispatchError> {
let storage = PalletStorage::<T>::new();
let header = storage
@@ -365,8 +362,8 @@ impl<T: Config> Module<T> {
return Err(Error::<T>::UnfinalizedHeader.into());
}
let storage_proof_checker =
StorageProofChecker::new(*header.state_root(), storage_proof).map_err(Error::<T>::from)?;
let storage_proof_checker = bp_runtime::StorageProofChecker::new(*header.state_root(), storage_proof)
.map_err(|_| Error::<T>::StorageRootMismatch)?;
Ok(parse(storage_proof_checker))
}
}
@@ -898,7 +895,7 @@ mod tests {
fn parse_finalized_storage_accepts_valid_proof() {
run_test(|| {
let mut storage = PalletStorage::<TestRuntime>::new();
let (state_root, storage_proof) = storage_proof::tests::craft_valid_storage_proof();
let (state_root, storage_proof) = bp_runtime::craft_valid_storage_proof();
let mut header = unfinalized_header(1);
header.is_finalized = true;
header.header.set_state_root(state_root);
@@ -1,122 +0,0 @@
// Copyright 2019-2020 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common 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.
// Parity Bridges Common 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 Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
// TODO: remove on actual use
#![allow(dead_code)]
//! Logic for checking Substrate storage proofs.
use hash_db::{HashDB, Hasher, EMPTY_PREFIX};
use sp_runtime::RuntimeDebug;
use sp_std::vec::Vec;
use sp_trie::{read_trie_value, Layout, MemoryDB, StorageProof};
/// 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<H>
where
H: Hasher,
{
root: H::Out,
db: MemoryDB<H>,
}
impl<H> StorageProofChecker<H>
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: StorageProof) -> Result<Self, Error> {
let db = proof.into_memory_db();
if !db.contains(&root, EMPTY_PREFIX) {
return Err(Error::StorageRootMismatch);
}
let checker = StorageProofChecker { root, db };
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<Option<Vec<u8>>, Error> {
read_trie_value::<Layout<H>, _>(&self.db, &self.root, key).map_err(|_| Error::StorageValueUnavailable)
}
}
#[derive(RuntimeDebug, PartialEq)]
pub enum Error {
StorageRootMismatch,
StorageValueUnavailable,
}
impl<T: crate::Config> From<Error> for crate::Error<T> {
fn from(error: Error) -> Self {
match error {
Error::StorageRootMismatch => crate::Error::StorageRootMismatch,
Error::StorageValueUnavailable => crate::Error::StorageValueUnavailable,
}
}
}
#[cfg(test)]
pub mod tests {
use super::*;
use sp_core::{Blake2Hasher, H256};
use sp_state_machine::{backend::Backend, prove_read, InMemoryBackend};
/// Return valid storage proof and state root.
pub fn craft_valid_storage_proof() -> (H256, StorageProof) {
// construct storage proof
let backend = <InMemoryBackend<Blake2Hasher>>::from(vec![
(None, vec![(b"key1".to_vec(), Some(b"value1".to_vec()))]),
(None, vec![(b"key2".to_vec(), Some(b"value2".to_vec()))]),
(None, vec![(b"key3".to_vec(), Some(b"value3".to_vec()))]),
// Value is too big to fit in a branch node
(None, vec![(b"key11".to_vec(), Some(vec![0u8; 32]))]),
]);
let root = backend.storage_root(std::iter::empty()).0;
let proof = StorageProof::new(
prove_read(backend, &[&b"key1"[..], &b"key2"[..], &b"key22"[..]])
.unwrap()
.iter_nodes()
.collect(),
);
(root, proof)
}
#[test]
fn storage_proof_check() {
let (root, proof) = craft_valid_storage_proof();
// check proof in runtime
let checker = <StorageProofChecker<Blake2Hasher>>::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!(
<StorageProofChecker<Blake2Hasher>>::new(H256::random(), proof).err(),
Some(Error::StorageRootMismatch)
);
}
}