mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-12 03:01:07 +00:00
Squashed commit of hc-jp-bridge-module:
commit 265365920836bb1d286c9b48b1902a2de278fdd9 Author: Hernando Castano <castano.ha@gmail.com> Date: Wed Jan 29 19:51:15 2020 -0500 Move hc-jp-bridge repo to different folder commit 8271991e95320baba70bd1cb9c4234d0ffd5b638 Merge: 57d0811 304cbc5 Author: Hernando Castano <castano.ha@gmail.com> Date: Wed Jan 29 19:36:41 2020 -0500 Merge branch 'hc-jp-bridge-module' of hc-jp-bridge-module commit 304cbc5f02d003ffa5404c1c01e461e5b8539888 Author: Hernando Castano <HCastano@users.noreply.github.com> Date: Wed Jan 29 00:38:27 2020 -0500 Update bridge pallet to work with the (almost) lastest master (#4672) * Update decl_error usage * WIP: Update error handling to use DispatchResult * Get module compiling with new error handling * Make tests compile again Main change was updating the usage of InMemoryBackend * Move `sp-state-machine` into dev-dependencies * Bump dependencies to v2.0.0 * Remove some stray comments * Appy code review suggestion commit 510cd6d96372688517496efa61773ea2839f8474 Author: Hernando Castano <HCastano@users.noreply.github.com> Date: Tue Dec 17 12:52:51 2019 -0500 Move Bridge Pallet into FRAME (#4373) * Move `bridge` crate into `frame` folder * Make `bridge` pallet compile after `the-big-reorg` commit ab54e838ef75e6a3f68fd0944bf22598c10c552f Author: Hernando Castano <castano.ha@gmail.com> Date: Mon Nov 11 21:56:40 2019 +0100 Use new StorageProof type from #3834 commit 8fc8911fd1b4acc2274c6863fb3dba91b30c90af Author: Hernando Castano <HCastano@users.noreply.github.com> Date: Tue Nov 5 00:50:34 2019 +0100 Verify Ancestry between Headers (#3963) * Create module for checking ancestry proofs * Use Vec of Headers instead of a HashMap * Move the ancestry verification into the lib.rs file * Change the proof format to exclude `child` and `ancestor` headers * Add a testing function for building header chains * Rename AncestorNotFound error to InvalidAncestryProof * Use ancestor hash instead of header when verifying ancestry * Clean up some stuff missed in the merge commit dbe85738b68358b790cf927b34a804b965a88f96 or: Hernando Castano <HCastano@users.noreply.github.com> Date: Fri Nov 1 15:41:58 2019 +0100 Check given Grandpa validator set against set found in storage (#3915) * Make StorageProofChecker happy * Update some tests * Check given validator set against set found in storage * Use Finality Grandpa's Authority Id and Weight * Add better error handling * Use error type from decl_error! macro commit 31b09216603d3e9c21144ce8c0b6bf59307a4f97 or: Hernando Castano <HCastano@users.noreply.github.com> Date: Wed Oct 23 14:55:37 2019 +0200 Make tests work after the changes introduced in #3793 (#3874) * Make tests work after the changes introduced in #3793 * Remove unneccessary import commit bce6d804aa86504599ff912387295c58f846cbf3 Author: Jim Posen <jim.posen@gmail.com> Date: Thu Oct 10 12:18:58 2019 +0200 Logic for checking Substrate proofs from within runtime module. (#3783) commit a7013e94b6c772c1d45a7cacbb445f73f6554fca Author: Hernando Castano <castano.ha@gmail.com> Date: Fri Oct 4 15:21:00 2019 +0300 Allow tracking of multiple bridges commit 3cf648242d631e32bd553a67df54bf5a48912839 Author: Hernando Castano <castano.ha@gmail.com> Date: Tue Oct 1 14:55:04 2019 +0200 Add BridgeId => Bridge mapping commit 001c74c45072213e01857d0a2454379b447c5a76 Author: Hernando Castano <castano.ha@gmail.com> Date: Tue Oct 1 11:10:19 2019 +0200 Get the mock runtime for tests set up commit 38443a1e8b424ed2f148eb95121d009f730e3b5a Author: Hernando Castano <castano.ha@gmail.com> Date: Fri Sep 27 14:52:53 2019 +0200 Clean up some warnings commit bdc3b01401e89c7111f8bf71f84c50750d25089f Author: Hernando Castano <castano.ha@gmail.com> Date: Thu Sep 26 16:41:01 2019 +0200 Add more skeleton code commit 26995efbf4bac2842eb2822322f7ad3c3e88feb8 Author: Hernando Castano <castano.ha@gmail.com> Date: Wed Sep 25 15:16:57 2019 +0200 Create `bridge` module skeleton
This commit is contained in:
committed by
Bastian Köcher
parent
9a3e2c8c5a
commit
d530bf2199
@@ -0,0 +1,453 @@
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! # Bridge Module
|
||||
//!
|
||||
//! This will eventually have some useful documentation.
|
||||
//! For now though, enjoy this cow's wisdom.
|
||||
//!
|
||||
//!```ignore
|
||||
//!________________________________________
|
||||
//! / You are only young once, but you can \
|
||||
//! \ stay immature indefinitely. /
|
||||
//! ----------------------------------------
|
||||
//! \ ^__^
|
||||
//! \ (oo)\_______
|
||||
//! (__)\ )\/\
|
||||
//! ||----w |
|
||||
//! || ||
|
||||
//!```
|
||||
|
||||
// Ensure we're `no_std` when compiling for Wasm.
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
mod storage_proof;
|
||||
|
||||
use crate::storage_proof::{StorageProof, StorageProofChecker};
|
||||
use codec::{Encode, Decode};
|
||||
use sp_finality_grandpa::{AuthorityId, AuthorityWeight, GRANDPA_AUTHORITIES_KEY};
|
||||
use sp_runtime::traits::Header;
|
||||
use frame_support::{
|
||||
dispatch::{DispatchResult, DispatchError},
|
||||
decl_error, decl_module, decl_storage,
|
||||
};
|
||||
use frame_system::{self as system, ensure_signed};
|
||||
use storage_proof::Error as StorageError;
|
||||
|
||||
#[derive(Encode, Decode, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "std", derive(Debug))]
|
||||
pub struct BridgeInfo<T: Trait> {
|
||||
last_finalized_block_number: T::BlockNumber,
|
||||
last_finalized_block_hash: T::Hash,
|
||||
last_finalized_state_root: T::Hash,
|
||||
current_validator_set: Vec<(AuthorityId, AuthorityWeight)>,
|
||||
}
|
||||
|
||||
impl<T: Trait> BridgeInfo<T> {
|
||||
pub fn new(
|
||||
block_number: &T::BlockNumber,
|
||||
block_hash: &T::Hash,
|
||||
state_root: &T::Hash,
|
||||
validator_set: Vec<(AuthorityId, AuthorityWeight)>,
|
||||
) -> Self
|
||||
{
|
||||
// I don't like how this is done, should come back to...
|
||||
BridgeInfo {
|
||||
last_finalized_block_number: *block_number,
|
||||
last_finalized_block_hash: *block_hash,
|
||||
last_finalized_state_root: *state_root,
|
||||
current_validator_set: validator_set,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type BridgeId = u64;
|
||||
|
||||
pub trait Trait: system::Trait {}
|
||||
|
||||
decl_storage! {
|
||||
trait Store for Module<T: Trait> as Bridge {
|
||||
/// The number of current bridges managed by the module.
|
||||
pub NumBridges get(num_bridges) config(): BridgeId;
|
||||
|
||||
/// Maps a bridge id to a bridge struct. Allows a single
|
||||
/// `bridge` module to manage multiple bridges.
|
||||
pub TrackedBridges get(tracked_bridges): map BridgeId => Option<BridgeInfo<T>>;
|
||||
}
|
||||
}
|
||||
|
||||
decl_module! {
|
||||
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
|
||||
type Error = Error<T>;
|
||||
|
||||
fn initialize_bridge(
|
||||
origin,
|
||||
block_header: T::Header,
|
||||
validator_set: Vec<(AuthorityId, AuthorityWeight)>,
|
||||
validator_set_proof: StorageProof,
|
||||
) {
|
||||
// NOTE: Will want to make this a governance issued call
|
||||
let _sender = ensure_signed(origin)?;
|
||||
|
||||
let block_number = block_header.number();
|
||||
let block_hash = block_header.hash();
|
||||
let state_root = block_header.state_root();
|
||||
|
||||
Self::check_validator_set_proof(state_root, validator_set_proof, &validator_set)?;
|
||||
|
||||
let bridge_info = BridgeInfo::new(block_number, &block_hash, state_root, validator_set);
|
||||
|
||||
let new_bridge_id = NumBridges::get() + 1;
|
||||
<TrackedBridges<T>>::insert(new_bridge_id, bridge_info);
|
||||
|
||||
NumBridges::put(new_bridge_id);
|
||||
}
|
||||
|
||||
fn submit_finalized_headers(origin) {
|
||||
let _sender = ensure_signed(origin)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
decl_error! {
|
||||
// Error for the Bridge module
|
||||
pub enum Error for Module<T: Trait> {
|
||||
InvalidStorageProof,
|
||||
StorageRootMismatch,
|
||||
StorageValueUnavailable,
|
||||
InvalidValidatorSetProof,
|
||||
ValidatorSetMismatch,
|
||||
InvalidAncestryProof,
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> Module<T> {
|
||||
fn check_validator_set_proof(
|
||||
state_root: &T::Hash,
|
||||
proof: StorageProof,
|
||||
validator_set: &Vec<(AuthorityId, AuthorityWeight)>,
|
||||
) -> DispatchResult {
|
||||
|
||||
let checker = <StorageProofChecker<<T::Hashing as sp_runtime::traits::Hash>::Hasher>>::new(
|
||||
*state_root,
|
||||
proof.clone()
|
||||
);
|
||||
|
||||
let checker = checker.map_err(Self::map_storage_err)?;
|
||||
|
||||
// By encoding the given set we should have an easy way to compare
|
||||
// with the stuff we get out of storage via `read_value`
|
||||
let encoded_validator_set = validator_set.encode();
|
||||
|
||||
let c = checker.read_value(GRANDPA_AUTHORITIES_KEY).map_err(Self::map_storage_err)?;
|
||||
let actual_validator_set = c.ok_or(Error::<T>::StorageValueUnavailable)?;
|
||||
|
||||
if encoded_validator_set == actual_validator_set {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::<T>::ValidatorSetMismatch.into())
|
||||
}
|
||||
}
|
||||
|
||||
// A naive way to check whether a `child` header is a decendent
|
||||
// of an `ancestor` header. For this it requires a proof which
|
||||
// is a chain of headers between (but not including) the `child`
|
||||
// and `ancestor`. This could be updated to use something like
|
||||
// Log2 Ancestors (#2053) in the future.
|
||||
fn verify_ancestry<H>(proof: Vec<H>, ancestor_hash: H::Hash, child: H) -> DispatchResult
|
||||
where
|
||||
H: Header,
|
||||
{
|
||||
let mut parent_hash = child.parent_hash();
|
||||
|
||||
// If we find that the header's parent hash matches our ancestor's hash we're done
|
||||
for header in proof.iter() {
|
||||
// Need to check that blocks are actually related
|
||||
if header.hash() != *parent_hash {
|
||||
break;
|
||||
}
|
||||
|
||||
parent_hash = header.parent_hash();
|
||||
if *parent_hash == ancestor_hash {
|
||||
return Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::<T>::InvalidAncestryProof.into())
|
||||
}
|
||||
|
||||
fn map_storage_err(e: StorageError) -> DispatchError {
|
||||
match e {
|
||||
StorageError::StorageRootMismatch => Error::<T>::StorageRootMismatch,
|
||||
StorageError::StorageValueUnavailable => Error::<T>::StorageValueUnavailable,
|
||||
}.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use sp_core::{Blake2Hasher, H256, Public};
|
||||
use sp_runtime::{
|
||||
Perbill, traits::{Header as HeaderT, IdentityLookup}, testing::Header, generic::Digest,
|
||||
};
|
||||
use frame_support::{assert_ok, assert_err, impl_outer_origin, parameter_types};
|
||||
|
||||
impl_outer_origin! {
|
||||
pub enum Origin for Test where system = frame_system {}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct Test;
|
||||
|
||||
type _System = system::Module<Test>;
|
||||
type MockBridge = Module<Test>;
|
||||
|
||||
// TODO: Figure out what I actually need from here
|
||||
parameter_types! {
|
||||
pub const BlockHashCount: u64 = 250;
|
||||
pub const MaximumBlockWeight: u32 = 1024;
|
||||
pub const MaximumBlockLength: u32 = 2 * 1024;
|
||||
pub const MinimumPeriod: u64 = 5;
|
||||
pub const AvailableBlockRatio: Perbill = Perbill::one();
|
||||
}
|
||||
|
||||
type DummyAuthorityId = u64;
|
||||
|
||||
impl system::Trait for Test {
|
||||
type Origin = Origin;
|
||||
type Index = u64;
|
||||
type BlockNumber = u64;
|
||||
type Call = ();
|
||||
type Hash = H256;
|
||||
type Hashing = sp_runtime::traits::BlakeTwo256;
|
||||
type AccountId = DummyAuthorityId;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Header = Header;
|
||||
type Event = ();
|
||||
type BlockHashCount = ();
|
||||
type MaximumBlockWeight = ();
|
||||
type AvailableBlockRatio = ();
|
||||
type MaximumBlockLength = ();
|
||||
type Version = ();
|
||||
type ModuleToIndex = ();
|
||||
}
|
||||
|
||||
impl Trait for Test {}
|
||||
|
||||
fn new_test_ext() -> sp_io::TestExternalities {
|
||||
let mut t = system::GenesisConfig::default().build_storage::<Test>().unwrap();
|
||||
GenesisConfig {
|
||||
num_bridges: 0,
|
||||
}.assimilate_storage(&mut t).unwrap();
|
||||
t.into()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_works_for_default_value() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_eq!(MockBridge::num_bridges(), 0);
|
||||
});
|
||||
}
|
||||
|
||||
fn get_dummy_authorities() -> Vec<(AuthorityId, AuthorityWeight)> {
|
||||
let authority1 = (AuthorityId::from_slice(&[1; 32]), 1);
|
||||
let authority2 = (AuthorityId::from_slice(&[2; 32]), 1);
|
||||
let authority3 = (AuthorityId::from_slice(&[3; 32]), 1);
|
||||
|
||||
vec![authority1, authority2, authority3]
|
||||
}
|
||||
|
||||
fn create_dummy_validator_proof(validator_set: Vec<(AuthorityId, AuthorityWeight)>) -> (H256, StorageProof) {
|
||||
use sp_state_machine::{prove_read, backend::Backend, InMemoryBackend};
|
||||
|
||||
let encoded_set = validator_set.encode();
|
||||
|
||||
// construct storage proof
|
||||
let backend = <InMemoryBackend<Blake2Hasher>>::from(vec![
|
||||
(None, vec![(GRANDPA_AUTHORITIES_KEY.to_vec(), Some(encoded_set))]),
|
||||
]);
|
||||
let root = backend.storage_root(std::iter::empty()).0;
|
||||
|
||||
// Generates a storage read proof
|
||||
let proof: StorageProof = prove_read(backend, &[&GRANDPA_AUTHORITIES_KEY[..]])
|
||||
.unwrap()
|
||||
.iter_nodes()
|
||||
.collect();
|
||||
|
||||
(root, proof)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_can_validate_validator_sets() {
|
||||
let authorities = get_dummy_authorities();
|
||||
let (root, proof) = create_dummy_validator_proof(authorities.clone());
|
||||
|
||||
assert_ok!(MockBridge::check_validator_set_proof(&root, proof, &authorities));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_rejects_invalid_validator_sets() {
|
||||
let mut authorities = get_dummy_authorities();
|
||||
let (root, proof) = create_dummy_validator_proof(authorities.clone());
|
||||
|
||||
// Do something to make the authority set invalid
|
||||
authorities.reverse();
|
||||
let invalid_authorities = authorities;
|
||||
|
||||
assert_err!(
|
||||
MockBridge::check_validator_set_proof(&root, proof, &invalid_authorities),
|
||||
Error::<Test>::ValidatorSetMismatch
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_creates_a_new_bridge() {
|
||||
let authorities = get_dummy_authorities();
|
||||
let (root, proof) = create_dummy_validator_proof(authorities.clone());
|
||||
|
||||
let test_header = Header {
|
||||
parent_hash: H256::default(),
|
||||
number: 42,
|
||||
state_root: root,
|
||||
extrinsics_root: H256::default(),
|
||||
digest: Digest::default(),
|
||||
};
|
||||
let test_hash = test_header.hash();
|
||||
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_eq!(MockBridge::num_bridges(), 0);
|
||||
dbg!(&test_header);
|
||||
assert_ok!(
|
||||
MockBridge::initialize_bridge(
|
||||
Origin::signed(1),
|
||||
test_header,
|
||||
authorities.clone(),
|
||||
proof,
|
||||
));
|
||||
|
||||
assert_eq!(
|
||||
MockBridge::tracked_bridges(1),
|
||||
Some(BridgeInfo {
|
||||
last_finalized_block_number: 42,
|
||||
last_finalized_block_hash: test_hash,
|
||||
last_finalized_state_root: root,
|
||||
current_validator_set: authorities.clone(),
|
||||
}));
|
||||
|
||||
assert_eq!(MockBridge::num_bridges(), 1);
|
||||
});
|
||||
}
|
||||
|
||||
fn build_header_chain(root_header: Header, len: usize) -> Vec<Header> {
|
||||
let mut header_chain = vec![root_header];
|
||||
for i in 1..len {
|
||||
let parent = &header_chain[i - 1];
|
||||
|
||||
let h = Header {
|
||||
parent_hash: parent.hash(),
|
||||
number: parent.number() + 1,
|
||||
state_root: H256::default(),
|
||||
extrinsics_root: H256::default(),
|
||||
digest: Digest::default(),
|
||||
};
|
||||
|
||||
header_chain.push(h);
|
||||
}
|
||||
|
||||
// We want our proofs to go from newest to older headers
|
||||
header_chain.reverse();
|
||||
// We don't actually need the oldest header in the proof
|
||||
header_chain.pop();
|
||||
header_chain
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_that_child_is_ancestor_of_grandparent() {
|
||||
let ancestor = Header {
|
||||
parent_hash: H256::default(),
|
||||
number: 1,
|
||||
state_root: H256::default(),
|
||||
extrinsics_root: H256::default(),
|
||||
digest: Digest::default(),
|
||||
};
|
||||
|
||||
// A valid proof doesn't include the child header, so remove it
|
||||
let mut proof = build_header_chain(ancestor.clone(), 10);
|
||||
let child = proof.remove(0);
|
||||
|
||||
assert_ok!(MockBridge::verify_ancestry(proof, ancestor.hash(), child));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fake_ancestor_is_not_found_in_child_ancestry() {
|
||||
let ancestor = Header {
|
||||
parent_hash: H256::default(),
|
||||
number: 1,
|
||||
state_root: H256::default(),
|
||||
extrinsics_root: H256::default(),
|
||||
digest: Digest::default(),
|
||||
};
|
||||
|
||||
// A valid proof doesn't include the child header, so remove it
|
||||
let mut proof = build_header_chain(ancestor, 10);
|
||||
let child = proof.remove(0);
|
||||
|
||||
let fake_ancestor = Header {
|
||||
parent_hash: H256::from_slice(&[1u8; 32]),
|
||||
number: 42,
|
||||
state_root: H256::default(),
|
||||
extrinsics_root: H256::default(),
|
||||
digest: Digest::default(),
|
||||
};
|
||||
|
||||
assert_err!(
|
||||
MockBridge::verify_ancestry(proof, fake_ancestor.hash(), child),
|
||||
Error::<Test>::InvalidAncestryProof
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn checker_fails_if_given_an_unrelated_header() {
|
||||
let ancestor = Header {
|
||||
parent_hash: H256::default(),
|
||||
number: 1,
|
||||
state_root: H256::default(),
|
||||
extrinsics_root: H256::default(),
|
||||
digest: Digest::default(),
|
||||
};
|
||||
|
||||
// A valid proof doesn't include the child header, so remove it
|
||||
let mut invalid_proof = build_header_chain(ancestor.clone(), 10);
|
||||
let child = invalid_proof.remove(0);
|
||||
|
||||
let fake_ancestor = Header {
|
||||
parent_hash: H256::from_slice(&[1u8; 32]),
|
||||
number: 42,
|
||||
state_root: H256::default(),
|
||||
extrinsics_root: H256::default(),
|
||||
digest: Digest::default(),
|
||||
};
|
||||
|
||||
invalid_proof.insert(5, fake_ancestor);
|
||||
|
||||
assert_err!(
|
||||
MockBridge::verify_ancestry(invalid_proof, ancestor.hash(), child),
|
||||
Error::<Test>::InvalidAncestryProof
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Logic for checking Substrate storage proofs.
|
||||
|
||||
use hash_db::{Hasher, HashDB, EMPTY_PREFIX};
|
||||
use sp_trie::{MemoryDB, Trie, trie_types::TrieDB};
|
||||
use sp_runtime::RuntimeDebug;
|
||||
|
||||
pub(crate) type StorageProof = Vec<Vec<u8>>;
|
||||
|
||||
/// 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 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<Option<Vec<u8>>, Error> {
|
||||
self.trie()?
|
||||
.get(key)
|
||||
.map(|value| value.map(|value| value.to_vec()))
|
||||
.map_err(|_| Error::StorageValueUnavailable)
|
||||
}
|
||||
|
||||
fn trie(&self) -> Result<TrieDB<H>, Error> {
|
||||
TrieDB::new(&self.db, &self.root)
|
||||
.map_err(|_| Error::StorageRootMismatch)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(RuntimeDebug, PartialEq)]
|
||||
pub enum Error {
|
||||
StorageRootMismatch,
|
||||
StorageValueUnavailable,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use sp_core::{Blake2Hasher, H256};
|
||||
use sp_state_machine::{prove_read, backend::Backend, InMemoryBackend};
|
||||
|
||||
#[test]
|
||||
fn storage_proof_check() {
|
||||
// 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 = prove_read(backend, &[&b"key1"[..], &b"key2"[..], &b"key22"[..]])
|
||||
.unwrap()
|
||||
.iter_nodes()
|
||||
.collect();
|
||||
|
||||
// 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)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
[package]
|
||||
name = "pallet-bridge"
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false }
|
||||
frame-support = { version = "2.0.0", default-features = false, path = "../support" }
|
||||
frame-system = { version = "2.0.0", default-features = false, path = "../system" }
|
||||
hash-db = { version = "0.15.2", default-features = false }
|
||||
pallet-session = { version = "2.0.0", default-features = false, path = "../session" }
|
||||
serde = { version = "1.0", optional = true }
|
||||
sp-core = { version = "2.0.0", default-features = false, path = "../../primitives/core" }
|
||||
sp-finality-grandpa = { version = "2.0.0", default-features = false, path = "../../primitives/finality-grandpa" }
|
||||
sp-runtime = { version = "2.0.0", default-features = false, path = "../../primitives/runtime" }
|
||||
sp-trie = { version = "2.0.0", default-features = false, path = "../../primitives/trie" }
|
||||
|
||||
[dev-dependencies]
|
||||
sp-io = { version = "2.0.0", default-features = false, path = "../../primitives/io" }
|
||||
sp-state-machine = { version = "2.0.0", path = "../../primitives/state-machine" }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"serde",
|
||||
"codec/std",
|
||||
"frame-support/std",
|
||||
"frame-system/std",
|
||||
"pallet-session/std",
|
||||
"sp-core/std",
|
||||
"sp-finality-grandpa/std",
|
||||
"sp-runtime/std",
|
||||
"sp-trie/std",
|
||||
"sp-io/std",
|
||||
]
|
||||
Reference in New Issue
Block a user