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:
Alfredo Garcia 2021-08-08 22:22:27 -03:00 committed by GitHub
parent 4c4dbfe7cd
commit 910f0ff5dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 164 additions and 20 deletions

View File

@ -16,6 +16,12 @@ use std::{
use crate::serialization::{ZcashDeserialize, ZcashSerialize};
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>;
/// A runtime validated type for representing amounts of zatoshis
@ -48,6 +54,15 @@ impl<C> Amount<C> {
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`
pub fn zero() -> Amount<C>
where
@ -452,22 +467,7 @@ impl ZcashDeserialize for Amount<NonNegative> {
}
}
#[cfg(any(test, feature = "proptest-impl"))]
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>;
}
// TODO: move to tests::vectors after PR #2577 merges
#[cfg(test)]
mod test {
use crate::serialization::ZcashDeserializeInto;

View File

@ -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>;
}

View File

@ -0,0 +1,3 @@
//! Tests for amounts
mod prop;

View File

@ -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);
}
}
}

View File

@ -2,6 +2,8 @@
use crate::amount::{Amount, Constraint, Error, NonNegative};
use std::convert::TryInto;
#[cfg(any(test, feature = "proptest-impl"))]
mod arbitrary;
@ -128,6 +130,51 @@ where
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)]

View File

@ -1 +1,3 @@
//! Tests for value balances.
mod prop;

View File

@ -1,9 +1,12 @@
use crate::{amount::*, value_balance::*};
//! Randomised property tests for value balances.
use proptest::prelude::*;
use crate::{amount::*, value_balance::*};
proptest! {
#[test]
fn test_add(
fn value_blance_add(
value_balance1 in any::<ValueBalance<NegativeAllowed>>(),
value_balance2 in any::<ValueBalance<NegativeAllowed>>())
{
@ -32,7 +35,7 @@ proptest! {
}
}
#[test]
fn test_sub(
fn value_balance_sub(
value_balance1 in any::<ValueBalance<NegativeAllowed>>(),
value_balance2 in any::<ValueBalance<NegativeAllowed>>())
{
@ -62,7 +65,7 @@ proptest! {
}
#[test]
fn test_sum(
fn value_balance_sum(
value_balance1 in any::<ValueBalance<NegativeAllowed>>(),
value_balance2 in any::<ValueBalance<NegativeAllowed>>(),
) {
@ -88,4 +91,24 @@ proptest! {
_ => 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);
}
}
}

View File

@ -3,6 +3,7 @@ use std::{collections::BTreeMap, convert::TryInto, fmt::Debug, sync::Arc};
use bincode::Options;
use zebra_chain::{
amount::NonNegative,
block,
block::{Block, Height},
history_tree::HistoryTree,
@ -12,6 +13,7 @@ use zebra_chain::{
sapling,
serialization::{ZcashDeserialize, ZcashDeserializeInto, ZcashSerialize},
sprout, transaction, transparent,
value_balance::ValueBalance,
};
#[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
// `bincode` because currently the inner Merkle tree frontier (from
// `incrementalmerkletree`) only supports `serde` for serialization. `bincode`
@ -526,4 +543,11 @@ mod tests {
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));
}
}