) -> ClientResult,
{
let mut current_cht_num = None;
let mut current_cht_blocks = Vec::new();
for block in blocks {
let new_cht_num = block_to_cht_number(cht_size, block).ok_or_else(|| ClientError::Backend(format!(
"Cannot compute CHT root for the block #{}", block))
)?;
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,
current_cht_num,
std::mem::take(&mut current_cht_blocks),
)?;
}
current_cht_blocks.push(block);
current_cht_num = Some(new_cht_num);
}
if let Some(current_cht_num) = current_cht_num {
functor(
functor_param,
current_cht_num,
std::mem::take(&mut current_cht_blocks),
)?;
}
Ok(())
}
/// Build pairs for computing CHT.
fn build_pairs(
cht_size: Header::Number,
cht_num: Header::Number,
hashes: I
) -> ClientResult, Vec)>>
where
Header: HeaderT,
I: IntoIterator- >>,
{
let start_num = start_number(cht_size, cht_num);
let mut pairs = Vec::new();
let mut hash_index = Header::Number::zero();
for hash in hashes.into_iter() {
let hash = hash?.ok_or_else(|| ClientError::from(
ClientError::MissingHashRequiredForCHT
))?;
pairs.push((
encode_cht_key(start_num + hash_index).to_vec(),
encode_cht_value(hash)
));
hash_index += Header::Number::one();
if hash_index == cht_size {
break;
}
}
if hash_index == cht_size {
Ok(pairs)
} else {
Err(ClientError::MissingHashRequiredForCHT)
}
}
/// Get the starting block of a given CHT.
/// CHT 0 includes block 1...SIZE,
/// CHT 1 includes block SIZE + 1 ... 2*SIZE
/// More generally: CHT N includes block (1 + N*SIZE)...((N+1)*SIZE).
/// This is because the genesis hash is assumed to be known
/// and including it would be redundant.
pub fn start_number(cht_size: N, cht_num: N) -> N {
(cht_num * cht_size) + N::one()
}
/// Get the ending block of a given CHT.
pub fn end_number(cht_size: N, cht_num: N) -> N {
(cht_num + N::one()) * cht_size
}
/// Convert a block number to a CHT number.
/// Returns `None` for `block_num` == 0, `Some` otherwise.
pub fn block_to_cht_number(cht_size: N, block_num: N) -> Option {
if block_num == N::zero() {
None
} else {
Some((block_num - N::one()) / cht_size)
}
}
/// Convert header number into CHT key.
pub fn encode_cht_key(number: N) -> Vec {
number.encode()
}
/// Convert header hash into CHT value.
fn encode_cht_value>(hash: Hash) -> Vec {
hash.as_ref().to_vec()
}
/// Convert CHT value into block header hash.
pub fn decode_cht_value(value: &[u8]) -> Option {
match value.len() {
32 => Some(H256::from_slice(&value[0..32])),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use sp_runtime::{generic, traits::BlakeTwo256};
type Header = generic::Header;
#[test]
fn is_build_required_works() {
assert_eq!(is_build_required(SIZE, 0u32.into()), None);
assert_eq!(is_build_required(SIZE, 1u32.into()), None);
assert_eq!(is_build_required(SIZE, SIZE), None);
assert_eq!(is_build_required(SIZE, SIZE + 1), None);
assert_eq!(is_build_required(SIZE, 2 * SIZE), None);
assert_eq!(is_build_required(SIZE, 2 * SIZE + 1), Some(0));
assert_eq!(is_build_required(SIZE, 2 * SIZE + 2), None);
assert_eq!(is_build_required(SIZE, 3 * SIZE), None);
assert_eq!(is_build_required(SIZE, 3 * SIZE + 1), Some(1));
assert_eq!(is_build_required(SIZE, 3 * SIZE + 2), None);
}
#[test]
fn max_cht_number_works() {
assert_eq!(max_cht_number(SIZE, 0u32.into()), None);
assert_eq!(max_cht_number(SIZE, 1u32.into()), None);
assert_eq!(max_cht_number(SIZE, SIZE), None);
assert_eq!(max_cht_number(SIZE, SIZE + 1), None);
assert_eq!(max_cht_number(SIZE, 2 * SIZE), None);
assert_eq!(max_cht_number(SIZE, 2 * SIZE + 1), Some(0));
assert_eq!(max_cht_number(SIZE, 2 * SIZE + 2), Some(0));
assert_eq!(max_cht_number(SIZE, 3 * SIZE), Some(0));
assert_eq!(max_cht_number(SIZE, 3 * SIZE + 1), Some(1));
assert_eq!(max_cht_number(SIZE, 3 * SIZE + 2), Some(1));
}
#[test]
fn start_number_works() {
assert_eq!(start_number(SIZE, 0u32), 1u32);
assert_eq!(start_number(SIZE, 1u32), SIZE + 1);
assert_eq!(start_number(SIZE, 2u32), SIZE + SIZE + 1);
}
#[test]
fn end_number_works() {
assert_eq!(end_number(SIZE, 0u32), SIZE);
assert_eq!(end_number(SIZE, 1u32), SIZE + SIZE);
assert_eq!(end_number(SIZE, 2u32), SIZE + SIZE + SIZE);
}
#[test]
fn build_pairs_fails_when_no_enough_blocks() {
assert!(build_pairs::(SIZE as _, 0,
::std::iter::repeat_with(|| Ok(Some(H256::from_low_u64_be(1)))).take(SIZE as usize / 2)).is_err());
}
#[test]
fn build_pairs_fails_when_missing_block() {
assert!(build_pairs::(
SIZE as _,
0,
::std::iter::repeat_with(|| Ok(Some(H256::from_low_u64_be(1))))
.take(SIZE as usize / 2)
.chain(::std::iter::once(Ok(None)))
.chain(::std::iter::repeat_with(|| Ok(Some(H256::from_low_u64_be(2))))
.take(SIZE as usize / 2 - 1))
).is_err());
}
#[test]
fn compute_root_works() {
assert!(compute_root::(
SIZE as _,
42,
::std::iter::repeat_with(|| Ok(Some(H256::from_low_u64_be(1))))
.take(SIZE as usize)
).is_ok());
}
#[test]
#[should_panic]
fn build_proof_panics_when_querying_wrong_block() {
assert!(build_proof::(
SIZE as _,
0,
vec![(SIZE * 1000) as u64],
::std::iter::repeat_with(|| Ok(Some(H256::from_low_u64_be(1))))
.take(SIZE as usize)
).is_err());
}
#[test]
fn build_proof_works() {
assert!(build_proof::(
SIZE as _,
0,
vec![(SIZE / 2) as u64],
::std::iter::repeat_with(|| Ok(Some(H256::from_low_u64_be(1))))
.take(SIZE as usize)
).is_ok());
}
#[test]
#[should_panic]
fn for_each_cht_group_panics() {
let cht_size = SIZE as u64;
let _ = for_each_cht_group::(
cht_size,
vec![cht_size * 5, cht_size * 2],
|_, _, _| Ok(()),
(),
);
}
#[test]
fn for_each_cht_group_works() {
let cht_size = SIZE as u64;
let _ = for_each_cht_group::(
cht_size,
vec![
cht_size * 2 + 1, cht_size * 2 + 2, cht_size * 2 + 5,
cht_size * 4 + 1, cht_size * 4 + 7,
cht_size * 6 + 1
], |_, cht_num, blocks| {
match cht_num {
2 => assert_eq!(blocks, vec![cht_size * 2 + 1, cht_size * 2 + 2, cht_size * 2 + 5]),
4 => assert_eq!(blocks, vec![cht_size * 4 + 1, cht_size * 4 + 7]),
6 => assert_eq!(blocks, vec![cht_size * 6 + 1]),
_ => unreachable!(),
}
Ok(())
}, ()
);
}
}