vapcore-light 1.12.0

Tetsy Vapory (VapCore) Light Client Implementation (Block Import IO Service, Blockchain Data Fetching, Light Client Header Chain Storage, Tetsy Light Protocol (PLP) Provider, Light Transaction Queue, CHT Definitions, Light Client Data Cache), Tetsy Light Protocol (PLP) Implementation, P2P Network I/O and Event Context Generalization, Peer Error Handling & Punishment, Request Load Timer & Distribution Manager, Pending Request Set Storage, Request Credit Management, Light Client Request Types, Request Chain Builder Utility, On-demand Chain Request Service over LES (for RPCs), ResponseGuard Implementation)
Documentation
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
// This file is part of Tetsy Vapory.

// Tetsy Vapory 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.

// Tetsy Vapory 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 Tetsy Vapory.  If not, see <http://www.gnu.org/licenses/>.

//! Canonical hash trie definitions and helper functions.
//!
//! Each CHT is a trie mapping block numbers to canonical hashes and total difficulty.
//! One is generated for every `SIZE` blocks, allowing us to discard those blocks in
//! favor of the trie root. When the "ancient" blocks need to be accessed, we simply
//! request an inclusion proof of a specific block number against the trie with the
//! root has. A correct proof implies that the claimed block is identical to the one
//! we discarded.

use common_types::ids::BlockId;
use vapory_types::{H256, U256};
use tetsy_hash_db::HashDB;
use tetsy_keccak_hasher::KeccakHasher;
use tetsy_kvdb::DBValue;
use tetsy_memory_db::MemoryDB;
use journaldb::new_tetsy_memory_db;
use bytes::Bytes;
use trie::{TrieMut, Trie, Recorder};
use vaptrie::{self, TrieDB, TrieDBMut};
use tetsy_rlp::{RlpStream, Rlp};

// encode a key.
macro_rules! key {
	($num: expr) => { ::tetsy_rlp::encode(&$num) }
}

macro_rules! val {
	($hash: expr, $td: expr) => {{
		let mut stream = RlpStream::new_list(2);
		stream.append(&$hash).append(&$td);
		stream.drain()
	}}
}

/// The size of each CHT.
pub const SIZE: u64 = 2048;

/// A canonical hash trie. This is generic over any database it can query.
/// See module docs for more details.
#[derive(Debug, Clone)]
pub struct CHT<DB: HashDB<KeccakHasher, DBValue>> {
	db: DB,
	root: H256, // the root of this CHT.
	number: u64,
}

impl<DB: HashDB<KeccakHasher, DBValue>> CHT<DB> {
	/// Query the root of the CHT.
	pub fn root(&self) -> H256 { self.root }

	/// Query the number of the CHT.
	pub fn number(&self) -> u64 { self.number }

	/// Generate an inclusion proof for the entry at a specific block.
	/// Nodes before level `from_level` will be omitted.
	/// Returns an error on an incomplete trie, and `Ok(None)` on an unprovable request.
	pub fn prove(&self, num: u64, from_level: u32) -> vaptrie::Result<Option<Vec<Bytes>>> {
		if block_to_cht_number(num) != Some(self.number) { return Ok(None) }

		let mut recorder = Recorder::with_depth(from_level);
		let db: &dyn HashDB<_,_> = &self.db;
		let t = TrieDB::new(&db, &self.root)?;
		t.get_with(&key!(num), &mut recorder)?;

		Ok(Some(recorder.drain().into_iter().map(|x| x.data).collect()))
	}
}

/// Block information necessary to build a CHT.
pub struct BlockInfo {
	/// The block's hash.
	pub hash: H256,
	/// The block's parent's hash.
	pub parent_hash: H256,
	/// The block's total difficulty.
	pub total_difficulty: U256,
}

