// Copyright 2015-2018 Parity Technologies (UK) Ltd. // This file is part of Parity. // Parity 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. // Parity 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 Parity. If not, see . //! 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 ethcore::ids::BlockId; use ethereum_types::{H256, U256}; use hashdb::HashDB; use memorydb::MemoryDB; use bytes::Bytes; use trie::{self, TrieMut, TrieDBMut, Trie, TrieDB, Recorder}; use rlp::{RlpStream, Rlp}; // encode a key. macro_rules! key { ($num: expr) => { ::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: DB, root: H256, // the root of this CHT. number: u64, } impl CHT { /// 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) -> trie::Result>> { if block_to_cht_number(num) != Some(self.number) { return Ok(None) } let mut recorder = Recorder::with_depth(from_level); let t = TrieDB::new(&self.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(cht_num: u64, mut fetcher: F) -> Option> where F: FnMut(BlockId) -> Option { let mut db = MemoryDB::new(); // 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::default(); { 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: db, root: 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(cht_num: u64, iterable: I) -> Option where I: IntoIterator { 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).into_vec(), val!(h, td).into_vec())) } if v.len() == SIZE as usize { Some(::triehash::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 = MemoryDB::new(); for node in proof { db.insert(&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::(0) .and_then(|h| rlp.val_at::(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 { 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); } }