use std::collections::BTreeMap;
use bincode::Options;
use serde_big_array::BigArray;
use zebra_chain::{
amount::NonNegative,
block::Height,
block_info::BlockInfo,
history_tree::{HistoryTreeError, NonEmptyHistoryTree},
parameters::{Network, NetworkKind},
primitives::zcash_history,
value_balance::ValueBalance,
};
use crate::service::finalized_state::disk_format::{FromDisk, IntoDisk};
impl IntoDisk for ValueBalance<NonNegative> {
type Bytes = [u8; 48];
fn as_bytes(&self) -> Self::Bytes {
self.to_bytes()
}
}
impl FromDisk for ValueBalance<NonNegative> {
fn from_bytes(bytes: impl AsRef<[u8]>) -> Self {
ValueBalance::from_bytes(bytes.as_ref()).expect("ValueBalance should be parsable")
}
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct HistoryTreeParts {
network_kind: NetworkKind,
size: u32,
peaks: BTreeMap<u32, zcash_history::Entry>,
current_height: Height,
}
impl HistoryTreeParts {
pub(crate) fn with_network(
self,
network: &Network,
) -> Result<NonEmptyHistoryTree, HistoryTreeError> {
assert_eq!(
self.network_kind,
network.kind(),
"history tree network kind should match current network"
);
NonEmptyHistoryTree::from_cache(network, self.size, self.peaks, self.current_height)
}
}
impl From<&NonEmptyHistoryTree> for HistoryTreeParts {
fn from(history_tree: &NonEmptyHistoryTree) -> Self {
HistoryTreeParts {
network_kind: history_tree.network().kind(),
size: history_tree.size(),
peaks: history_tree.peaks().clone(),
current_height: history_tree.current_height(),
}
}
}
impl IntoDisk for HistoryTreeParts {
type Bytes = Vec<u8>;
fn as_bytes(&self) -> Self::Bytes {
bincode::DefaultOptions::new()
.serialize(self)
.expect("serialization to vec doesn't fail")
}
}
const LEGACY_MAX_ENTRY_SIZE: usize = 253;
#[derive(serde::Serialize, serde::Deserialize)]
struct LegacyHistoryTreeParts {
network_kind: NetworkKind,
size: u32,
peaks: BTreeMap<u32, LegacyEntry>,
current_height: Height,
}
#[derive(serde::Serialize, serde::Deserialize)]
struct LegacyEntry {
#[serde(with = "BigArray")]
inner: [u8; LEGACY_MAX_ENTRY_SIZE],
}
impl From<LegacyHistoryTreeParts> for HistoryTreeParts {
fn from(legacy: LegacyHistoryTreeParts) -> Self {
HistoryTreeParts {
network_kind: legacy.network_kind,
size: legacy.size,
peaks: legacy
.peaks
.into_iter()
.map(|(index, entry)| {
(
index,
zcash_history::Entry::from_raw_bytes_padded(&entry.inner),
)
})
.collect(),
current_height: legacy.current_height,
}
}
}
impl FromDisk for HistoryTreeParts {
fn from_bytes(bytes: impl AsRef<[u8]>) -> Self {
let bytes = bytes.as_ref();
let options = bincode::DefaultOptions::new();
options
.deserialize::<HistoryTreeParts>(bytes)
.or_else(|_| {
options
.deserialize::<LegacyHistoryTreeParts>(bytes)
.map(HistoryTreeParts::from)
})
.expect("deserialization format should match the serialization format used by IntoDisk")
}
}
impl IntoDisk for BlockInfo {
type Bytes = Vec<u8>;
fn as_bytes(&self) -> Self::Bytes {
self.value_pools()
.as_bytes()
.iter()
.copied()
.chain(self.size().to_le_bytes().iter().copied())
.collect()
}
}
impl FromDisk for BlockInfo {
fn from_bytes(bytes: impl AsRef<[u8]>) -> Self {
match bytes.as_ref().len() {
52.. => {
let value_pools = ValueBalance::<NonNegative>::from_bytes(&bytes.as_ref()[0..48])
.expect("must work for 48 bytes");
let size =
u32::from_le_bytes(bytes.as_ref()[48..52].try_into().expect("must be 4 bytes"));
BlockInfo::new(value_pools, size)
}
44.. => {
let value_pools = ValueBalance::<NonNegative>::from_bytes(&bytes.as_ref()[0..40])
.expect("must work for 40 bytes");
let size =
u32::from_le_bytes(bytes.as_ref()[40..44].try_into().expect("must be 4 bytes"));
BlockInfo::new(value_pools, size)
}
_ => panic!("invalid format"),
}
}
}
#[cfg(test)]
mod tests;