use zebra_chain::{
block::{self, Height},
serialization::{ZcashDeserializeInto, ZcashSerialize},
transaction::{self, Transaction},
};
use crate::service::finalized_state::disk_format::{
expand_zero_be_bytes, truncate_zero_be_bytes, FromDisk, IntoDisk,
};
#[cfg(any(test, feature = "proptest-impl"))]
use proptest_derive::Arbitrary;
#[cfg(any(test, feature = "proptest-impl"))]
use serde::{Deserialize, Serialize};
pub const MAX_ON_DISK_HEIGHT: Height = Height((1 << (HEIGHT_DISK_BYTES * 8)) - 1);
pub const HEIGHT_DISK_BYTES: usize = 3;
pub const TX_INDEX_DISK_BYTES: usize = 2;
pub const TRANSACTION_LOCATION_DISK_BYTES: usize = HEIGHT_DISK_BYTES + TX_INDEX_DISK_BYTES;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[cfg_attr(
any(test, feature = "proptest-impl"),
derive(Arbitrary, Default, Serialize, Deserialize)
)]
pub struct TransactionIndex(pub(super) u16);
impl TransactionIndex {
pub fn from_index(transaction_index: u16) -> TransactionIndex {
TransactionIndex(transaction_index)
}
pub fn index(&self) -> u16 {
self.0
}
pub fn from_usize(transaction_index: usize) -> TransactionIndex {
TransactionIndex(
transaction_index
.try_into()
.expect("the maximum valid index fits in the inner type"),
)
}
pub fn as_usize(&self) -> usize {
self.0.into()
}
pub fn from_u64(transaction_index: u64) -> TransactionIndex {
TransactionIndex(
transaction_index
.try_into()
.expect("the maximum valid index fits in the inner type"),
)
}
#[allow(dead_code)]
pub fn as_u64(&self) -> u64 {
self.0.into()
}
pub const MIN: Self = Self(u16::MIN);
pub const MAX: Self = Self(u16::MAX);
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[cfg_attr(
any(test, feature = "proptest-impl"),
derive(Arbitrary, Default, Serialize, Deserialize)
)]
pub struct TransactionLocation {
pub height: Height,
pub index: TransactionIndex,
}
impl TransactionLocation {
pub fn from_parts(height: Height, index: TransactionIndex) -> TransactionLocation {
TransactionLocation { height, index }
}
pub fn from_index(height: Height, transaction_index: u16) -> TransactionLocation {
TransactionLocation {
height,
index: TransactionIndex::from_index(transaction_index),
}
}
pub fn from_usize(height: Height, transaction_index: usize) -> TransactionLocation {
TransactionLocation {
height,
index: TransactionIndex::from_usize(transaction_index),
}
}
pub fn from_u64(height: Height, transaction_index: u64) -> TransactionLocation {
TransactionLocation {
height,
index: TransactionIndex::from_u64(transaction_index),
}
}
pub const MIN: Self = Self {
height: Height::MIN,
index: TransactionIndex::MIN,
};
pub const MAX: Self = Self {
height: Height::MAX,
index: TransactionIndex::MAX,
};
pub const fn min_for_height(height: Height) -> Self {
Self {
height,
index: TransactionIndex::MIN,
}
}
pub const fn max_for_height(height: Height) -> Self {
Self {
height,
index: TransactionIndex::MAX,
}
}
}
impl IntoDisk for block::Header {
type Bytes = Vec<u8>;
fn as_bytes(&self) -> Self::Bytes {
self.zcash_serialize_to_vec()
.expect("serialization to vec doesn't fail")
}
}
impl FromDisk for block::Header {
fn from_bytes(bytes: impl AsRef<[u8]>) -> Self {
bytes
.as_ref()
.zcash_deserialize_into()
.expect("deserialization format should match the serialization format used by IntoDisk")
}
}
impl IntoDisk for Height {
type Bytes = [u8; HEIGHT_DISK_BYTES];
fn as_bytes(&self) -> Self::Bytes {
let mem_bytes = self.0.to_be_bytes();
let disk_bytes = truncate_zero_be_bytes(&mem_bytes, HEIGHT_DISK_BYTES);
match disk_bytes {
Some(b) => b.try_into().unwrap(),
None => truncate_zero_be_bytes(&MAX_ON_DISK_HEIGHT.0.to_be_bytes(), HEIGHT_DISK_BYTES)
.expect("max on disk height is valid")
.try_into()
.unwrap(),
}
}
}
impl FromDisk for Height {
fn from_bytes(disk_bytes: impl AsRef<[u8]>) -> Self {
const MEM_LEN: usize = size_of::<u32>();
let mem_bytes = expand_zero_be_bytes::<MEM_LEN>(disk_bytes.as_ref());
Height(u32::from_be_bytes(mem_bytes))
}
}
impl IntoDisk for block::Hash {
type Bytes = [u8; 32];
fn as_bytes(&self) -> Self::Bytes {
self.0
}
}
impl FromDisk for block::Hash {
fn from_bytes(bytes: impl AsRef<[u8]>) -> Self {
let array = bytes.as_ref().try_into().unwrap();
Self(array)
}
}
impl IntoDisk for Transaction {
type Bytes = Vec<u8>;
fn as_bytes(&self) -> Self::Bytes {
self.zcash_serialize_to_vec()
.expect("serialization to vec doesn't fail")
}
}
impl FromDisk for Transaction {
fn from_bytes(bytes: impl AsRef<[u8]>) -> Self {
let bytes = bytes.as_ref();
bytes
.as_ref()
.zcash_deserialize_into()
.expect("deserialization format should match the serialization format used by IntoDisk")
}
}
impl IntoDisk for TransactionIndex {
type Bytes = [u8; TX_INDEX_DISK_BYTES];
fn as_bytes(&self) -> Self::Bytes {
self.index().to_be_bytes()
}
}
impl FromDisk for TransactionIndex {
fn from_bytes(disk_bytes: impl AsRef<[u8]>) -> Self {
let disk_bytes = disk_bytes.as_ref().try_into().unwrap();
TransactionIndex::from_index(u16::from_be_bytes(disk_bytes))
}
}
impl IntoDisk for TransactionLocation {
type Bytes = [u8; TRANSACTION_LOCATION_DISK_BYTES];
fn as_bytes(&self) -> Self::Bytes {
let height_bytes = self.height.as_bytes().to_vec();
let index_bytes = self.index.as_bytes().to_vec();
[height_bytes, index_bytes].concat().try_into().unwrap()
}
}
impl FromDisk for Option<TransactionLocation> {
fn from_bytes(disk_bytes: impl AsRef<[u8]>) -> Self {
if disk_bytes.as_ref().len() == TRANSACTION_LOCATION_DISK_BYTES {
Some(TransactionLocation::from_bytes(disk_bytes))
} else {
None
}
}
}
impl FromDisk for TransactionLocation {
fn from_bytes(disk_bytes: impl AsRef<[u8]>) -> Self {
let (height_bytes, index_bytes) = disk_bytes.as_ref().split_at(HEIGHT_DISK_BYTES);
let height = Height::from_bytes(height_bytes);
let index = TransactionIndex::from_bytes(index_bytes);
TransactionLocation { height, index }
}
}
impl IntoDisk for transaction::Hash {
type Bytes = [u8; 32];
fn as_bytes(&self) -> Self::Bytes {
self.0
}
}
impl FromDisk for transaction::Hash {
fn from_bytes(disk_bytes: impl AsRef<[u8]>) -> Self {
transaction::Hash(disk_bytes.as_ref().try_into().unwrap())
}
}