Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -291,5 +291,5 @@ lto = "thin"
# The linter should ignore these expected config flags/values
unexpected_cfgs = { level = "warn", check-cfg = [
'cfg(tokio_unstable)', # Used by tokio-console
'cfg(zcash_unstable, values("zfuture", "nu6.1", "nu7"))' # Used in Zebra and librustzcash
'cfg(zcash_unstable, values("zfuture", "nu6.1", "nu7", "zip234", "zip235"))' # Used in Zebra and librustzcash
] }
3 changes: 2 additions & 1 deletion zebra-chain/src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,15 @@ use crate::{
};

mod commitment;
mod error;
mod hash;
mod header;
mod height;
mod serialize;

pub mod error;
pub mod genesis;
pub mod merkle;
pub mod subsidy;

#[cfg(any(test, feature = "proptest-impl"))]
pub mod arbitrary;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//!
//! [7.8]: https://zips.z.cash/protocol/protocol.pdf#subsidies

use zebra_chain::transparent::{self, Script};
use crate::transparent::{self, Script};

/// Funding Streams functions apply for blocks at and after Canopy.
pub mod funding_streams;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//!
//! [7.8]: https://zips.z.cash/protocol/protocol.pdf#subsidies

use zebra_chain::{
use crate::{
block::Height,
parameters::{subsidy::*, Network},
transparent::{self},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,15 @@

use std::collections::HashMap;

use crate::{
amount::Amount,
parameters::{
subsidy::{block_subsidy_pre_nsm, funding_stream_values, FundingStreamReceiver},
NetworkKind,
NetworkUpgrade::*,
},
};
use color_eyre::Report;
use zebra_chain::amount::Amount;
use zebra_chain::parameters::NetworkUpgrade::*;
use zebra_chain::parameters::{subsidy::FundingStreamReceiver, NetworkKind};

use crate::block::subsidy::new_coinbase_script;

Expand Down Expand Up @@ -89,7 +94,8 @@ fn test_funding_stream_values() -> Result<(), Report> {
nu6_1_fund_height_range.end,
nu6_1_fund_height_range.end.next().unwrap(),
] {
let fsv = funding_stream_values(height, network, block_subsidy(height, network)?).unwrap();
let fsv = funding_stream_values(height, network, block_subsidy_pre_nsm(height, network)?)
.unwrap();

if height < canopy_activation_height {
assert!(fsv.is_empty());
Expand Down
46 changes: 46 additions & 0 deletions zebra-chain/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use std::{io, sync::Arc};
use thiserror::Error;
use zcash_protocol::value::BalanceError;

use crate::parameters::subsidy::SubsidyError;

// TODO: Move all these enums into a common enum at the bottom.

/// Errors related to random bytes generation.
Expand Down Expand Up @@ -112,3 +114,47 @@ impl PartialEq for Error {
}

impl Eq for Error {}

#[derive(Error, Clone, Debug, PartialEq, Eq)]
#[allow(missing_docs)]
pub enum CoinbaseTransactionError {
#[error("block has no transactions")]
NoTransactions,

#[error("first transaction must be coinbase")]
Position,

#[error("coinbase input found in non-coinbase transaction")]
AfterFirst,

#[error("coinbase transaction MUST NOT have any JoinSplit descriptions")]
HasJoinSplit,

#[error("coinbase transaction MUST NOT have any Spend descriptions")]
HasSpend,

#[error("coinbase transaction MUST NOT have any Output descriptions pre-Heartwood")]
HasOutputPreHeartwood,

#[error("coinbase transaction MUST NOT have the EnableSpendsOrchard flag set")]
HasEnableSpendsOrchard,

#[error("coinbase transaction Sapling or Orchard outputs MUST be decryptable with an all-zero outgoing viewing key")]
OutputsNotDecryptable,

#[error("coinbase inputs MUST NOT exist in mempool")]
InMempool,

#[error(
"coinbase expiry {expiry_height:?} must be the same as the block {block_height:?} \
after NU5 activation, failing transaction: {transaction_hash:?}"
)]
ExpiryBlockHeight {
expiry_height: Option<crate::block::Height>,
block_height: crate::block::Height,
transaction_hash: crate::transaction::Hash,
},

#[error("coinbase transaction failed subsidy validation")]
Subsidy(#[from] SubsidyError),
}
32 changes: 31 additions & 1 deletion zebra-chain/src/parameters/network/subsidy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -789,6 +789,12 @@ pub enum SubsidyError {

#[error("invalid amount")]
InvalidAmount(#[from] amount::Error),

#[error("invalid zip233 amount")]
InvalidZip233Amount,

#[error("unexpected error occurred: {0}")]
Other(String),
}

/// The divisor used for halvings.
Expand Down Expand Up @@ -834,10 +840,34 @@ pub fn num_halvings(height: Height, network: &Network) -> u32 {
.expect("already checked for negatives")
}

#[cfg(zcash_unstable = "zip234")]
pub fn block_subsidy(
height: Height,
network: &Network,
money_reserve: Amount<NonNegative>,
) -> Result<Amount<NonNegative>, SubsidyError> {
let Some(nsm_activation_height) = NetworkUpgrade::Nu7.activation_height(network) else {
return block_subsidy_pre_nsm(height, network);
};
if height < nsm_activation_height {
return block_subsidy_pre_nsm(height, network);
}
let money_reserve: i64 = money_reserve.into();
let money_reserve: i128 = money_reserve.into();
const BLOCK_SUBSIDY_DENOMINATOR: i128 = 10_000_000_000;
const BLOCK_SUBSIDY_NUMERATOR: i128 = 4_126;

// calculate the block subsidy (in zatoshi) using the money reserve, note the rounding up
let subsidy = (money_reserve * BLOCK_SUBSIDY_NUMERATOR + (BLOCK_SUBSIDY_DENOMINATOR - 1))
/ BLOCK_SUBSIDY_DENOMINATOR;

Ok(subsidy.try_into().expect("subsidy should fit in Amount"))
}

/// `BlockSubsidy(height)` as described in [protocol specification §7.8][7.8]
///
/// [7.8]: https://zips.z.cash/protocol/protocol.pdf#subsidies
pub fn block_subsidy(
pub fn block_subsidy_pre_nsm(
height: Height,
network: &Network,
) -> Result<Amount<NonNegative>, SubsidyError> {
Expand Down
97 changes: 79 additions & 18 deletions zebra-chain/src/parameters/network/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::{
block::Height,
parameters::{
subsidy::{
block_subsidy, halving_divisor, height_for_halving, num_halvings,
block_subsidy_pre_nsm, halving_divisor, height_for_halving, num_halvings,
ParameterSubsidy as _, POST_BLOSSOM_HALVING_INTERVAL,
},
NetworkUpgrade,
Expand Down Expand Up @@ -163,32 +163,32 @@ fn block_subsidy_for_network(network: &Network) -> Result<(), Report> {
// https://z.cash/support/faq/#what-is-slow-start-mining
assert_eq!(
Amount::<NonNegative>::try_from(1_250_000_000)?,
block_subsidy((network.slow_start_interval() + 1).unwrap(), network)?
block_subsidy_pre_nsm((network.slow_start_interval() + 1).unwrap(), network)?
);
assert_eq!(
Amount::<NonNegative>::try_from(1_250_000_000)?,
block_subsidy((blossom_height - 1).unwrap(), network)?
block_subsidy_pre_nsm((blossom_height - 1).unwrap(), network)?
);

// After Blossom the block subsidy is reduced to 6.25 ZEC without halving
// https://z.cash/upgrade/blossom/
assert_eq!(
Amount::<NonNegative>::try_from(625_000_000)?,
block_subsidy(blossom_height, network)?
block_subsidy_pre_nsm(blossom_height, network)?
);

// After the 1st halving, the block subsidy is reduced to 3.125 ZEC
// https://z.cash/upgrade/canopy/
assert_eq!(
Amount::<NonNegative>::try_from(312_500_000)?,
block_subsidy(first_halving_height, network)?
block_subsidy_pre_nsm(first_halving_height, network)?
);

// After the 2nd halving, the block subsidy is reduced to 1.5625 ZEC
// See "7.8 Calculation of Block Subsidy and Founders' Reward"
assert_eq!(
Amount::<NonNegative>::try_from(156_250_000)?,
block_subsidy(
block_subsidy_pre_nsm(
(first_halving_height + POST_BLOSSOM_HALVING_INTERVAL).unwrap(),
network
)?
Expand All @@ -198,7 +198,7 @@ fn block_subsidy_for_network(network: &Network) -> Result<(), Report> {
// Check that the block subsidy rounds down correctly, and there are no errors
assert_eq!(
Amount::<NonNegative>::try_from(4_882_812)?,
block_subsidy(
block_subsidy_pre_nsm(
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL * 6)).unwrap(),
network
)?
Expand All @@ -208,7 +208,7 @@ fn block_subsidy_for_network(network: &Network) -> Result<(), Report> {
// Check that the block subsidy is calculated correctly at the limit
assert_eq!(
Amount::<NonNegative>::try_from(1)?,
block_subsidy(
block_subsidy_pre_nsm(
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL * 28)).unwrap(),
network
)?
Expand All @@ -218,31 +218,31 @@ fn block_subsidy_for_network(network: &Network) -> Result<(), Report> {
// Check that there are no errors
assert_eq!(
Amount::<NonNegative>::try_from(0)?,
block_subsidy(
block_subsidy_pre_nsm(
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL * 29)).unwrap(),
network
)?
);

assert_eq!(
Amount::<NonNegative>::try_from(0)?,
block_subsidy(
block_subsidy_pre_nsm(
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL * 39)).unwrap(),
network
)?
);

assert_eq!(
Amount::<NonNegative>::try_from(0)?,
block_subsidy(
block_subsidy_pre_nsm(
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL * 49)).unwrap(),
network
)?
);

assert_eq!(
Amount::<NonNegative>::try_from(0)?,
block_subsidy(
block_subsidy_pre_nsm(
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL * 59)).unwrap(),
network
)?
Expand All @@ -251,7 +251,7 @@ fn block_subsidy_for_network(network: &Network) -> Result<(), Report> {
// The largest possible integer divisor
assert_eq!(
Amount::<NonNegative>::try_from(0)?,
block_subsidy(
block_subsidy_pre_nsm(
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL * 62)).unwrap(),
network
)?
Expand All @@ -260,33 +260,94 @@ fn block_subsidy_for_network(network: &Network) -> Result<(), Report> {
// Other large divisors which should also result in zero
assert_eq!(
Amount::<NonNegative>::try_from(0)?,
block_subsidy(
block_subsidy_pre_nsm(
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL * 63)).unwrap(),
network
)?
);

assert_eq!(
Amount::<NonNegative>::try_from(0)?,
block_subsidy(
block_subsidy_pre_nsm(
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL * 64)).unwrap(),
network
)?
);

assert_eq!(
Amount::<NonNegative>::try_from(0)?,
block_subsidy(Height(Height::MAX_AS_U32 / 4), network)?
block_subsidy_pre_nsm(Height(Height::MAX_AS_U32 / 4), network)?
);

assert_eq!(
Amount::<NonNegative>::try_from(0)?,
block_subsidy(Height(Height::MAX_AS_U32 / 2), network)?
block_subsidy_pre_nsm(Height(Height::MAX_AS_U32 / 2), network)?
);

assert_eq!(
Amount::<NonNegative>::try_from(0)?,
block_subsidy(Height::MAX, network)?
block_subsidy_pre_nsm(Height::MAX, network)?
);

Ok(())
}

#[cfg(all(feature = "tx_v6", zcash_unstable = "zip234"))]
#[test]
fn block_subsidy_reserve_decreases_test() -> Result<(), Report> {
use crate::{
amount::MAX_MONEY,
parameters::{
subsidy::block_subsidy,
testnet::{self, ConfiguredActivationHeights},
},
};

let network = testnet::Parameters::build()
.with_activation_heights(ConfiguredActivationHeights {
nu7: Some(1),
..Default::default()
})
.clear_funding_streams()
.to_network();

let mut reserve: Amount<NonNegative> = MAX_MONEY.try_into()?;
let first_height = (network.slow_start_interval() + 1).unwrap();
let last_height = Height::MAX;

// Test a range of heights, e.g., every 100_000 blocks, plus the first and last
let mut heights = vec![first_height, last_height];
let mut h = first_height;
while h < last_height {
heights.push(h);
h = (h + 100_000).unwrap_or(last_height);
}
heights.sort_unstable();
heights.dedup();

let mut prev_subsidy = reserve;
for &height in &heights {
let subsidy = block_subsidy(height, &network, reserve)?;
assert!(
subsidy < reserve,
"subsidy exceeds reserve at height {height:?}"
);
assert!(
subsidy < prev_subsidy,
"subsidy did not decrease at height {height:?}"
);
prev_subsidy = subsidy;
// Reserve should decrease by the subsidy amount
let result = reserve.checked_sub(subsidy).expect("reserve went negative");

reserve = Amount::<NonNegative>::try_from(i64::from(result))
.expect("reserve should be non-negative");
}

// At the end, reserve should be >= 0
assert!(
reserve >= Amount::<NonNegative>::try_from(0)?.into(),
"reserve went negative"
);

Ok(())
Expand Down
Loading
Loading