2. refactor(db): split the raw disk serialzation format into modules (#3717)
* doc(db): fix some comments * refactor(db): split disk serialization types into their own module * refactor(db): split the disk format into modules * doc(db/test): explain the RON serialization format
This commit is contained in:
parent
4a5f0c25ce
commit
f1123e0386
|
|
@ -211,6 +211,10 @@ jobs:
|
||||||
/zebra-state/**/constants.rs
|
/zebra-state/**/constants.rs
|
||||||
/zebra-state/**/finalized_state.rs
|
/zebra-state/**/finalized_state.rs
|
||||||
/zebra-state/**/disk_format.rs
|
/zebra-state/**/disk_format.rs
|
||||||
|
/zebra-state/**/disk_format/block.rs
|
||||||
|
/zebra-state/**/disk_format/chain.rs
|
||||||
|
/zebra-state/**/disk_format/shielded.rs
|
||||||
|
/zebra-state/**/disk_format/transparent.rs
|
||||||
/zebra-state/**/disk_db.rs
|
/zebra-state/**/disk_db.rs
|
||||||
/zebra-state/**/zebra_db.rs
|
/zebra-state/**/zebra_db.rs
|
||||||
/zebra-state/**/zebra_db/block.rs
|
/zebra-state/**/zebra_db/block.rs
|
||||||
|
|
|
||||||
|
|
@ -5,65 +5,44 @@
|
||||||
//! The [`crate::constants::DATABASE_FORMAT_VERSION`] constant must
|
//! The [`crate::constants::DATABASE_FORMAT_VERSION`] constant must
|
||||||
//! be incremented each time the database format (column, serialization, etc) changes.
|
//! be incremented each time the database format (column, serialization, etc) changes.
|
||||||
|
|
||||||
use std::{collections::BTreeMap, convert::TryInto, fmt::Debug, sync::Arc};
|
use std::sync::Arc;
|
||||||
|
|
||||||
use bincode::Options;
|
pub mod block;
|
||||||
use serde::{Deserialize, Serialize};
|
pub mod chain;
|
||||||
|
pub mod shielded;
|
||||||
use zebra_chain::{
|
pub mod transparent;
|
||||||
amount::NonNegative,
|
|
||||||
block,
|
|
||||||
block::{Block, Height},
|
|
||||||
history_tree::NonEmptyHistoryTree,
|
|
||||||
orchard,
|
|
||||||
parameters::Network,
|
|
||||||
primitives::zcash_history,
|
|
||||||
sapling,
|
|
||||||
serialization::{ZcashDeserialize, ZcashDeserializeInto, ZcashSerialize},
|
|
||||||
sprout, transaction, transparent,
|
|
||||||
value_balance::ValueBalance,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
/// A transaction's location in the chain, by block height and transaction index.
|
pub use block::TransactionLocation;
|
||||||
///
|
|
||||||
/// This provides a chain-order list of transactions.
|
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
|
|
||||||
pub struct TransactionLocation {
|
|
||||||
/// The block height of the transaction.
|
|
||||||
pub height: block::Height,
|
|
||||||
|
|
||||||
/// The index of the transaction in its block.
|
/// Helper trait for defining the exact format used to interact with disk per
|
||||||
pub index: u32,
|
/// type.
|
||||||
}
|
|
||||||
|
|
||||||
impl TransactionLocation {
|
|
||||||
/// Create a transaction location from a block height and index (as the native index integer type).
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn from_usize(height: Height, index: usize) -> TransactionLocation {
|
|
||||||
TransactionLocation {
|
|
||||||
height,
|
|
||||||
index: index
|
|
||||||
.try_into()
|
|
||||||
.expect("all valid indexes are much lower than u32::MAX"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper trait for defining the exact format used to interact with disk per
|
|
||||||
// type.
|
|
||||||
pub trait IntoDisk {
|
pub trait IntoDisk {
|
||||||
// The type used to compare a value as a key to other keys stored in a
|
/// The type used to compare a value as a key to other keys stored in a
|
||||||
// database
|
/// database.
|
||||||
type Bytes: AsRef<[u8]>;
|
type Bytes: AsRef<[u8]>;
|
||||||
|
|
||||||
// function to convert the current type to its disk format in `zs_get()`
|
/// Converts the current type to its disk format in `zs_get()`,
|
||||||
// without necessarily allocating a new IVec
|
/// without necessarily allocating a new ivec.
|
||||||
fn as_bytes(&self) -> Self::Bytes;
|
fn as_bytes(&self) -> Self::Bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper type for retrieving types from the disk with the correct format.
|
||||||
|
///
|
||||||
|
/// The ivec should be correctly encoded by IntoDisk.
|
||||||
|
pub trait FromDisk: Sized {
|
||||||
|
/// Function to convert the disk bytes back into the deserialized type.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// - if the input data doesn't deserialize correctly
|
||||||
|
fn from_bytes(bytes: impl AsRef<[u8]>) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generic trait impls
|
||||||
|
|
||||||
impl<'a, T> IntoDisk for &'a T
|
impl<'a, T> IntoDisk for &'a T
|
||||||
where
|
where
|
||||||
T: IntoDisk,
|
T: IntoDisk,
|
||||||
|
|
@ -86,18 +65,6 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper type for retrieving types from the disk with the correct format.
|
|
||||||
///
|
|
||||||
/// The ivec should be correctly encoded by IntoDisk.
|
|
||||||
pub trait FromDisk: Sized {
|
|
||||||
/// Function to convert the disk bytes back into the deserialized type.
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// - if the input data doesn't deserialize correctly
|
|
||||||
fn from_bytes(bytes: impl AsRef<[u8]>) -> Self;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> FromDisk for Arc<T>
|
impl<T> FromDisk for Arc<T>
|
||||||
where
|
where
|
||||||
T: FromDisk,
|
T: FromDisk,
|
||||||
|
|
@ -107,106 +74,6 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoDisk for Block {
|
|
||||||
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 {
|
|
||||||
fn from_bytes(bytes: impl AsRef<[u8]>) -> Self {
|
|
||||||
Block::zcash_deserialize(bytes.as_ref())
|
|
||||||
.expect("deserialization format should match the serialization format used by IntoDisk")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoDisk for TransactionLocation {
|
|
||||||
type Bytes = [u8; 8];
|
|
||||||
|
|
||||||
fn as_bytes(&self) -> Self::Bytes {
|
|
||||||
let height_bytes = self.height.0.to_be_bytes();
|
|
||||||
let index_bytes = self.index.to_be_bytes();
|
|
||||||
|
|
||||||
let mut bytes = [0; 8];
|
|
||||||
|
|
||||||
bytes[0..4].copy_from_slice(&height_bytes);
|
|
||||||
bytes[4..8].copy_from_slice(&index_bytes);
|
|
||||||
|
|
||||||
bytes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromDisk for TransactionLocation {
|
|
||||||
fn from_bytes(disk_bytes: impl AsRef<[u8]>) -> Self {
|
|
||||||
let disk_bytes = disk_bytes.as_ref();
|
|
||||||
let height = {
|
|
||||||
let mut bytes = [0; 4];
|
|
||||||
bytes.copy_from_slice(&disk_bytes[0..4]);
|
|
||||||
let height = u32::from_be_bytes(bytes);
|
|
||||||
block::Height(height)
|
|
||||||
};
|
|
||||||
|
|
||||||
let index = {
|
|
||||||
let mut bytes = [0; 4];
|
|
||||||
bytes.copy_from_slice(&disk_bytes[4..8]);
|
|
||||||
u32::from_be_bytes(bytes)
|
|
||||||
};
|
|
||||||
|
|
||||||
TransactionLocation { height, index }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoDisk for transaction::Hash {
|
|
||||||
type Bytes = [u8; 32];
|
|
||||||
|
|
||||||
fn as_bytes(&self) -> Self::Bytes {
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 sprout::Nullifier {
|
|
||||||
type Bytes = [u8; 32];
|
|
||||||
|
|
||||||
fn as_bytes(&self) -> Self::Bytes {
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoDisk for sapling::Nullifier {
|
|
||||||
type Bytes = [u8; 32];
|
|
||||||
|
|
||||||
fn as_bytes(&self) -> Self::Bytes {
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoDisk for orchard::Nullifier {
|
|
||||||
type Bytes = [u8; 32];
|
|
||||||
|
|
||||||
fn as_bytes(&self) -> Self::Bytes {
|
|
||||||
let nullifier: orchard::Nullifier = *self;
|
|
||||||
nullifier.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoDisk for () {
|
impl IntoDisk for () {
|
||||||
type Bytes = [u8; 0];
|
type Bytes = [u8; 0];
|
||||||
|
|
||||||
|
|
@ -214,197 +81,3 @@ impl IntoDisk for () {
|
||||||
[]
|
[]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoDisk for block::Height {
|
|
||||||
type Bytes = [u8; 4];
|
|
||||||
|
|
||||||
fn as_bytes(&self) -> Self::Bytes {
|
|
||||||
self.0.to_be_bytes()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromDisk for block::Height {
|
|
||||||
fn from_bytes(bytes: impl AsRef<[u8]>) -> Self {
|
|
||||||
let array = bytes.as_ref().try_into().unwrap();
|
|
||||||
block::Height(u32::from_be_bytes(array))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoDisk for transparent::Utxo {
|
|
||||||
type Bytes = Vec<u8>;
|
|
||||||
|
|
||||||
fn as_bytes(&self) -> Self::Bytes {
|
|
||||||
let mut bytes = vec![0; 5];
|
|
||||||
bytes[0..4].copy_from_slice(&self.height.0.to_be_bytes());
|
|
||||||
bytes[4] = self.from_coinbase as u8;
|
|
||||||
self.output
|
|
||||||
.zcash_serialize(&mut bytes)
|
|
||||||
.expect("serialization to vec doesn't fail");
|
|
||||||
bytes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromDisk for transparent::Utxo {
|
|
||||||
fn from_bytes(bytes: impl AsRef<[u8]>) -> Self {
|
|
||||||
let (meta_bytes, output_bytes) = bytes.as_ref().split_at(5);
|
|
||||||
let height = block::Height(u32::from_be_bytes(meta_bytes[0..4].try_into().unwrap()));
|
|
||||||
let from_coinbase = meta_bytes[4] == 1u8;
|
|
||||||
let output = output_bytes
|
|
||||||
.zcash_deserialize_into()
|
|
||||||
.expect("db has serialized data");
|
|
||||||
Self {
|
|
||||||
output,
|
|
||||||
height,
|
|
||||||
from_coinbase,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoDisk for transparent::OutPoint {
|
|
||||||
type Bytes = Vec<u8>;
|
|
||||||
|
|
||||||
fn as_bytes(&self) -> Self::Bytes {
|
|
||||||
self.zcash_serialize_to_vec()
|
|
||||||
.expect("serialization to vec doesn't fail")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoDisk for sprout::tree::Root {
|
|
||||||
type Bytes = [u8; 32];
|
|
||||||
|
|
||||||
fn as_bytes(&self) -> Self::Bytes {
|
|
||||||
self.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoDisk for sapling::tree::Root {
|
|
||||||
type Bytes = [u8; 32];
|
|
||||||
|
|
||||||
fn as_bytes(&self) -> Self::Bytes {
|
|
||||||
self.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoDisk for orchard::tree::Root {
|
|
||||||
type Bytes = [u8; 32];
|
|
||||||
|
|
||||||
fn as_bytes(&self) -> Self::Bytes {
|
|
||||||
self.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoDisk for ValueBalance<NonNegative> {
|
|
||||||
type Bytes = [u8; 32];
|
|
||||||
|
|
||||||
fn as_bytes(&self) -> Self::Bytes {
|
|
||||||
self.to_bytes()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromDisk for ValueBalance<NonNegative> {
|
|
||||||
fn from_bytes(bytes: impl AsRef<[u8]>) -> Self {
|
|
||||||
let array = bytes.as_ref().try_into().unwrap();
|
|
||||||
ValueBalance::from_bytes(array).unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The following implementations for the note commitment trees use `serde` and
|
|
||||||
// `bincode` because currently the inner Merkle tree frontier (from
|
|
||||||
// `incrementalmerkletree`) only supports `serde` for serialization. `bincode`
|
|
||||||
// was chosen because it is small and fast. We explicitly use `DefaultOptions`
|
|
||||||
// in particular to disallow trailing bytes; see
|
|
||||||
// https://docs.rs/bincode/1.3.3/bincode/config/index.html#options-struct-vs-bincode-functions
|
|
||||||
|
|
||||||
impl IntoDisk for sprout::tree::NoteCommitmentTree {
|
|
||||||
type Bytes = Vec<u8>;
|
|
||||||
|
|
||||||
fn as_bytes(&self) -> Self::Bytes {
|
|
||||||
bincode::DefaultOptions::new()
|
|
||||||
.serialize(self)
|
|
||||||
.expect("serialization to vec doesn't fail")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromDisk for sprout::tree::NoteCommitmentTree {
|
|
||||||
fn from_bytes(bytes: impl AsRef<[u8]>) -> Self {
|
|
||||||
bincode::DefaultOptions::new()
|
|
||||||
.deserialize(bytes.as_ref())
|
|
||||||
.expect("deserialization format should match the serialization format used by IntoDisk")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl IntoDisk for sapling::tree::NoteCommitmentTree {
|
|
||||||
type Bytes = Vec<u8>;
|
|
||||||
|
|
||||||
fn as_bytes(&self) -> Self::Bytes {
|
|
||||||
bincode::DefaultOptions::new()
|
|
||||||
.serialize(self)
|
|
||||||
.expect("serialization to vec doesn't fail")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromDisk for sapling::tree::NoteCommitmentTree {
|
|
||||||
fn from_bytes(bytes: impl AsRef<[u8]>) -> Self {
|
|
||||||
bincode::DefaultOptions::new()
|
|
||||||
.deserialize(bytes.as_ref())
|
|
||||||
.expect("deserialization format should match the serialization format used by IntoDisk")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoDisk for orchard::tree::NoteCommitmentTree {
|
|
||||||
type Bytes = Vec<u8>;
|
|
||||||
|
|
||||||
fn as_bytes(&self) -> Self::Bytes {
|
|
||||||
bincode::DefaultOptions::new()
|
|
||||||
.serialize(self)
|
|
||||||
.expect("serialization to vec doesn't fail")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromDisk for orchard::tree::NoteCommitmentTree {
|
|
||||||
fn from_bytes(bytes: impl AsRef<[u8]>) -> Self {
|
|
||||||
bincode::DefaultOptions::new()
|
|
||||||
.deserialize(bytes.as_ref())
|
|
||||||
.expect("deserialization format should match the serialization format used by IntoDisk")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(serde::Serialize, serde::Deserialize)]
|
|
||||||
struct HistoryTreeParts {
|
|
||||||
network: Network,
|
|
||||||
size: u32,
|
|
||||||
peaks: BTreeMap<u32, zcash_history::Entry>,
|
|
||||||
current_height: Height,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoDisk for NonEmptyHistoryTree {
|
|
||||||
type Bytes = Vec<u8>;
|
|
||||||
|
|
||||||
fn as_bytes(&self) -> Self::Bytes {
|
|
||||||
let data = HistoryTreeParts {
|
|
||||||
network: self.network(),
|
|
||||||
size: self.size(),
|
|
||||||
peaks: self.peaks().clone(),
|
|
||||||
current_height: self.current_height(),
|
|
||||||
};
|
|
||||||
bincode::DefaultOptions::new()
|
|
||||||
.serialize(&data)
|
|
||||||
.expect("serialization to vec doesn't fail")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromDisk for NonEmptyHistoryTree {
|
|
||||||
fn from_bytes(bytes: impl AsRef<[u8]>) -> Self {
|
|
||||||
let parts: HistoryTreeParts = bincode::DefaultOptions::new()
|
|
||||||
.deserialize(bytes.as_ref())
|
|
||||||
.expect(
|
|
||||||
"deserialization format should match the serialization format used by IntoDisk",
|
|
||||||
);
|
|
||||||
NonEmptyHistoryTree::from_cache(
|
|
||||||
parts.network,
|
|
||||||
parts.size,
|
|
||||||
parts.peaks,
|
|
||||||
parts.current_height,
|
|
||||||
)
|
|
||||||
.expect("deserialization format should match the serialization format used by IntoDisk")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,137 @@
|
||||||
|
//! Block and transaction serialization formats for finalized data.
|
||||||
|
//!
|
||||||
|
//! # Correctness
|
||||||
|
//!
|
||||||
|
//! The [`crate::constants::DATABASE_FORMAT_VERSION`] constant must
|
||||||
|
//! be incremented each time the database format (column, serialization, etc) changes.
|
||||||
|
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use zebra_chain::{
|
||||||
|
block::{self, Block, Height},
|
||||||
|
serialization::{ZcashDeserialize, ZcashSerialize},
|
||||||
|
transaction,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::service::finalized_state::disk_format::{FromDisk, IntoDisk};
|
||||||
|
|
||||||
|
/// A transaction's location in the chain, by block height and transaction index.
|
||||||
|
///
|
||||||
|
/// This provides a chain-order list of transactions.
|
||||||
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
|
||||||
|
pub struct TransactionLocation {
|
||||||
|
/// The block height of the transaction.
|
||||||
|
pub height: Height,
|
||||||
|
|
||||||
|
/// The index of the transaction in its block.
|
||||||
|
pub index: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TransactionLocation {
|
||||||
|
/// Create a transaction location from a block height and index (as the native index integer type).
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn from_usize(height: Height, index: usize) -> TransactionLocation {
|
||||||
|
TransactionLocation {
|
||||||
|
height,
|
||||||
|
index: index
|
||||||
|
.try_into()
|
||||||
|
.expect("all valid indexes are much lower than u32::MAX"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block trait impls
|
||||||
|
|
||||||
|
impl IntoDisk for Block {
|
||||||
|
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 {
|
||||||
|
fn from_bytes(bytes: impl AsRef<[u8]>) -> Self {
|
||||||
|
Block::zcash_deserialize(bytes.as_ref())
|
||||||
|
.expect("deserialization format should match the serialization format used by IntoDisk")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoDisk for Height {
|
||||||
|
type Bytes = [u8; 4];
|
||||||
|
|
||||||
|
fn as_bytes(&self) -> Self::Bytes {
|
||||||
|
self.0.to_be_bytes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromDisk for Height {
|
||||||
|
fn from_bytes(bytes: impl AsRef<[u8]>) -> Self {
|
||||||
|
let array = bytes.as_ref().try_into().unwrap();
|
||||||
|
Height(u32::from_be_bytes(array))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transaction trait impls
|
||||||
|
|
||||||
|
impl IntoDisk for TransactionLocation {
|
||||||
|
type Bytes = [u8; 8];
|
||||||
|
|
||||||
|
fn as_bytes(&self) -> Self::Bytes {
|
||||||
|
let height_bytes = self.height.as_bytes();
|
||||||
|
let index_bytes = self.index.to_be_bytes();
|
||||||
|
|
||||||
|
let mut bytes = [0; 8];
|
||||||
|
|
||||||
|
bytes[0..4].copy_from_slice(&height_bytes);
|
||||||
|
bytes[4..8].copy_from_slice(&index_bytes);
|
||||||
|
|
||||||
|
bytes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromDisk for TransactionLocation {
|
||||||
|
fn from_bytes(disk_bytes: impl AsRef<[u8]>) -> Self {
|
||||||
|
let disk_bytes = disk_bytes.as_ref();
|
||||||
|
let height = {
|
||||||
|
let mut bytes = [0; 4];
|
||||||
|
bytes.copy_from_slice(&disk_bytes[0..4]);
|
||||||
|
let height = u32::from_be_bytes(bytes);
|
||||||
|
Height(height)
|
||||||
|
};
|
||||||
|
|
||||||
|
let index = {
|
||||||
|
let mut bytes = [0; 4];
|
||||||
|
bytes.copy_from_slice(&disk_bytes[4..8]);
|
||||||
|
u32::from_be_bytes(bytes)
|
||||||
|
};
|
||||||
|
|
||||||
|
TransactionLocation { height, index }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoDisk for transaction::Hash {
|
||||||
|
type Bytes = [u8; 32];
|
||||||
|
|
||||||
|
fn as_bytes(&self) -> Self::Bytes {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
//! Chain data serialization formats for finalized data.
|
||||||
|
//!
|
||||||
|
//! # Correctness
|
||||||
|
//!
|
||||||
|
//! The [`crate::constants::DATABASE_FORMAT_VERSION`] constant must
|
||||||
|
//! be incremented each time the database format (column, serialization, etc) changes.
|
||||||
|
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
use bincode::Options;
|
||||||
|
|
||||||
|
use zebra_chain::{
|
||||||
|
amount::NonNegative, block::Height, history_tree::NonEmptyHistoryTree, parameters::Network,
|
||||||
|
primitives::zcash_history, value_balance::ValueBalance,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::service::finalized_state::disk_format::{FromDisk, IntoDisk};
|
||||||
|
|
||||||
|
impl IntoDisk for ValueBalance<NonNegative> {
|
||||||
|
type Bytes = [u8; 32];
|
||||||
|
|
||||||
|
fn as_bytes(&self) -> Self::Bytes {
|
||||||
|
self.to_bytes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromDisk for ValueBalance<NonNegative> {
|
||||||
|
fn from_bytes(bytes: impl AsRef<[u8]>) -> Self {
|
||||||
|
let array = bytes.as_ref().try_into().unwrap();
|
||||||
|
ValueBalance::from_bytes(array).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize)]
|
||||||
|
struct HistoryTreeParts {
|
||||||
|
network: Network,
|
||||||
|
size: u32,
|
||||||
|
peaks: BTreeMap<u32, zcash_history::Entry>,
|
||||||
|
current_height: Height,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoDisk for NonEmptyHistoryTree {
|
||||||
|
type Bytes = Vec<u8>;
|
||||||
|
|
||||||
|
fn as_bytes(&self) -> Self::Bytes {
|
||||||
|
let data = HistoryTreeParts {
|
||||||
|
network: self.network(),
|
||||||
|
size: self.size(),
|
||||||
|
peaks: self.peaks().clone(),
|
||||||
|
current_height: self.current_height(),
|
||||||
|
};
|
||||||
|
bincode::DefaultOptions::new()
|
||||||
|
.serialize(&data)
|
||||||
|
.expect("serialization to vec doesn't fail")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromDisk for NonEmptyHistoryTree {
|
||||||
|
fn from_bytes(bytes: impl AsRef<[u8]>) -> Self {
|
||||||
|
let parts: HistoryTreeParts = bincode::DefaultOptions::new()
|
||||||
|
.deserialize(bytes.as_ref())
|
||||||
|
.expect(
|
||||||
|
"deserialization format should match the serialization format used by IntoDisk",
|
||||||
|
);
|
||||||
|
NonEmptyHistoryTree::from_cache(
|
||||||
|
parts.network,
|
||||||
|
parts.size,
|
||||||
|
parts.peaks,
|
||||||
|
parts.current_height,
|
||||||
|
)
|
||||||
|
.expect("deserialization format should match the serialization format used by IntoDisk")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,121 @@
|
||||||
|
//! Shielded transfer serialization formats for finalized data.
|
||||||
|
//!
|
||||||
|
//! # Correctness
|
||||||
|
//!
|
||||||
|
//! The [`crate::constants::DATABASE_FORMAT_VERSION`] constant must
|
||||||
|
//! be incremented each time the database format (column, serialization, etc) changes.
|
||||||
|
|
||||||
|
use bincode::Options;
|
||||||
|
|
||||||
|
use zebra_chain::{orchard, sapling, sprout};
|
||||||
|
|
||||||
|
use crate::service::finalized_state::disk_format::{FromDisk, IntoDisk};
|
||||||
|
|
||||||
|
impl IntoDisk for sprout::Nullifier {
|
||||||
|
type Bytes = [u8; 32];
|
||||||
|
|
||||||
|
fn as_bytes(&self) -> Self::Bytes {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoDisk for sapling::Nullifier {
|
||||||
|
type Bytes = [u8; 32];
|
||||||
|
|
||||||
|
fn as_bytes(&self) -> Self::Bytes {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoDisk for orchard::Nullifier {
|
||||||
|
type Bytes = [u8; 32];
|
||||||
|
|
||||||
|
fn as_bytes(&self) -> Self::Bytes {
|
||||||
|
let nullifier: orchard::Nullifier = *self;
|
||||||
|
nullifier.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoDisk for sprout::tree::Root {
|
||||||
|
type Bytes = [u8; 32];
|
||||||
|
|
||||||
|
fn as_bytes(&self) -> Self::Bytes {
|
||||||
|
self.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoDisk for sapling::tree::Root {
|
||||||
|
type Bytes = [u8; 32];
|
||||||
|
|
||||||
|
fn as_bytes(&self) -> Self::Bytes {
|
||||||
|
self.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoDisk for orchard::tree::Root {
|
||||||
|
type Bytes = [u8; 32];
|
||||||
|
|
||||||
|
fn as_bytes(&self) -> Self::Bytes {
|
||||||
|
self.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The following implementations for the note commitment trees use `serde` and
|
||||||
|
// `bincode` because currently the inner Merkle tree frontier (from
|
||||||
|
// `incrementalmerkletree`) only supports `serde` for serialization. `bincode`
|
||||||
|
// was chosen because it is small and fast. We explicitly use `DefaultOptions`
|
||||||
|
// in particular to disallow trailing bytes; see
|
||||||
|
// https://docs.rs/bincode/1.3.3/bincode/config/index.html#options-struct-vs-bincode-functions
|
||||||
|
|
||||||
|
impl IntoDisk for sprout::tree::NoteCommitmentTree {
|
||||||
|
type Bytes = Vec<u8>;
|
||||||
|
|
||||||
|
fn as_bytes(&self) -> Self::Bytes {
|
||||||
|
bincode::DefaultOptions::new()
|
||||||
|
.serialize(self)
|
||||||
|
.expect("serialization to vec doesn't fail")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromDisk for sprout::tree::NoteCommitmentTree {
|
||||||
|
fn from_bytes(bytes: impl AsRef<[u8]>) -> Self {
|
||||||
|
bincode::DefaultOptions::new()
|
||||||
|
.deserialize(bytes.as_ref())
|
||||||
|
.expect("deserialization format should match the serialization format used by IntoDisk")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl IntoDisk for sapling::tree::NoteCommitmentTree {
|
||||||
|
type Bytes = Vec<u8>;
|
||||||
|
|
||||||
|
fn as_bytes(&self) -> Self::Bytes {
|
||||||
|
bincode::DefaultOptions::new()
|
||||||
|
.serialize(self)
|
||||||
|
.expect("serialization to vec doesn't fail")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromDisk for sapling::tree::NoteCommitmentTree {
|
||||||
|
fn from_bytes(bytes: impl AsRef<[u8]>) -> Self {
|
||||||
|
bincode::DefaultOptions::new()
|
||||||
|
.deserialize(bytes.as_ref())
|
||||||
|
.expect("deserialization format should match the serialization format used by IntoDisk")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoDisk for orchard::tree::NoteCommitmentTree {
|
||||||
|
type Bytes = Vec<u8>;
|
||||||
|
|
||||||
|
fn as_bytes(&self) -> Self::Bytes {
|
||||||
|
bincode::DefaultOptions::new()
|
||||||
|
.serialize(self)
|
||||||
|
.expect("serialization to vec doesn't fail")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromDisk for orchard::tree::NoteCommitmentTree {
|
||||||
|
fn from_bytes(bytes: impl AsRef<[u8]>) -> Self {
|
||||||
|
bincode::DefaultOptions::new()
|
||||||
|
.deserialize(bytes.as_ref())
|
||||||
|
.expect("deserialization format should match the serialization format used by IntoDisk")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,6 +12,14 @@
|
||||||
//! If this test fails, run `cargo insta review` to update the test snapshots,
|
//! If this test fails, run `cargo insta review` to update the test snapshots,
|
||||||
//! then commit the `test_*.snap` files using git.
|
//! then commit the `test_*.snap` files using git.
|
||||||
//!
|
//!
|
||||||
|
//! # Snapshot Format
|
||||||
|
//!
|
||||||
|
//! These snapshots use [RON (Rusty Object Notation)](https://github.com/ron-rs/ron#readme),
|
||||||
|
//! a text format similar to Rust syntax. Raw byte data is encoded in hexadecimal.
|
||||||
|
//!
|
||||||
|
//! Due to `serde` limitations, some object types can't be represented exactly,
|
||||||
|
//! so RON uses the closest equivalent structure.
|
||||||
|
//!
|
||||||
//! # TODO
|
//! # TODO
|
||||||
//!
|
//!
|
||||||
//! Test shielded data, and data activated in Overwinter and later network upgrades.
|
//! Test shielded data, and data activated in Overwinter and later network upgrades.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
//! Transparent transfer serialization formats for finalized data.
|
||||||
|
//!
|
||||||
|
//! # Correctness
|
||||||
|
//!
|
||||||
|
//! The [`crate::constants::DATABASE_FORMAT_VERSION`] constant must
|
||||||
|
//! be incremented each time the database format (column, serialization, etc) changes.
|
||||||
|
|
||||||
|
use zebra_chain::{
|
||||||
|
block::Height,
|
||||||
|
serialization::{ZcashDeserializeInto, ZcashSerialize},
|
||||||
|
transparent,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::service::finalized_state::disk_format::{FromDisk, IntoDisk};
|
||||||
|
|
||||||
|
impl IntoDisk for transparent::Utxo {
|
||||||
|
type Bytes = Vec<u8>;
|
||||||
|
|
||||||
|
fn as_bytes(&self) -> Self::Bytes {
|
||||||
|
let mut bytes = vec![0; 5];
|
||||||
|
bytes[0..4].copy_from_slice(&self.height.0.to_be_bytes());
|
||||||
|
bytes[4] = self.from_coinbase as u8;
|
||||||
|
self.output
|
||||||
|
.zcash_serialize(&mut bytes)
|
||||||
|
.expect("serialization to vec doesn't fail");
|
||||||
|
bytes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromDisk for transparent::Utxo {
|
||||||
|
fn from_bytes(bytes: impl AsRef<[u8]>) -> Self {
|
||||||
|
let (meta_bytes, output_bytes) = bytes.as_ref().split_at(5);
|
||||||
|
let height = Height(u32::from_be_bytes(meta_bytes[0..4].try_into().unwrap()));
|
||||||
|
let from_coinbase = meta_bytes[4] == 1u8;
|
||||||
|
let output = output_bytes
|
||||||
|
.zcash_deserialize_into()
|
||||||
|
.expect("db has serialized data");
|
||||||
|
Self {
|
||||||
|
output,
|
||||||
|
height,
|
||||||
|
from_coinbase,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoDisk for transparent::OutPoint {
|
||||||
|
type Bytes = Vec<u8>;
|
||||||
|
|
||||||
|
fn as_bytes(&self) -> Self::Bytes {
|
||||||
|
self.zcash_serialize_to_vec()
|
||||||
|
.expect("serialization to vec doesn't fail")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,6 +14,14 @@
|
||||||
//!
|
//!
|
||||||
//! These tests use fixed test vectors, based on the results of other database queries.
|
//! These tests use fixed test vectors, based on the results of other database queries.
|
||||||
//!
|
//!
|
||||||
|
//! # Snapshot Format
|
||||||
|
//!
|
||||||
|
//! These snapshots use [RON (Rusty Object Notation)](https://github.com/ron-rs/ron#readme),
|
||||||
|
//! a text format similar to Rust syntax. Raw byte data is encoded in hexadecimal.
|
||||||
|
//!
|
||||||
|
//! Due to `serde` limitations, some object types can't be represented exactly,
|
||||||
|
//! so RON uses the closest equivalent structure.
|
||||||
|
//!
|
||||||
//! # Fixing Test Failures
|
//! # Fixing Test Failures
|
||||||
//!
|
//!
|
||||||
//! If this test fails, run `cargo insta review` to update the test snapshots,
|
//! If this test fails, run `cargo insta review` to update the test snapshots,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue