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};
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()
}}
}
pub const SIZE: u64 = 2048;
#[derive(Debug, Clone)]
pub struct CHT<DB: HashDB<KeccakHasher, DBValue>> {
db: DB,
root: H256, number: u64,
}
impl<DB: HashDB<KeccakHasher, DBValue>> CHT<DB> {
pub fn root(&self) -> H256 { self.root }
pub fn number(&self) -> u64 { self.number }
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()))
}
}
pub struct BlockInfo {
pub hash: H256,
pub parent_hash: H256,
pub total_difficulty: U256,
}
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();
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,
})
}
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
}
}
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,
}
}
pub fn block_to_cht_number(block_num: u64) -> Option<u64> {
match block_num {
0 => None,
n => Some((n - 1) / SIZE),
}
}
pub fn start_number(cht_num: u64) -> u64 {
(cht_num * SIZE) + 1
}
#[cfg(test)]
mod tests {
#[test]
fn size_is_lt_usize() {
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);
}
}