/// Build an in-memory CHT from a closure which provides necessary information
/// about blocks. If the fetcher ever fails to provide the info, the CHT
/// will not be generated.
pub fn build<F>(cht_num: u64, mut fetcher: F)
	-> Option<CHT<MemoryDB<KeccakHasher, tetsy_memory_db::HashKey<KeccakHasher>, DBValue>>>
	where F: FnMut(BlockId) -> Option<BlockInfo>
{
	let mut db = new_tetsy_memory_db();

	// start from the last block by number and work backwards.
	let last_num = start_number(cht_num + 1) - 1;
	let mut id = BlockId::Number(last_num);

	let mut root = H256::zero();

	{
		let mut t = TrieDBMut::new(&mut db, &mut root);
		for blk_num in (0..SIZE).map(|n| last_num - n) {
			let info = match fetcher(id) {
				Some(info) => info,
				None => return None,
			};

			id = BlockId::Hash(info.parent_hash);
			t.insert(&key!(blk_num), &val!(info.hash, info.total_difficulty))
				.expect("fresh in-memory database is infallible; qed");
		}
	}

	Some(CHT {
		db,
		root,
		number: cht_num,
	})
}

/// Compute a CHT root from an iterator of (hash, td) pairs. Fails if shorter than
/// SIZE items. The items are assumed to proceed sequentially from `start_number(cht_num)`.
/// Discards the trie's nodes.
pub fn compute_root<I>(cht_num: u64, iterable: I) -> Option<H256>
	where I: IntoIterator<Item=(H256, U256)>
{
	let mut v = Vec::with_capacity(SIZE as usize);
	let start_num = start_number(cht_num) as usize;

	for (i, (h, td)) in iterable.into_iter().take(SIZE as usize).enumerate() {
		v.push((key!(i + start_num), val!(h, td)))
	}

	if v.len() == SIZE as usize {
		Some(::triehash::tetsy_trie_root(v))
	} else {
		None
	}
}

/// Check a proof for a CHT.
/// Given a set of a trie nodes, a number to query, and a trie root,
/// verify the given trie branch and extract the canonical hash and total difficulty.
// TODO: better support for partially-checked queries.
pub fn check_proof(proof: &[Bytes], num: u64, root: H256) -> Option<(H256, U256)> {
	let mut db = new_tetsy_memory_db();

	for node in proof { db.insert(tetsy_hash_db::EMPTY_PREFIX, &node[..]); }
	let res = match TrieDB::new(&db, &root) {
		Err(_) => return None,
		Ok(trie) => trie.get_with(&key!(num), |val: &[u8]| {
			let rlp = Rlp::new(val);
			rlp.val_at::<H256>(0)
				.and_then(|h| rlp.val_at::<U256>(1).map(|td| (h, td)))
				.ok()
		})
	};

	match res {
		Ok(Some(Some((hash, td)))) => Some((hash, td)),
		_ => None,
	}
}

/// Convert a block number to a CHT number.
/// Returns `None` for `block_num` == 0, `Some` otherwise.
pub fn block_to_cht_number(block_num: u64) -> Option<u64> {
	match block_num {
		0 => None,
		n => Some((n - 1) / SIZE),
	}
}

/// 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_num: u64) -> u64 {
	(cht_num * SIZE) + 1
}

#[cfg(test)]
mod tests {
	#[test]
	fn size_is_lt_usize() {
		// to ensure safe casting on the target platform.
		assert!(::cht::SIZE < usize::max_value() as u64)
	}

	#[test]
	fn block_to_cht_number() {
		assert!(::cht::block_to_cht_number(0).is_none());
		assert_eq!(::cht::block_to_cht_number(1).unwrap(), 0);
		assert_eq!(::cht::block_to_cht_number(::cht::SIZE + 1).unwrap(), 1);
		assert_eq!(::cht::block_to_cht_number(::cht::SIZE).unwrap(), 0);
	}

	#[test]
	fn start_number() {
		assert_eq!(::cht::start_number(0), 1);
		assert_eq!(::cht::start_number(1), ::cht::SIZE + 1);
		assert_eq!(::cht::start_number(2), ::cht::SIZE * 2 + 1);
	}
}