CHT builder and prover
This commit is contained in:
parent
8e82b2f631
commit
fc8e9e39ec
@ -12,10 +12,110 @@
|
|||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
//! Canonical hash trie definitions and helper functions.
|
//! 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 the 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 util::{Bytes, H256, U256, HashDB, MemoryDB};
|
||||||
|
use util::trie::{self, TrieMut, TrieDBMut, Trie, TrieDB, Recorder};
|
||||||
|
use rlp::{Stream, RlpStream};
|
||||||
|
|
||||||
|
// 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.out()
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
/// The size of each CHT.
|
/// The size of each CHT.
|
||||||
pub const SIZE: u64 = 2048;
|
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> {
|
||||||
|
db: DB,
|
||||||
|
root: H256, // the root of this CHT.
|
||||||
|
number: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<DB: HashDB> 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) -> trie::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 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<F>(cht_num: u64, mut fetcher: F) -> Option<CHT<MemoryDB>>
|
||||||
|
where F: FnMut(BlockId) -> Option<BlockInfo>
|
||||||
|
{
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Convert a block number to a CHT number.
|
/// Convert a block number to a CHT number.
|
||||||
/// Returns `None` for `block_num` == 0, `Some` otherwise.
|
/// Returns `None` for `block_num` == 0, `Some` otherwise.
|
||||||
pub fn block_to_cht_number(block_num: u64) -> Option<u64> {
|
pub fn block_to_cht_number(block_num: u64) -> Option<u64> {
|
||||||
@ -37,6 +137,12 @@ pub fn start_number(cht_num: u64) -> u64 {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
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]
|
#[test]
|
||||||
fn block_to_cht_number() {
|
fn block_to_cht_number() {
|
||||||
assert!(::cht::block_to_cht_number(0).is_none());
|
assert!(::cht::block_to_cht_number(0).is_none());
|
||||||
|
Loading…
Reference in New Issue
Block a user