use std::collections::BTreeMap;
use bincode::Options;
use serde_big_array::BigArray;
use zebra_chain::{
amount::{Amount, NonNegative},
block::Height,
block_info::BlockInfo,
history_tree::{HistoryTreeError, NonEmptyHistoryTree},
parameters::{Network, NetworkKind},
primitives::zcash_history,
value_balance::{ValueBalance, SERIALIZED_SIZE},
};
use crate::service::finalized_state::disk_format::{FromDisk, IntoDisk};
impl IntoDisk for ValueBalance<NonNegative> {
type Bytes = [u8; SERIALIZED_SIZE];
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 = [u8; SERIALIZED_SIZE + 4];
fn as_bytes(&self) -> Self::Bytes {
let mut bytes = [0; SERIALIZED_SIZE + 4];
let value_pools = self.value_pools().to_bytes();
bytes[..48].copy_from_slice(&value_pools[..48]);
bytes[48..52].copy_from_slice(&self.size().to_le_bytes());
bytes[52..].copy_from_slice(&value_pools[48..]);
bytes
}
}
impl FromDisk for BlockInfo {
fn from_bytes(bytes: impl AsRef<[u8]>) -> Self {
let bytes = bytes.as_ref();
match bytes.len() {
60.. => {
let mut value_pools = ValueBalance::<NonNegative>::from_bytes(&bytes[..48])
.expect("must work for 48 bytes");
let size = u32::from_le_bytes(bytes[48..52].try_into().expect("must be 4 bytes"));
let nsm = Amount::from_bytes(bytes[52..60].try_into().expect("must be 8 bytes"))
.expect("stored NSM balance must be a valid nonnegative amount");
value_pools.set_nsm_amount(nsm);
BlockInfo::new(value_pools, size)
}
52.. => {
let value_pools = ValueBalance::<NonNegative>::from_bytes(&bytes[0..48])
.expect("must work for 48 bytes");
let size = u32::from_le_bytes(bytes[48..52].try_into().expect("must be 4 bytes"));
BlockInfo::new(value_pools, size)
}
44.. => {
let value_pools = ValueBalance::<NonNegative>::from_bytes(&bytes[0..40])
.expect("must work for 40 bytes");
let size = u32::from_le_bytes(bytes[40..44].try_into().expect("must be 4 bytes"));
BlockInfo::new(value_pools, size)
}
_ => panic!("invalid format"),
}
}
}
#[cfg(test)]
mod tests;