Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Block Subsidy and Founders Reward Amounts #1051

Merged
merged 23 commits into from
Oct 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
feed150
add amount operators
oxarbitrage Sep 29, 2020
c150f49
rename block validation methods
oxarbitrage Sep 29, 2020
c51e9c8
add network to block verifier
oxarbitrage Sep 29, 2020
6261820
add general and founders reward subsidy modules
oxarbitrage Sep 29, 2020
3801cb6
validate founders reward
oxarbitrage Sep 29, 2020
9c39594
pass all test vectors through current subsidy validation
oxarbitrage Sep 29, 2020
abe0dbf
fix minor errors
oxarbitrage Sep 29, 2020
7d1a0f1
Tweak error messages
teor2345 Sep 30, 2020
40ec5fd
Implement Add<Height> for Height
teor2345 Sep 30, 2020
c5a8d5a
Implement Ord, Eq, and Hash for Amount
teor2345 Sep 30, 2020
2aa9a50
Remove the founders_reward dependency from subsidy::general
teor2345 Sep 30, 2020
f4ca8e1
Add testnet and halving subsidy tests
teor2345 Sep 30, 2020
86a2ea5
Make SLOW_START_SHIFT into a constant
teor2345 Sep 30, 2020
8ec0264
Refactor subsidy functions to use match
teor2345 Sep 30, 2020
c0ffed5
Refactor subsidy_is_correct to use match
teor2345 Sep 30, 2020
93e708d
Move subsidy_is_correct_test to block::tests
teor2345 Sep 30, 2020
dddf7c0
Merge pull request #29 from teor2345/block_subsidy
oxarbitrage Sep 30, 2020
8927ebc
Apply operator suggestions
teor2345 Oct 1, 2020
919056f
rustfmt
teor2345 Oct 1, 2020
46e6526
matches to if-elses
teor2345 Oct 1, 2020
d9f5135
Merge pull request #30 from teor2345/block_subsidy
oxarbitrage Oct 1, 2020
2372341
add subsidy validation error tests
oxarbitrage Oct 1, 2020
6ef71ba
Use funding streams after Canopy on testnet
teor2345 Oct 9, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 159 additions & 2 deletions zebra-chain/src/amount.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
//! [`Result`](std::result::Result)s.

use std::{
cmp::Ordering,
convert::{TryFrom, TryInto},
hash::{Hash, Hasher},
marker::PhantomData,
ops::RangeInclusive,
};
Expand All @@ -17,7 +19,7 @@ use byteorder::{ByteOrder, LittleEndian, ReadBytesExt, WriteBytesExt};
type Result<T, E = Error> = std::result::Result<T, E>;

/// A runtime validated type for representing amounts of zatoshis
#[derive(Debug, Eq, PartialEq, Clone, Copy, Serialize, Deserialize, Hash)]
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[serde(try_from = "i64")]
#[serde(bound = "C: Constraint")]
pub struct Amount<C = NegativeAllowed>(i64, PhantomData<C>);
Expand Down Expand Up @@ -191,6 +193,75 @@ where
}
}

impl<C> Hash for Amount<C> {
/// Amounts with the same value are equal, even if they have different constraints
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}

impl<C1, C2> PartialEq<Amount<C2>> for Amount<C1> {
fn eq(&self, other: &Amount<C2>) -> bool {
self.0.eq(&other.0)
}
}

impl Eq for Amount<NegativeAllowed> {}
impl Eq for Amount<NonNegative> {}

impl<C1, C2> PartialOrd<Amount<C2>> for Amount<C1> {
fn partial_cmp(&self, other: &Amount<C2>) -> Option<Ordering> {
Some(self.0.cmp(&other.0))
}
}

impl Ord for Amount<NegativeAllowed> {
fn cmp(&self, other: &Amount<NegativeAllowed>) -> Ordering {
self.0.cmp(&other.0)
}
}

impl Ord for Amount<NonNegative> {
fn cmp(&self, other: &Amount<NonNegative>) -> Ordering {
self.0.cmp(&other.0)
}
}

impl std::ops::Mul<u64> for Amount<NonNegative> {
type Output = Result<Amount<NonNegative>>;

fn mul(self, rhs: u64) -> Self::Output {
let value = (self.0 as u64)
.checked_mul(rhs)
.ok_or(Error::MultiplicationOverflow {
amount: self.0,
multiplier: rhs,
})?;
value.try_into()
}
}

impl std::ops::Mul<Amount<NonNegative>> for u64 {
type Output = Result<Amount<NonNegative>>;

fn mul(self, rhs: Amount<NonNegative>) -> Self::Output {
rhs.mul(self)
}
}

impl std::ops::Div<u64> for Amount<NonNegative> {
type Output = Result<Amount<NonNegative>>;

fn div(self, rhs: u64) -> Self::Output {
let quotient = (self.0 as u64)
.checked_div(rhs)
.ok_or(Error::DivideByZero { amount: self.0 })?;
Ok(quotient
.try_into()
.expect("division by a positive integer always stays within the constraint"))
}
}

#[derive(thiserror::Error, Debug, displaydoc::Display, Clone, PartialEq)]
#[allow(missing_docs)]
/// Errors that can be returned when validating `Amount`s
Expand All @@ -205,6 +276,10 @@ pub enum Error {
value: u64,
source: std::num::TryFromIntError,
},
/// i64 overflow when multiplying i64 non-negative amount {amount} by u64 {multiplier}
MultiplicationOverflow { amount: i64, multiplier: u64 },
/// cannot divide amount {amount} by zero
DivideByZero { amount: i64 },
}

/// Marker type for `Amount` that allows negative values.
Expand Down Expand Up @@ -243,8 +318,11 @@ impl Constraint for NonNegative {
}
}

/// Number of zatoshis in 1 ZEC
pub const COIN: i64 = 100_000_000;

/// The maximum zatoshi amount.
pub const MAX_MONEY: i64 = 21_000_000 * 100_000_000;
pub const MAX_MONEY: i64 = 21_000_000 * COIN;

/// A trait for defining constraints on `Amount`
pub trait Constraint {
Expand Down Expand Up @@ -315,6 +393,11 @@ where
#[cfg(test)]
mod test {
use super::*;

use std::{
collections::hash_map::RandomState, collections::HashSet, fmt::Debug, iter::FromIterator,
};

use color_eyre::eyre::Result;

#[test]
Expand Down Expand Up @@ -486,4 +569,78 @@ mod test {

Ok(())
}

#[test]
fn hash() -> Result<()> {
let one = Amount::<NonNegative>::try_from(1)?;
let another_one = Amount::<NonNegative>::try_from(1)?;
let zero = Amount::<NonNegative>::try_from(0)?;

let hash_set: HashSet<Amount<NonNegative>, RandomState> =
HashSet::from_iter([one].iter().cloned());
assert_eq!(hash_set.len(), 1);

let hash_set: HashSet<Amount<NonNegative>, RandomState> =
HashSet::from_iter([one, one].iter().cloned());
assert_eq!(hash_set.len(), 1, "Amount hashes are consistent");

let hash_set: HashSet<Amount<NonNegative>, RandomState> =
HashSet::from_iter([one, another_one].iter().cloned());
assert_eq!(hash_set.len(), 1, "Amount hashes are by value");

let hash_set: HashSet<Amount<NonNegative>, RandomState> =
HashSet::from_iter([one, zero].iter().cloned());
assert_eq!(
hash_set.len(),
2,
"Amount hashes are different for different values"
);

Ok(())
}

#[test]
fn ordering_constraints() -> Result<()> {
ordering::<NonNegative, NonNegative>()?;
ordering::<NonNegative, NegativeAllowed>()?;
ordering::<NegativeAllowed, NonNegative>()?;
ordering::<NegativeAllowed, NegativeAllowed>()?;

Ok(())
}

fn ordering<C1, C2>() -> Result<()>
where
C1: Constraint + Debug,
C2: Constraint + Debug,
{
let zero = Amount::<C1>::try_from(0)?;
let one = Amount::<C2>::try_from(1)?;
let another_one = Amount::<C1>::try_from(1)?;

assert_eq!(one, one);
assert_eq!(one, another_one, "Amount equality is by value");

assert_ne!(one, zero);
assert_ne!(zero, one);

assert!(one > zero);
assert!(zero < one);
assert!(zero <= one);

let negative_one = Amount::<NegativeAllowed>::try_from(-1)?;
let negative_two = Amount::<NegativeAllowed>::try_from(-2)?;

assert_ne!(negative_one, zero);
assert_ne!(negative_one, one);

assert!(negative_one < zero);
assert!(negative_one <= one);
assert!(zero > negative_one);
assert!(zero >= negative_one);
assert!(negative_two < negative_one);
assert!(negative_one > negative_two);

Ok(())
}
}
86 changes: 81 additions & 5 deletions zebra-chain/src/block/height.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use crate::serialization::SerializationError;

use std::ops::{Add, Sub};
use std::{
convert::TryFrom,
ops::{Add, Sub},
};

/// The height of a block is the length of the chain back to the genesis block.
///
Expand Down Expand Up @@ -47,19 +50,51 @@ impl Height {
pub const MAX_AS_U32: u32 = Self::MAX.0;
}

impl Add<Height> for Height {
type Output = Option<Height>;

fn add(self, rhs: Height) -> Option<Height> {
// We know that both values are positive integers. Therefore, the result is
// positive, and we can skip the conversions. The checked_add is required,
// because the result may overflow.
let height = self.0.checked_add(rhs.0)?;
let height = Height(height);

if height <= Height::MAX && height >= Height::MIN {
Some(height)
} else {
None
}
}
}

impl Sub<Height> for Height {
type Output = i32;

/// Panics if the inputs or result are outside the valid i32 range.
fn sub(self, rhs: Height) -> i32 {
(self.0 as i32) - (rhs.0 as i32)
// We construct heights from integers without any checks,
// so the inputs or result could be out of range.
let lhs = i32::try_from(self.0)
.expect("out of range input `self`: inputs should be valid Heights");
let rhs =
i32::try_from(rhs.0).expect("out of range input `rhs`: inputs should be valid Heights");
lhs.checked_sub(rhs)
.expect("out of range result: valid input heights should yield a valid result")
}
}

// We don't implement Add<u32> or Sub<u32>, because they cause type inference issues for integer constants.

impl Add<i32> for Height {
type Output = Option<Height>;

fn add(self, rhs: i32) -> Option<Height> {
let result = ((self.0 as i32) + rhs) as u32;
// Because we construct heights from integers without any checks,
// the input values could be outside the valid range for i32.
let lhs = i32::try_from(self.0).ok()?;
let result = lhs.checked_add(rhs)?;
let result = u32::try_from(result).ok()?;
match result {
h if (Height(h) <= Height::MAX && Height(h) >= Height::MIN) => Some(Height(h)),
_ => None,
Expand All @@ -71,7 +106,10 @@ impl Sub<i32> for Height {
type Output = Option<Height>;

fn sub(self, rhs: i32) -> Option<Height> {
let result = ((self.0 as i32) - rhs) as u32;
// These checks are required, see above for details.
let lhs = i32::try_from(self.0).ok()?;
let result = lhs.checked_sub(rhs)?;
let result = u32::try_from(result).ok()?;
match result {
h if (Height(h) <= Height::MAX && Height(h) >= Height::MIN) => Some(Height(h)),
_ => None,
Expand All @@ -94,14 +132,52 @@ impl Arbitrary for Height {

#[test]
fn operator_tests() {
assert_eq!(Some(Height(2)), Height(1) + Height(1));
assert_eq!(None, Height::MAX + Height(1));
// Bad heights aren't caught at compile-time or runtime, until we add or subtract
assert_eq!(None, Height(Height::MAX_AS_U32 + 1) + Height(0));
assert_eq!(None, Height(i32::MAX as u32) + Height(0));
assert_eq!(None, Height(u32::MAX) + Height(0));

assert_eq!(Some(Height(2)), Height(1) + 1);
assert_eq!(None, Height::MAX + 1);
// Adding negative numbers
assert_eq!(Some(Height(1)), Height(2) + -1);
assert_eq!(Some(Height(0)), Height(1) + -1);
assert_eq!(None, Height(0) + -1);
assert_eq!(Some(Height(Height::MAX_AS_U32 - 1)), Height::MAX + -1);
// Bad heights aren't caught at compile-time or runtime, until we add or subtract
// `+ 0` would also cause an error here, but it triggers a spurious clippy lint
assert_eq!(None, Height(Height::MAX_AS_U32 + 1) + 1);
assert_eq!(None, Height(i32::MAX as u32) + 1);
assert_eq!(None, Height(u32::MAX) + 1);
// Adding negative numbers
assert_eq!(None, Height(i32::MAX as u32) + -1);
assert_eq!(None, Height(u32::MAX) + -1);

assert_eq!(Some(Height(1)), Height(2) - 1);
assert_eq!(Some(Height(0)), Height(1) - 1);
assert_eq!(None, Height(0) - 1);

assert_eq!(Some(Height(Height::MAX_AS_U32 - 1)), Height::MAX - 1);
// Subtracting negative numbers
assert_eq!(Some(Height(2)), Height(1) - -1);
assert_eq!(Some(Height::MAX), Height(Height::MAX_AS_U32 - 1) - -1);
assert_eq!(None, Height::MAX - -1);
// Bad heights aren't caught at compile-time or runtime, until we add or subtract
assert_eq!(None, Height(i32::MAX as u32) - 1);
assert_eq!(None, Height(u32::MAX) - 1);
// Subtracting negative numbers
assert_eq!(None, Height(Height::MAX_AS_U32 + 1) - -1);
assert_eq!(None, Height(i32::MAX as u32) - -1);
assert_eq!(None, Height(u32::MAX) - -1);

// Sub<Height> panics on out of range errors
assert_eq!(1, Height(2) - Height(1));
assert_eq!(0, Height(1) - Height(1));
assert_eq!(-1, Height(0) - Height(1));
assert_eq!(-5, Height(2) - Height(7));
assert_eq!(Height::MAX_AS_U32 as i32, Height::MAX - Height(0));
assert_eq!(1, Height::MAX - Height(Height::MAX_AS_U32 - 1));
assert_eq!(-1, Height(Height::MAX_AS_U32 - 1) - Height::MAX);
assert_eq!(-(Height::MAX_AS_U32 as i32), Height(0) - Height::MAX);
}
Loading