mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-12 18:11:10 +00:00
Fetch changes trie roots + CHT-based proof for these roots (#896)
* build CHT for changes tries roots * collect chnages tries roots proof in key_changes_proof * flush check_changes_proof * fixed compilation * LightDataChecker now has a ref to the blockchain * continue passing proofs * new light db tests * more CHT tests * more tests for key changes proof when headers are missing * lost files
This commit is contained in:
committed by
Gav Wood
parent
7f8ee0f53b
commit
fa84cec382
@@ -23,6 +23,8 @@
|
||||
//! root has. A correct proof implies that the claimed block is identical to the one
|
||||
//! we discarded.
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use hash_db;
|
||||
use heapsize::HeapSizeOf;
|
||||
use trie;
|
||||
@@ -30,7 +32,8 @@ use trie;
|
||||
use primitives::{H256, convert_hash};
|
||||
use runtime_primitives::traits::{As, Header as HeaderT, SimpleArithmetic, One};
|
||||
use state_machine::backend::InMemory as InMemoryState;
|
||||
use state_machine::{prove_read, read_proof_check};
|
||||
use state_machine::{MemoryDB, TrieBackend, Backend as StateBackend,
|
||||
prove_read_on_trie_backend, read_proof_check, read_proof_check_on_proving_backend};
|
||||
|
||||
use error::{Error as ClientError, ErrorKind as ClientErrorKind, Result as ClientResult};
|
||||
|
||||
@@ -63,41 +66,48 @@ pub fn compute_root<Header, Hasher, I>(
|
||||
cht_size: u64,
|
||||
cht_num: Header::Number,
|
||||
hashes: I,
|
||||
) -> Option<Hasher::Out>
|
||||
) -> ClientResult<Hasher::Out>
|
||||
where
|
||||
Header: HeaderT,
|
||||
Hasher: hash_db::Hasher,
|
||||
Hasher::Out: Ord,
|
||||
I: IntoIterator<Item=Option<Header::Hash>>,
|
||||
I: IntoIterator<Item=ClientResult<Option<Header::Hash>>>,
|
||||
{
|
||||
build_pairs::<Header, I>(cht_size, cht_num, hashes)
|
||||
.map(|pairs| trie::trie_root::<Hasher, _, _, _>(pairs))
|
||||
Ok(trie::trie_root::<Hasher, _, _, _>(
|
||||
build_pairs::<Header, I>(cht_size, cht_num, hashes)?
|
||||
))
|
||||
}
|
||||
|
||||
/// Build CHT-based header proof.
|
||||
pub fn build_proof<Header, Hasher, I>(
|
||||
pub fn build_proof<Header, Hasher, BlocksI, HashesI>(
|
||||
cht_size: u64,
|
||||
cht_num: Header::Number,
|
||||
block_num: Header::Number,
|
||||
hashes: I
|
||||
) -> Option<Vec<Vec<u8>>>
|
||||
blocks: BlocksI,
|
||||
hashes: HashesI
|
||||
) -> ClientResult<Vec<Vec<u8>>>
|
||||
where
|
||||
Header: HeaderT,
|
||||
Hasher: hash_db::Hasher,
|
||||
Hasher::Out: Ord + HeapSizeOf,
|
||||
I: IntoIterator<Item=Option<Header::Hash>>,
|
||||
BlocksI: IntoIterator<Item=Header::Number>,
|
||||
HashesI: IntoIterator<Item=ClientResult<Option<Header::Hash>>>,
|
||||
{
|
||||
let transaction = build_pairs::<Header, I>(cht_size, cht_num, hashes)?
|
||||
let transaction = build_pairs::<Header, _>(cht_size, cht_num, hashes)?
|
||||
.into_iter()
|
||||
.map(|(k, v)| (None, k, Some(v)))
|
||||
.collect::<Vec<_>>();
|
||||
let storage = InMemoryState::<Hasher>::default().update(transaction);
|
||||
let (value, proof) = prove_read(storage, &encode_cht_key(block_num)).ok()?;
|
||||
if value.is_none() {
|
||||
None
|
||||
} else {
|
||||
Some(proof)
|
||||
let trie_storage = storage.try_into_trie_backend()
|
||||
.expect("InMemoryState::try_into_trie_backend always returns Some; qed");
|
||||
let mut total_proof = HashSet::new();
|
||||
for block in blocks.into_iter() {
|
||||
debug_assert_eq!(block_to_cht_number(cht_size, block), Some(cht_num));
|
||||
|
||||
let (value, proof) = prove_read_on_trie_backend(&trie_storage, &encode_cht_key(block))?;
|
||||
assert!(value.is_some(), "we have just built trie that includes the value for block");
|
||||
total_proof.extend(proof);
|
||||
}
|
||||
Ok(total_proof.into_iter().collect())
|
||||
}
|
||||
|
||||
/// Check CHT-based header proof.
|
||||
@@ -109,20 +119,104 @@ pub fn check_proof<Header, Hasher>(
|
||||
) -> ClientResult<()>
|
||||
where
|
||||
Header: HeaderT,
|
||||
Header::Hash: AsRef<[u8]>,
|
||||
Hasher: hash_db::Hasher,
|
||||
Hasher::Out: Ord + HeapSizeOf,
|
||||
{
|
||||
do_check_proof::<Header, Hasher, _>(local_root, local_number, remote_hash, move |local_root, local_cht_key|
|
||||
read_proof_check::<Hasher>(local_root, remote_proof,
|
||||
local_cht_key).map_err(|e| ClientError::from(e)))
|
||||
}
|
||||
|
||||
/// Check CHT-based header proof on pre-created proving backend.
|
||||
pub fn check_proof_on_proving_backend<Header, Hasher>(
|
||||
local_root: Header::Hash,
|
||||
local_number: Header::Number,
|
||||
remote_hash: Header::Hash,
|
||||
proving_backend: &TrieBackend<MemoryDB<Hasher>, Hasher>,
|
||||
) -> ClientResult<()>
|
||||
where
|
||||
Header: HeaderT,
|
||||
Hasher: hash_db::Hasher,
|
||||
Hasher::Out: Ord + HeapSizeOf,
|
||||
{
|
||||
do_check_proof::<Header, Hasher, _>(local_root, local_number, remote_hash, |_, local_cht_key|
|
||||
read_proof_check_on_proving_backend::<Hasher>(
|
||||
proving_backend, local_cht_key).map_err(|e| ClientError::from(e)))
|
||||
}
|
||||
|
||||
/// Check CHT-based header proof using passed checker function.
|
||||
fn do_check_proof<Header, Hasher, F>(
|
||||
local_root: Header::Hash,
|
||||
local_number: Header::Number,
|
||||
remote_hash: Header::Hash,
|
||||
checker: F,
|
||||
) -> ClientResult<()>
|
||||
where
|
||||
Header: HeaderT,
|
||||
Hasher: hash_db::Hasher,
|
||||
Hasher::Out: Ord + HeapSizeOf,
|
||||
F: FnOnce(Hasher::Out, &[u8]) -> ClientResult<Option<Vec<u8>>>,
|
||||
{
|
||||
let root: Hasher::Out = convert_hash(&local_root);
|
||||
let local_cht_key = encode_cht_key(local_number);
|
||||
let local_cht_value = read_proof_check::<Hasher>(root, remote_proof,
|
||||
&local_cht_key).map_err(|e| ClientError::from(e))?;
|
||||
let local_cht_value = local_cht_value.ok_or_else(|| ClientErrorKind::InvalidHeaderProof)?;
|
||||
let local_hash = decode_cht_value(&local_cht_value).ok_or_else(|| ClientErrorKind::InvalidHeaderProof)?;
|
||||
let local_cht_value = checker(root, &local_cht_key)?;
|
||||
let local_cht_value = local_cht_value.ok_or_else(|| ClientErrorKind::InvalidCHTProof)?;
|
||||
let local_hash = decode_cht_value(&local_cht_value).ok_or_else(|| ClientErrorKind::InvalidCHTProof)?;
|
||||
match &local_hash[..] == remote_hash.as_ref() {
|
||||
true => Ok(()),
|
||||
false => Err(ClientErrorKind::InvalidHeaderProof.into()),
|
||||
false => Err(ClientErrorKind::InvalidCHTProof.into()),
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Group ordered blocks by CHT number and call functor with blocks of each group.
|
||||
pub fn for_each_cht_group<Header, I, F, P>(
|
||||
cht_size: u64,
|
||||
blocks: I,
|
||||
mut functor: F,
|
||||
mut functor_param: P,
|
||||
) -> ClientResult<()>
|
||||
where
|
||||
Header: HeaderT,
|
||||
I: IntoIterator<Item=Header::Number>,
|
||||
F: FnMut(P, Header::Number, Vec<Header::Number>) -> ClientResult<P>,
|
||||
{
|
||||
let mut current_cht_num = None;
|
||||
let mut current_cht_blocks = Vec::new();
|
||||
for block in blocks {
|
||||
let new_cht_num = match block_to_cht_number(cht_size, block.as_()) {
|
||||
Some(new_cht_num) => new_cht_num,
|
||||
None => return Err(ClientErrorKind::Backend(format!(
|
||||
"Cannot compute CHT root for the block #{}", block)).into()
|
||||
),
|
||||
};
|
||||
|
||||
let advance_to_next_cht = current_cht_num.is_some() && current_cht_num != Some(new_cht_num);
|
||||
if advance_to_next_cht {
|
||||
let current_cht_num = current_cht_num.expect("advance_to_next_cht is true;
|
||||
it is true only when current_cht_num is Some; qed");
|
||||
assert!(new_cht_num > current_cht_num, "for_each_cht_group only supports ordered iterators");
|
||||
|
||||
functor_param = functor(
|
||||
functor_param,
|
||||
As::sa(current_cht_num),
|
||||
::std::mem::replace(&mut current_cht_blocks, Vec::new()),
|
||||
)?;
|
||||
}
|
||||
|
||||
current_cht_blocks.push(block);
|
||||
current_cht_num = Some(new_cht_num);
|
||||
}
|
||||
|
||||
if let Some(current_cht_num) = current_cht_num {
|
||||
functor(
|
||||
functor_param,
|
||||
As::sa(current_cht_num),
|
||||
::std::mem::replace(&mut current_cht_blocks, Vec::new()),
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Build pairs for computing CHT.
|
||||
@@ -130,26 +224,29 @@ fn build_pairs<Header, I>(
|
||||
cht_size: u64,
|
||||
cht_num: Header::Number,
|
||||
hashes: I
|
||||
) -> Option<Vec<(Vec<u8>, Vec<u8>)>>
|
||||
) -> ClientResult<Vec<(Vec<u8>, Vec<u8>)>>
|
||||
where
|
||||
Header: HeaderT,
|
||||
I: IntoIterator<Item=Option<Header::Hash>>,
|
||||
I: IntoIterator<Item=ClientResult<Option<Header::Hash>>>,
|
||||
{
|
||||
let start_num = start_number(cht_size, cht_num);
|
||||
let mut pairs = Vec::new();
|
||||
let mut hash_number = start_num;
|
||||
for hash in hashes.into_iter().take(cht_size as usize) {
|
||||
pairs.push(hash.map(|hash| (
|
||||
let hash = hash?.ok_or_else(|| ClientError::from(
|
||||
ClientErrorKind::MissingHashRequiredForCHT(cht_num.as_(), hash_number.as_())
|
||||
))?;
|
||||
pairs.push((
|
||||
encode_cht_key(hash_number).to_vec(),
|
||||
encode_cht_value(hash)
|
||||
))?);
|
||||
));
|
||||
hash_number += Header::Number::one();
|
||||
}
|
||||
|
||||
if pairs.len() as u64 == cht_size {
|
||||
Some(pairs)
|
||||
Ok(pairs)
|
||||
} else {
|
||||
None
|
||||
Err(ClientErrorKind::MissingHashRequiredForCHT(cht_num.as_(), hash_number.as_()).into())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,30 +338,59 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn build_pairs_fails_when_no_enough_blocks() {
|
||||
assert!(build_pairs::<Header, _>(SIZE, 0, vec![Some(1.into()); SIZE as usize / 2]).is_none());
|
||||
assert!(build_pairs::<Header, _>(SIZE, 0,
|
||||
::std::iter::repeat_with(|| Ok(Some(1.into()))).take(SIZE as usize / 2)).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_pairs_fails_when_missing_block() {
|
||||
assert!(build_pairs::<Header, _>(SIZE, 0, ::std::iter::repeat(Some(1.into())).take(SIZE as usize / 2)
|
||||
.chain(::std::iter::once(None))
|
||||
.chain(::std::iter::repeat(Some(2.into())).take(SIZE as usize / 2 - 1))).is_none());
|
||||
assert!(build_pairs::<Header, _>(SIZE, 0, ::std::iter::repeat_with(|| Ok(Some(1.into()))).take(SIZE as usize / 2)
|
||||
.chain(::std::iter::once(Ok(None)))
|
||||
.chain(::std::iter::repeat_with(|| Ok(Some(2.into()))).take(SIZE as usize / 2 - 1))).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compute_root_works() {
|
||||
assert!(compute_root::<Header, Blake2Hasher, _>(SIZE, 42, vec![Some(1.into()); SIZE as usize]).is_some());
|
||||
assert!(compute_root::<Header, Blake2Hasher, _>(SIZE, 42,
|
||||
::std::iter::repeat_with(|| Ok(Some(1.into()))).take(SIZE as usize)).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_proof_fails_when_querying_wrong_block() {
|
||||
assert!(build_proof::<Header, Blake2Hasher, _>(
|
||||
SIZE, 0, (SIZE * 1000) as u64, vec![Some(1.into()); SIZE as usize]).is_none());
|
||||
#[should_panic]
|
||||
fn build_proof_panics_when_querying_wrong_block() {
|
||||
assert!(build_proof::<Header, Blake2Hasher, _, _>(
|
||||
SIZE, 0, vec![(SIZE * 1000) as u64],
|
||||
::std::iter::repeat_with(|| Ok(Some(1.into()))).take(SIZE as usize)).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_proof_works() {
|
||||
assert!(build_proof::<Header, Blake2Hasher, _>(
|
||||
SIZE, 0, (SIZE / 2) as u64, vec![Some(1.into()); SIZE as usize]).is_some());
|
||||
assert!(build_proof::<Header, Blake2Hasher, _, _>(
|
||||
SIZE, 0, vec![(SIZE / 2) as u64],
|
||||
::std::iter::repeat_with(|| Ok(Some(1.into()))).take(SIZE as usize)).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn for_each_cht_group_panics() {
|
||||
let _ = for_each_cht_group::<Header, _, _, _>(SIZE, vec![SIZE * 5, SIZE * 2], |_, _, _| Ok(()), ());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_each_cht_group_works() {
|
||||
let _ = for_each_cht_group::<Header, _, _, _>(SIZE, vec![
|
||||
SIZE * 2 + 1, SIZE * 2 + 2, SIZE * 2 + 5,
|
||||
SIZE * 4 + 1, SIZE * 4 + 7,
|
||||
SIZE * 6 + 1
|
||||
], |_, cht_num, blocks| {
|
||||
match cht_num {
|
||||
2 => assert_eq!(blocks, vec![SIZE * 2 + 1, SIZE * 2 + 2, SIZE * 2 + 5]),
|
||||
4 => assert_eq!(blocks, vec![SIZE * 4 + 1, SIZE * 4 + 7]),
|
||||
6 => assert_eq!(blocks, vec![SIZE * 6 + 1]),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}, ());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user