Add Chain ValueBalance serialization (#2554)
* add serialization for value balances * change test names * change panic messages * add a deserialization test * return the errors from `from_bytes()` methods * add prop test for serialize/deserialize Amount * Move amount proptests to amount::tests::prop Co-authored-by: teor <teor@riseup.net>
This commit is contained in:
parent
4c4dbfe7cd
commit
910f0ff5dc
|
@ -16,6 +16,12 @@ use std::{
|
||||||
use crate::serialization::{ZcashDeserialize, ZcashSerialize};
|
use crate::serialization::{ZcashDeserialize, ZcashSerialize};
|
||||||
use byteorder::{ByteOrder, LittleEndian, ReadBytesExt, WriteBytesExt};
|
use byteorder::{ByteOrder, LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||||
|
|
||||||
|
#[cfg(any(test, feature = "proptest-impl"))]
|
||||||
|
pub mod arbitrary;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
type Result<T, E = Error> = std::result::Result<T, E>;
|
type Result<T, E = Error> = std::result::Result<T, E>;
|
||||||
|
|
||||||
/// A runtime validated type for representing amounts of zatoshis
|
/// A runtime validated type for representing amounts of zatoshis
|
||||||
|
@ -48,6 +54,15 @@ impl<C> Amount<C> {
|
||||||
buf
|
buf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// From little endian byte array
|
||||||
|
pub fn from_bytes(bytes: [u8; 8]) -> Result<Amount<C>>
|
||||||
|
where
|
||||||
|
C: Constraint,
|
||||||
|
{
|
||||||
|
let amount = i64::from_le_bytes(bytes);
|
||||||
|
amount.try_into()
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a zero `Amount`
|
/// Create a zero `Amount`
|
||||||
pub fn zero() -> Amount<C>
|
pub fn zero() -> Amount<C>
|
||||||
where
|
where
|
||||||
|
@ -452,22 +467,7 @@ impl ZcashDeserialize for Amount<NonNegative> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(test, feature = "proptest-impl"))]
|
// TODO: move to tests::vectors after PR #2577 merges
|
||||||
use proptest::prelude::*;
|
|
||||||
#[cfg(any(test, feature = "proptest-impl"))]
|
|
||||||
impl<C> Arbitrary for Amount<C>
|
|
||||||
where
|
|
||||||
C: Constraint + std::fmt::Debug,
|
|
||||||
{
|
|
||||||
type Parameters = ();
|
|
||||||
|
|
||||||
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
|
|
||||||
C::valid_range().prop_map(|v| Self(v, PhantomData)).boxed()
|
|
||||||
}
|
|
||||||
|
|
||||||
type Strategy = BoxedStrategy<Self>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::serialization::ZcashDeserializeInto;
|
use crate::serialization::ZcashDeserializeInto;
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
//! Randomised test case generation for amounts.
|
||||||
|
|
||||||
|
use proptest::prelude::*;
|
||||||
|
|
||||||
|
use crate::amount::*;
|
||||||
|
|
||||||
|
impl<C> Arbitrary for Amount<C>
|
||||||
|
where
|
||||||
|
C: Constraint + std::fmt::Debug,
|
||||||
|
{
|
||||||
|
type Parameters = ();
|
||||||
|
|
||||||
|
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
|
||||||
|
C::valid_range().prop_map(|v| Self(v, PhantomData)).boxed()
|
||||||
|
}
|
||||||
|
|
||||||
|
type Strategy = BoxedStrategy<Self>;
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
//! Tests for amounts
|
||||||
|
|
||||||
|
mod prop;
|
|
@ -0,0 +1,27 @@
|
||||||
|
//! Randomised property tests for amounts.
|
||||||
|
|
||||||
|
use proptest::prelude::*;
|
||||||
|
|
||||||
|
use crate::amount::*;
|
||||||
|
|
||||||
|
proptest! {
|
||||||
|
#[test]
|
||||||
|
fn amount_serialization(amount in any::<Amount<NegativeAllowed>>()) {
|
||||||
|
zebra_test::init();
|
||||||
|
|
||||||
|
let bytes = amount.to_bytes();
|
||||||
|
let serialized_amount = Amount::<NegativeAllowed>::from_bytes(bytes)?;
|
||||||
|
|
||||||
|
prop_assert_eq!(amount, serialized_amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn amount_deserialization(bytes in any::<[u8; 8]>()) {
|
||||||
|
zebra_test::init();
|
||||||
|
|
||||||
|
if let Ok(deserialized) = Amount::<NegativeAllowed>::from_bytes(bytes) {
|
||||||
|
let bytes2 = deserialized.to_bytes();
|
||||||
|
prop_assert_eq!(bytes, bytes2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
use crate::amount::{Amount, Constraint, Error, NonNegative};
|
use crate::amount::{Amount, Constraint, Error, NonNegative};
|
||||||
|
|
||||||
|
use std::convert::TryInto;
|
||||||
|
|
||||||
#[cfg(any(test, feature = "proptest-impl"))]
|
#[cfg(any(test, feature = "proptest-impl"))]
|
||||||
mod arbitrary;
|
mod arbitrary;
|
||||||
|
|
||||||
|
@ -128,6 +130,51 @@ where
|
||||||
orchard: zero,
|
orchard: zero,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// To byte array
|
||||||
|
pub fn to_bytes(self) -> [u8; 32] {
|
||||||
|
let transparent = self.transparent.to_bytes();
|
||||||
|
let sprout = self.sprout.to_bytes();
|
||||||
|
let sapling = self.sapling.to_bytes();
|
||||||
|
let orchard = self.orchard.to_bytes();
|
||||||
|
match [transparent, sprout, sapling, orchard].concat().try_into() {
|
||||||
|
Ok(bytes) => bytes,
|
||||||
|
_ => unreachable!(
|
||||||
|
"Four [u8; 8] should always concat with no error into a single [u8; 32]"
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// From byte array
|
||||||
|
pub fn from_bytes(bytes: [u8; 32]) -> Result<ValueBalance<C>, ValueBalanceError> {
|
||||||
|
let transparent = Amount::from_bytes(
|
||||||
|
bytes[0..8]
|
||||||
|
.try_into()
|
||||||
|
.expect("Extracting the first quarter of a [u8; 32] should always succeed"),
|
||||||
|
)?;
|
||||||
|
let sprout = Amount::from_bytes(
|
||||||
|
bytes[8..16]
|
||||||
|
.try_into()
|
||||||
|
.expect("Extracting the second quarter of a [u8; 32] should always succeed"),
|
||||||
|
)?;
|
||||||
|
let sapling = Amount::from_bytes(
|
||||||
|
bytes[16..24]
|
||||||
|
.try_into()
|
||||||
|
.expect("Extracting the third quarter of a [u8; 32] should always succeed"),
|
||||||
|
)?;
|
||||||
|
let orchard = Amount::from_bytes(
|
||||||
|
bytes[24..32]
|
||||||
|
.try_into()
|
||||||
|
.expect("Extracting the last quarter of a [u8; 32] should always succeed"),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(ValueBalance {
|
||||||
|
transparent,
|
||||||
|
sprout,
|
||||||
|
sapling,
|
||||||
|
orchard,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug, Clone, PartialEq)]
|
#[derive(thiserror::Error, Debug, Clone, PartialEq)]
|
||||||
|
|
|
@ -1 +1,3 @@
|
||||||
|
//! Tests for value balances.
|
||||||
|
|
||||||
mod prop;
|
mod prop;
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
use crate::{amount::*, value_balance::*};
|
//! Randomised property tests for value balances.
|
||||||
|
|
||||||
use proptest::prelude::*;
|
use proptest::prelude::*;
|
||||||
|
|
||||||
|
use crate::{amount::*, value_balance::*};
|
||||||
|
|
||||||
proptest! {
|
proptest! {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_add(
|
fn value_blance_add(
|
||||||
value_balance1 in any::<ValueBalance<NegativeAllowed>>(),
|
value_balance1 in any::<ValueBalance<NegativeAllowed>>(),
|
||||||
value_balance2 in any::<ValueBalance<NegativeAllowed>>())
|
value_balance2 in any::<ValueBalance<NegativeAllowed>>())
|
||||||
{
|
{
|
||||||
|
@ -32,7 +35,7 @@ proptest! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn test_sub(
|
fn value_balance_sub(
|
||||||
value_balance1 in any::<ValueBalance<NegativeAllowed>>(),
|
value_balance1 in any::<ValueBalance<NegativeAllowed>>(),
|
||||||
value_balance2 in any::<ValueBalance<NegativeAllowed>>())
|
value_balance2 in any::<ValueBalance<NegativeAllowed>>())
|
||||||
{
|
{
|
||||||
|
@ -62,7 +65,7 @@ proptest! {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_sum(
|
fn value_balance_sum(
|
||||||
value_balance1 in any::<ValueBalance<NegativeAllowed>>(),
|
value_balance1 in any::<ValueBalance<NegativeAllowed>>(),
|
||||||
value_balance2 in any::<ValueBalance<NegativeAllowed>>(),
|
value_balance2 in any::<ValueBalance<NegativeAllowed>>(),
|
||||||
) {
|
) {
|
||||||
|
@ -88,4 +91,24 @@ proptest! {
|
||||||
_ => prop_assert!(matches!(collection.iter().sum(), Err(ValueBalanceError::AmountError(_))))
|
_ => prop_assert!(matches!(collection.iter().sum(), Err(ValueBalanceError::AmountError(_))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn value_balance_serialization(value_balance in any::<ValueBalance<NegativeAllowed>>()) {
|
||||||
|
zebra_test::init();
|
||||||
|
|
||||||
|
let bytes = value_balance.to_bytes();
|
||||||
|
let serialized_value_balance = ValueBalance::from_bytes(bytes)?;
|
||||||
|
|
||||||
|
prop_assert_eq!(value_balance, serialized_value_balance);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn value_balance_deserialization(bytes in any::<[u8; 32]>()) {
|
||||||
|
zebra_test::init();
|
||||||
|
|
||||||
|
if let Ok(deserialized) = ValueBalance::<NegativeAllowed>::from_bytes(bytes) {
|
||||||
|
let bytes2 = deserialized.to_bytes();
|
||||||
|
prop_assert_eq!(bytes, bytes2);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ use std::{collections::BTreeMap, convert::TryInto, fmt::Debug, sync::Arc};
|
||||||
|
|
||||||
use bincode::Options;
|
use bincode::Options;
|
||||||
use zebra_chain::{
|
use zebra_chain::{
|
||||||
|
amount::NonNegative,
|
||||||
block,
|
block,
|
||||||
block::{Block, Height},
|
block::{Block, Height},
|
||||||
history_tree::HistoryTree,
|
history_tree::HistoryTree,
|
||||||
|
@ -12,6 +13,7 @@ use zebra_chain::{
|
||||||
sapling,
|
sapling,
|
||||||
serialization::{ZcashDeserialize, ZcashDeserializeInto, ZcashSerialize},
|
serialization::{ZcashDeserialize, ZcashDeserializeInto, ZcashSerialize},
|
||||||
sprout, transaction, transparent,
|
sprout, transaction, transparent,
|
||||||
|
value_balance::ValueBalance,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
@ -253,6 +255,21 @@ impl IntoDisk for orchard::tree::Root {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// The following implementations for the note commitment trees use `serde` and
|
||||||
// `bincode` because currently the inner Merkle tree frontier (from
|
// `bincode` because currently the inner Merkle tree frontier (from
|
||||||
// `incrementalmerkletree`) only supports `serde` for serialization. `bincode`
|
// `incrementalmerkletree`) only supports `serde` for serialization. `bincode`
|
||||||
|
@ -526,4 +543,11 @@ mod tests {
|
||||||
|
|
||||||
proptest!(|(val in any::<transparent::Utxo>())| assert_value_properties(val));
|
proptest!(|(val in any::<transparent::Utxo>())| assert_value_properties(val));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn roundtrip_value_balance() {
|
||||||
|
zebra_test::init();
|
||||||
|
|
||||||
|
proptest!(|(val in any::<ValueBalance::<NonNegative>>())| assert_value_properties(val));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue