mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-31 02:51:01 +00:00
Parse substrate message proof (#479)
* parse substrate message proof * unfinalized_header
This commit is contained in:
committed by
Bastian Köcher
parent
b0a5e75ff4
commit
2d7eacf6e2
@@ -32,17 +32,19 @@
|
|||||||
#![allow(clippy::large_enum_variant)]
|
#![allow(clippy::large_enum_variant)]
|
||||||
|
|
||||||
use crate::storage::ImportedHeader;
|
use crate::storage::ImportedHeader;
|
||||||
use bp_runtime::{BlockNumberOf, Chain, HashOf, HeaderOf};
|
use bp_runtime::{BlockNumberOf, Chain, HashOf, HasherOf, HeaderOf};
|
||||||
use frame_support::{decl_error, decl_module, decl_storage, dispatch::DispatchResult};
|
use frame_support::{decl_error, decl_module, decl_storage, dispatch::DispatchResult};
|
||||||
use frame_system::ensure_signed;
|
use frame_system::ensure_signed;
|
||||||
use sp_runtime::traits::Header as HeaderT;
|
use sp_runtime::traits::Header as HeaderT;
|
||||||
use sp_runtime::RuntimeDebug;
|
use sp_runtime::RuntimeDebug;
|
||||||
use sp_std::{marker::PhantomData, prelude::*};
|
use sp_std::{marker::PhantomData, prelude::*};
|
||||||
|
use sp_trie::StorageProof;
|
||||||
|
|
||||||
// Re-export since the node uses these when configuring genesis
|
// Re-export since the node uses these when configuring genesis
|
||||||
pub use storage::{AuthoritySet, ScheduledChange};
|
pub use storage::{AuthoritySet, ScheduledChange};
|
||||||
|
|
||||||
pub use justification::decode_justification_target;
|
pub use justification::decode_justification_target;
|
||||||
|
pub use storage_proof::StorageProofChecker;
|
||||||
|
|
||||||
mod justification;
|
mod justification;
|
||||||
mod storage;
|
mod storage;
|
||||||
@@ -59,6 +61,8 @@ mod fork_tests;
|
|||||||
pub(crate) type BridgedBlockNumber<T> = BlockNumberOf<<T as Trait>::BridgedChain>;
|
pub(crate) type BridgedBlockNumber<T> = BlockNumberOf<<T as Trait>::BridgedChain>;
|
||||||
/// Block hash of the bridged chain.
|
/// Block hash of the bridged chain.
|
||||||
pub(crate) type BridgedBlockHash<T> = HashOf<<T as Trait>::BridgedChain>;
|
pub(crate) type BridgedBlockHash<T> = HashOf<<T as Trait>::BridgedChain>;
|
||||||
|
/// Hasher of the bridged chain.
|
||||||
|
pub(crate) type BridgedBlockHasher<T> = HasherOf<<T as Trait>::BridgedChain>;
|
||||||
/// Header of the bridged chain.
|
/// Header of the bridged chain.
|
||||||
pub(crate) type BridgedHeader<T> = HeaderOf<<T as Trait>::BridgedChain>;
|
pub(crate) type BridgedHeader<T> = HeaderOf<<T as Trait>::BridgedChain>;
|
||||||
|
|
||||||
@@ -158,6 +162,12 @@ decl_error! {
|
|||||||
InvalidHeader,
|
InvalidHeader,
|
||||||
/// This header has not been finalized.
|
/// This header has not been finalized.
|
||||||
UnfinalizedHeader,
|
UnfinalizedHeader,
|
||||||
|
/// The header is unknown.
|
||||||
|
UnknownHeader,
|
||||||
|
/// The storage proof doesn't contains storage root. So it is invalid for given header.
|
||||||
|
StorageRootMismatch,
|
||||||
|
/// Error when trying to fetch storage value from the proof.
|
||||||
|
StorageValueUnavailable,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,6 +277,27 @@ impl<T: Trait> Module<T> {
|
|||||||
.map(|id| (id.number, id.hash))
|
.map(|id| (id.number, id.hash))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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>(
|
||||||
|
finalized_header_hash: BridgedBlockHash<T>,
|
||||||
|
storage_proof: StorageProof,
|
||||||
|
parse: impl FnOnce(StorageProofChecker<BridgedBlockHasher<T>>) -> R,
|
||||||
|
) -> Result<R, sp_runtime::DispatchError> {
|
||||||
|
let storage = PalletStorage::<T>::new();
|
||||||
|
let header = storage
|
||||||
|
.header_by_hash(finalized_header_hash)
|
||||||
|
.ok_or(Error::<T>::UnknownHeader)?;
|
||||||
|
if !header.is_finalized {
|
||||||
|
return Err(Error::<T>::UnfinalizedHeader.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let storage_proof_checker =
|
||||||
|
StorageProofChecker::new(*header.state_root(), storage_proof).map_err(Error::<T>::from)?;
|
||||||
|
Ok(parse(storage_proof_checker))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Expected interface for interacting with bridge pallet storage.
|
/// Expected interface for interacting with bridge pallet storage.
|
||||||
@@ -433,3 +464,59 @@ impl<T: Trait> BridgeStorage for PalletStorage<T> {
|
|||||||
<NextScheduledChange<T>>::insert(signal_hash, next_change)
|
<NextScheduledChange<T>>::insert(signal_hash, next_change)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::mock::{helpers::unfinalized_header, run_test, TestRuntime};
|
||||||
|
use frame_support::{assert_noop, assert_ok};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_finalized_storage_proof_rejects_proof_on_unknown_header() {
|
||||||
|
run_test(|| {
|
||||||
|
assert_noop!(
|
||||||
|
Module::<TestRuntime>::parse_finalized_storage_proof(
|
||||||
|
Default::default(),
|
||||||
|
StorageProof::new(vec![]),
|
||||||
|
|_| (),
|
||||||
|
),
|
||||||
|
Error::<TestRuntime>::UnknownHeader,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_finalized_storage_proof_rejects_proof_on_unfinalized_header() {
|
||||||
|
run_test(|| {
|
||||||
|
let mut storage = PalletStorage::<TestRuntime>::new();
|
||||||
|
let header = unfinalized_header(1);
|
||||||
|
storage.write_header(&header);
|
||||||
|
|
||||||
|
assert_noop!(
|
||||||
|
Module::<TestRuntime>::parse_finalized_storage_proof(
|
||||||
|
header.header.hash(),
|
||||||
|
StorageProof::new(vec![]),
|
||||||
|
|_| (),
|
||||||
|
),
|
||||||
|
Error::<TestRuntime>::UnfinalizedHeader,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
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 mut header = unfinalized_header(1);
|
||||||
|
header.is_finalized = true;
|
||||||
|
header.header.set_state_root(state_root);
|
||||||
|
storage.write_header(&header);
|
||||||
|
|
||||||
|
assert_ok!(
|
||||||
|
Module::<TestRuntime>::parse_finalized_storage_proof(header.header.hash(), storage_proof, |_| (),),
|
||||||
|
(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ pub fn run_test<T>(test: impl FnOnce() -> T) -> T {
|
|||||||
|
|
||||||
pub mod helpers {
|
pub mod helpers {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::storage::ImportedHeader;
|
||||||
use crate::{BridgedBlockHash, BridgedBlockNumber, BridgedHeader};
|
use crate::{BridgedBlockHash, BridgedBlockNumber, BridgedHeader};
|
||||||
use finality_grandpa::voter_set::VoterSet;
|
use finality_grandpa::voter_set::VoterSet;
|
||||||
use sp_finality_grandpa::{AuthorityId, AuthorityList};
|
use sp_finality_grandpa::{AuthorityId, AuthorityList};
|
||||||
@@ -114,6 +115,15 @@ pub mod helpers {
|
|||||||
header
|
header
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn unfinalized_header(num: u64) -> ImportedHeader<TestHeader> {
|
||||||
|
ImportedHeader {
|
||||||
|
header: test_header(num),
|
||||||
|
requires_justification: false,
|
||||||
|
is_finalized: false,
|
||||||
|
signal_hash: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn header_id(index: u8) -> HeaderId {
|
pub fn header_id(index: u8) -> HeaderId {
|
||||||
(test_header(index.into()).hash(), index as _)
|
(test_header(index.into()).hash(), index as _)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,15 +65,24 @@ pub enum Error {
|
|||||||
StorageValueUnavailable,
|
StorageValueUnavailable,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: crate::Trait> 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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
pub mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use sp_core::{Blake2Hasher, H256};
|
use sp_core::{Blake2Hasher, H256};
|
||||||
use sp_state_machine::{backend::Backend, prove_read, InMemoryBackend};
|
use sp_state_machine::{backend::Backend, prove_read, InMemoryBackend};
|
||||||
|
|
||||||
#[test]
|
/// Return valid storage proof and state root.
|
||||||
fn storage_proof_check() {
|
pub fn craft_valid_storage_proof() -> (H256, StorageProof) {
|
||||||
// construct storage proof
|
// construct storage proof
|
||||||
let backend = <InMemoryBackend<Blake2Hasher>>::from(vec![
|
let backend = <InMemoryBackend<Blake2Hasher>>::from(vec![
|
||||||
(None, vec![(b"key1".to_vec(), Some(b"value1".to_vec()))]),
|
(None, vec![(b"key1".to_vec(), Some(b"value1".to_vec()))]),
|
||||||
@@ -90,6 +99,13 @@ mod tests {
|
|||||||
.collect(),
|
.collect(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
(root, proof)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn storage_proof_check() {
|
||||||
|
let (root, proof) = craft_valid_storage_proof();
|
||||||
|
|
||||||
// check proof in runtime
|
// check proof in runtime
|
||||||
let checker = <StorageProofChecker<Blake2Hasher>>::new(root, proof.clone()).unwrap();
|
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"key1"), Ok(Some(b"value1".to_vec())));
|
||||||
|
|||||||
@@ -361,15 +361,6 @@ mod tests {
|
|||||||
use sp_finality_grandpa::{AuthorityId, SetId};
|
use sp_finality_grandpa::{AuthorityId, SetId};
|
||||||
use sp_runtime::{Digest, DigestItem};
|
use sp_runtime::{Digest, DigestItem};
|
||||||
|
|
||||||
fn unfinalized_header(num: u64) -> ImportedHeader<TestHeader> {
|
|
||||||
ImportedHeader {
|
|
||||||
header: test_header(num),
|
|
||||||
requires_justification: false,
|
|
||||||
is_finalized: false,
|
|
||||||
signal_hash: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn schedule_next_change(
|
fn schedule_next_change(
|
||||||
authorities: Vec<AuthorityId>,
|
authorities: Vec<AuthorityId>,
|
||||||
set_id: SetId,
|
set_id: SetId,
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ pub type BlockNumberOf<C> = <C as Chain>::BlockNumber;
|
|||||||
pub type HashOf<C> = <C as Chain>::Hash;
|
pub type HashOf<C> = <C as Chain>::Hash;
|
||||||
|
|
||||||
/// Hasher type used by the chain.
|
/// Hasher type used by the chain.
|
||||||
pub type HasherOf<C> = <C as Chain>::Header;
|
pub type HasherOf<C> = <C as Chain>::Hasher;
|
||||||
|
|
||||||
/// Header type used by the chain.
|
/// Header type used by the chain.
|
||||||
pub type HeaderOf<C> = <C as Chain>::Header;
|
pub type HeaderOf<C> = <C as Chain>::Header;
|
||||||
|
|||||||
Reference in New Issue
Block a user