use std::collections::HashMap;
use std::sync::Arc;
use byteorder::{ByteOrder, LittleEndian};
use crate::def::HPFILE_RANGE;
use hpfile::HPFile;
use xxhash_rust::xxh32;
const IGNORED_COUNT: u64 = 2048 - 256; const TWIG_FULL_LENGTH: u64 = 4095; const TWIG_ENTRY_COUNT: u64 = TWIG_FULL_LENGTH - IGNORED_COUNT;
pub const TWIG_SIZE: u64 = 12 + TWIG_ENTRY_COUNT * 32;
#[derive(Debug)]
pub struct TwigFile {
pub hp_file: HPFile,
}
impl TwigFile {
pub fn new(buffer_size: usize, segment_size: i64, dir_name: String) -> TwigFile {
TwigFile {
hp_file: HPFile::new(buffer_size as i64, segment_size, dir_name, false).unwrap(),
}
}
pub fn empty() -> TwigFile {
TwigFile {
hp_file: HPFile::empty(),
}
}
pub fn is_empty(&self) -> bool {
self.hp_file.is_empty()
}
fn append_twig(&self, m_tree: &[[u8; 32]], last_entry_end_pos: i64, buffer: &mut Vec<u8>) {
if self.hp_file.is_empty() {
return;
}
if last_entry_end_pos < 0 {
panic!("Invalid last entry end position: {}", last_entry_end_pos);
}
if m_tree.len() != TWIG_FULL_LENGTH as usize + 1 {
panic!("len(m_tree): {} != {}", m_tree.len(), TWIG_FULL_LENGTH);
}
let mut buf: [u8; 12] = [0; 12];
LittleEndian::write_i64(&mut buf[0..8], last_entry_end_pos);
let digest = xxh32::xxh32(&buf[0..8], 0);
LittleEndian::write_u32(&mut buf[8..], digest);
_ = self.hp_file.append(&buf[..], buffer);
for i in 1..256 {
_ = self.hp_file.append(&m_tree[i as usize][..], buffer);
}
for i in 2048..TWIG_FULL_LENGTH + 1 {
_ = self.hp_file.append(&m_tree[i as usize][..], buffer);
}
}
pub fn get_first_entry_pos(&self, mut twig_id: u64) -> i64 {
if twig_id == 0 {
return 0;
}
twig_id -= 1;
let mut buf: [u8; 12] = [0; 12];
self.hp_file
.read_at(&mut buf[..], (twig_id * TWIG_SIZE) as i64 % HPFILE_RANGE)
.unwrap();
let mut digest = [0; 4];
LittleEndian::write_u32(&mut digest, xxh32::xxh32(&buf[0..8], 0));
assert_eq!(buf[8..], digest[..], "Checksum Error!");
LittleEndian::read_i64(&buf[0..8])
}
pub fn get_leaf_range(hash_id: i64) -> (i64, i64) {
if (256..512).contains(&hash_id) {
(hash_id * 8, hash_id * 8 + 8)
} else if hash_id < 1024 {
return ((hash_id / 2) * 8, (hash_id / 2) * 8 + 8);
} else if hash_id < 2048 {
return ((hash_id / 4) * 8, (hash_id / 4) * 8 + 8);
} else {
panic!("Invalid hashID")
}
}
pub fn get_hash_node_in_ignore_range(
&self,
twig_id: u64,
hash_id: i64,
cache: &mut HashMap<i64, [u8; 32]>,
out: &mut [u8; 32],
) {
let (start, end) = Self::get_leaf_range(hash_id);
let mut buf = [0; 32 * 8];
let offset =
twig_id * TWIG_SIZE + 12 + (start as u64 - 1) * 32
- IGNORED_COUNT * 32;
let offset = (offset as i64) % HPFILE_RANGE;
let num_bytes_read = self.hp_file.read_at(&mut buf[..], offset).unwrap_or(0);
if num_bytes_read != buf.len() {
for i in 0..8 {
self.hp_file
.read_at(&mut buf[i * 32..i * 32 + 32], offset + (i * 32) as i64)
.unwrap();
}
}
{
let count = ((end - start) / 2) as usize;
let mut levels = Vec::with_capacity(count);
let mut lefts = Vec::with_capacity(count);
let mut rights = Vec::with_capacity(count);
for i in start / 2..end / 2 {
let off = ((i - start / 2) * 64) as usize;
levels.push(0u8);
lefts.push(<[u8; 32]>::try_from(&buf[off..off + 32]).unwrap());
rights.push(<[u8; 32]>::try_from(&buf[off + 32..off + 64]).unwrap());
}
let mut out = vec![[0u8; 32]; count];
crate::utils::hasher::batch_node_hash_cpu(&levels, &lefts, &rights, &mut out);
for (idx, i) in (start / 2..end / 2).enumerate() {
cache.insert(i, out[idx]);
}
}
{
let count = ((end / 4) - (start / 4)) as usize;
let mut levels = Vec::with_capacity(count);
let mut lefts = Vec::with_capacity(count);
let mut rights = Vec::with_capacity(count);
for i in start / 4..end / 4 {
levels.push(1u8);
lefts.push(*cache.get(&(i * 2)).unwrap());
rights.push(*cache.get(&(i * 2 + 1)).unwrap());
}
let mut out_l1 = vec![[0u8; 32]; count];
crate::utils::hasher::batch_node_hash_cpu(&levels, &lefts, &rights, &mut out_l1);
for (idx, i) in (start / 4..end / 4).enumerate() {
cache.insert(i, out_l1[idx]);
}
}
{
let id = start / 8;
let levels = [2u8];
let lefts = [*cache.get(&(id * 2)).unwrap()];
let rights = [*cache.get(&(id * 2 + 1)).unwrap()];
let mut out_l2 = [[0u8; 32]; 1];
crate::utils::hasher::batch_node_hash_cpu(&levels, &lefts, &rights, &mut out_l2);
cache.insert(id, out_l2[0]);
}
out.copy_from_slice(cache.get(&hash_id).unwrap().as_slice());
}
pub fn get_hash_root(&self, twig_id: u64, buf: &mut [u8; 32]) {
self.get_hash_node(twig_id, 1, &mut HashMap::new(), buf)
}
pub fn get_hash_node<'a>(
&'a self,
twig_id: u64,
hash_id: i64,
cache: &'a mut HashMap<i64, [u8; 32]>,
buf: &mut [u8; 32],
) {
if hash_id <= 0 || hash_id >= 4096 {
panic!("Invalid hashID: {}", hash_id);
}
if (256..2048).contains(&hash_id) {
return self.get_hash_node_in_ignore_range(twig_id, hash_id, cache, buf);
}
let mut offset =
twig_id * TWIG_SIZE + 12 + (hash_id as u64 - 1) * 32;
if hash_id >= 2048 {
offset -= IGNORED_COUNT * 32;
}
let offset = (offset as i64) % HPFILE_RANGE;
self.hp_file
.read_at(&mut buf[..], offset)
.expect("TODO: panic message");
}
pub fn truncate(&self, size: i64) {
if let Err(err) = self.hp_file.truncate(size) {
panic!("{}", err)
}
}
pub fn close(&self) {
self.hp_file.close();
}
pub fn prune_head(&self, off: i64) {
if let Err(err) = self.hp_file.prune_head(off) {
panic!("{}", err)
}
}
}
pub struct TwigFileWriter {
pub twig_file: Arc<TwigFile>,
wrbuf: Vec<u8>,
}
impl TwigFileWriter {
pub fn new(twig_file: Arc<TwigFile>, buffer_size: usize) -> TwigFileWriter {
TwigFileWriter {
twig_file,
wrbuf: Vec::with_capacity(buffer_size),
}
}
pub fn temp_clone(&self) -> TwigFileWriter {
TwigFileWriter {
twig_file: self.twig_file.clone(),
wrbuf: Vec::with_capacity(0),
}
}
pub fn append_twig(&mut self, m_tree: &[[u8; 32]], last_entry_end_pos: i64) {
self.twig_file
.append_twig(m_tree, last_entry_end_pos, &mut self.wrbuf);
}
pub fn flush(&mut self) {
self.twig_file
.hp_file
.flush(&mut self.wrbuf, false)
.unwrap();
}
}
#[cfg(test)]
mod twig_file_test {
use super::*;
use crate::{
merkletree::twig::{sync_mtree, TwigMT},
test_helper::TempDir,
};
fn generate_twig(mut rand_num: u32, twig: &mut TwigMT) {
for i in 2048..4096 {
let mut j = 0;
while j + 4 < 32 {
LittleEndian::write_u32(&mut twig[i][j..j + 4], rand_num);
rand_num += 257;
j += 4;
}
}
sync_mtree(twig, 0, 2048);
}
#[test]
fn twig_file_all() {
let dir = TempDir::new("./twig");
let tf = TwigFile::new(64 * 1024, 1024 * 1024, dir.to_str());
let mut twigs = [[[0; 32]; 4096]; 3];
generate_twig(1000, &mut twigs[0]);
generate_twig(1111111, &mut twigs[1]);
generate_twig(2222222, &mut twigs[2]);
let mut buffer = vec![];
tf.append_twig(&twigs[0][..], 789, &mut buffer);
tf.append_twig(&twigs[1][..], 1000789, &mut buffer);
tf.append_twig(&twigs[2][..], 2000789, &mut buffer);
let _ = tf.hp_file.flush(&mut buffer, false);
tf.close();
let tf = TwigFile::new(64 * 1024, 1024 * 1024, dir.to_str());
assert_eq!(0, tf.get_first_entry_pos(0));
assert_eq!(789, tf.get_first_entry_pos(1));
assert_eq!(1000789, tf.get_first_entry_pos(2));
assert_eq!(2000789, tf.get_first_entry_pos(3));
for twig_id in 0..3 {
for i in 1..4096 {
let mut cache = HashMap::<i64, [u8; 32]>::new();
let mut buf = [0; 32];
tf.get_hash_node(twig_id, i, &mut cache, &mut buf);
assert_eq!(buf[..], twigs[twig_id as usize][i as usize][..]);
}
}
for twig_id in 0..3 {
let mut cache = HashMap::<i64, [u8; 32]>::new();
for i in 1..4096 {
if cache.contains_key(&i) {
let bz = cache.get(&i).unwrap();
assert_eq!(&twigs[twig_id as usize][i as usize][..], bz.as_slice());
} else {
let mut buf = [0; 32];
tf.get_hash_node(twig_id, i, &mut cache, &mut buf);
assert_eq!(buf[..], twigs[twig_id as usize][i as usize][..]);
}
}
}
tf.close();
}
}