remove founders reward code (#3430)

* remove founders reward code

* panic if a block before Canopy is validated for subsidy

Co-Authored-By: Daira Hopwood <daira@jacaranda.org>

Co-authored-by: Daira Hopwood <daira@jacaranda.org>
This commit is contained in:
Alfredo Garcia 2022-01-28 19:14:46 -03:00 committed by GitHub
parent e5b5ea5889
commit 6821ba9791
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 23 additions and 240 deletions

View File

@ -107,7 +107,7 @@ pub fn subsidy_is_valid(block: &Block, network: Network) -> Result<(), BlockErro
let height = block.coinbase_height().ok_or(SubsidyError::NoCoinbase)?;
let coinbase = block.transactions.get(0).ok_or(SubsidyError::NoCoinbase)?;
// Validate founders reward and funding streams
// Validate funding streams
let halving_div = subsidy::general::halving_divisor(height, network);
let canopy_activation_height = NetworkUpgrade::Canopy
.activation_height(network)
@ -121,17 +121,9 @@ pub fn subsidy_is_valid(block: &Block, network: Network) -> Result<(), BlockErro
} else if halving_div.count_ones() != 1 {
unreachable!("invalid halving divisor: the halving divisor must be a non-zero power of two")
} else if height < canopy_activation_height {
// Founders rewards are paid up to Canopy activation, on both mainnet and testnet
let founders_reward = subsidy::founders_reward::founders_reward(height, network)
.expect("invalid Amount: founders reward should be valid");
let matching_values = subsidy::general::find_output_with_amount(coinbase, founders_reward);
// TODO: the exact founders reward value must be sent as a single output to the correct address
if !matching_values.is_empty() {
Ok(())
} else {
Err(SubsidyError::FoundersRewardNotFound)?
}
// Founders rewards are paid up to Canopy activation, on both mainnet and testnet.
// But we checkpoint in Canopy so founders reward does not apply for Zebra.
unreachable!("we cannot verify consensus rules before Canopy activation");
} else if halving_div < 4 {
// Funding streams are paid from Canopy activation to the second halving
// Note: Canopy activation is at the first halving on mainnet, but not on testnet

View File

@ -2,8 +2,6 @@
//!
//! [7.8]: https://zips.z.cash/protocol/protocol.pdf#subsidies
/// Founders' Reward functions apply for blocks before Canopy.
pub mod founders_reward;
/// Funding Streams functions apply for blocks at and after Canopy.
pub mod funding_streams;
/// General subsidy functions apply for blocks after slow-start mining.

View File

@ -1,63 +0,0 @@
//! Founders' Reward calculations. - [§7.8][7.8]
//!
//! [7.8]: https://zips.z.cash/protocol/protocol.pdf#subsidies
use std::convert::TryFrom;
use zebra_chain::{
amount::{Amount, Error, NonNegative},
block::Height,
parameters::Network,
};
use crate::block::subsidy::general::{block_subsidy, halving_divisor};
use crate::parameters::subsidy::FOUNDERS_FRACTION_DIVISOR;
/// `FoundersReward(height)` as described in [protocol specification §7.8][7.8]
///
/// [7.8]: https://zips.z.cash/protocol/protocol.pdf#subsidies
pub fn founders_reward(height: Height, network: Network) -> Result<Amount<NonNegative>, Error> {
if halving_divisor(height, network) == 1 {
// this calculation is exact, because the block subsidy is divisible by
// the FOUNDERS_FRACTION_DIVISOR until long after the first halving
block_subsidy(height, network)? / FOUNDERS_FRACTION_DIVISOR
} else {
Amount::try_from(0)
}
}
#[cfg(test)]
mod test {
use super::*;
use color_eyre::Report;
use zebra_chain::parameters::NetworkUpgrade::*;
#[test]
fn test_founders_reward() -> Result<(), Report> {
zebra_test::init();
let network = Network::Mainnet;
let blossom_height = Blossom.activation_height(network).unwrap();
let canopy_height = Canopy.activation_height(network).unwrap();
// Founders reward is 20% of the block subsidy
// https://z.cash/support/faq/#founders-reward-recipients
// Before Blossom this is 20*12.5/100 = 2.5 ZEC
assert_eq!(
Amount::try_from(250_000_000),
founders_reward((blossom_height - 1).unwrap(), network)
);
// Founders reward is still 20% of the block subsidy but the block reward is half what it was
// After Blossom this is 20*6.25/100 = 1.25 ZEC
// https://z.cash/upgrade/blossom/
assert_eq!(
Amount::try_from(125_000_000),
founders_reward(blossom_height, network)
);
// After first halving(coinciding with Canopy) founders reward will expire
// https://z.cash/support/faq/#does-the-founders-reward-expire
assert_eq!(Amount::try_from(0), founders_reward(canopy_height, network));
Ok(())
}
}

View File

@ -119,7 +119,7 @@ pub fn funding_stream_address(
Address::from_str(address).expect("Address should deserialize")
}
/// Given a founders reward address, create a script and check if it is the same
/// Given a funding stream address, create a script and check if it is the same
/// as the given lock_script as described in [protocol specification §7.10][7.10]
///
/// [7.10]: https://zips.z.cash/protocol/protocol.pdf#fundingstreams.

View File

@ -9,7 +9,6 @@ use zebra_chain::{
block::Height,
parameters::{Network, NetworkUpgrade::*},
transaction::Transaction,
transparent,
};
use crate::parameters::subsidy::*;
@ -69,41 +68,7 @@ pub fn block_subsidy(height: Height, network: Network) -> Result<Amount<NonNegat
}
}
/// `MinerSubsidy(height)` as described in [protocol specification §7.8][7.8]
///
/// [7.8]: https://zips.z.cash/protocol/protocol.pdf#subsidies
///
/// `non_miner_reward` is the founders reward or funding stream value.
/// If all the rewards for a block go to the miner, use `None`.
#[allow(dead_code)]
pub fn miner_subsidy(
height: Height,
network: Network,
non_miner_reward: Option<Amount<NonNegative>>,
) -> Result<Amount<NonNegative>, Error> {
if let Some(non_miner_reward) = non_miner_reward {
block_subsidy(height, network)? - non_miner_reward
} else {
block_subsidy(height, network)
}
}
/// Returns a list of outputs in `Transaction`, which have a value equal to `Amount`.
pub fn find_output_with_amount(
transaction: &Transaction,
amount: Amount<NonNegative>,
) -> Vec<transparent::Output> {
// TODO: shielded coinbase - Heartwood
transaction
.outputs()
.iter()
.filter(|o| o.value == amount)
.cloned()
.collect()
}
/// Returns all output amounts in `Transaction`.
#[allow(dead_code)]
pub fn output_amounts(transaction: &Transaction) -> HashSet<Amount<NonNegative>> {
transaction
.outputs()
@ -118,11 +83,6 @@ mod test {
use super::*;
use color_eyre::Report;
use crate::block::subsidy::{
founders_reward::founders_reward,
funding_streams::{funding_stream_values, height_for_first_halving},
};
#[test]
fn halving_test() -> Result<(), Report> {
zebra_test::init();
@ -300,58 +260,4 @@ mod test {
Ok(())
}
#[test]
fn miner_subsidy_test() -> Result<(), Report> {
zebra_test::init();
miner_subsidy_for_network(Network::Mainnet)?;
miner_subsidy_for_network(Network::Testnet)?;
Ok(())
}
fn miner_subsidy_for_network(network: Network) -> Result<(), Report> {
let blossom_height = Blossom.activation_height(network).unwrap();
let first_halving_height = height_for_first_halving(network);
// Miner reward before Blossom is 80% of the total block reward
// 80*12.5/100 = 10 ZEC
let founders_amount = founders_reward((blossom_height - 1).unwrap(), network)?;
assert_eq!(
Amount::try_from(1_000_000_000),
miner_subsidy(
(blossom_height - 1).unwrap(),
network,
Some(founders_amount)
)
);
// After blossom the total block reward is "halved", miner still get 80%
// 80*6.25/100 = 5 ZEC
let founders_amount = founders_reward(blossom_height, network)?;
assert_eq!(
Amount::try_from(500_000_000),
miner_subsidy(blossom_height, network, Some(founders_amount))
);
// After first halving, miner will get 2.5 ZEC per mined block (not counting fees)
let funding_stream_values = funding_stream_values(first_halving_height, network)?
.iter()
.map(|row| row.1)
.sum::<Result<Amount<NonNegative>, Error>>()
.unwrap();
assert_eq!(
Amount::try_from(250_000_000),
miner_subsidy(first_halving_height, network, Some(funding_stream_values))
);
// TODO: After second halving, there will be no funding streams, and
// miners will get all the block reward
// TODO: also add some very large halvings
Ok(())
}
}

View File

@ -22,10 +22,7 @@ use zebra_chain::{
use zebra_script::CachedFfiTransaction;
use zebra_test::transcript::{ExpectedTranscriptError, Transcript};
use crate::{
parameters::{SLOW_START_INTERVAL, SLOW_START_SHIFT},
script, transaction,
};
use crate::{parameters::SLOW_START_SHIFT, script, transaction};
use super::*;
@ -294,8 +291,12 @@ fn subsidy_is_valid_for_network(network: Network) -> Result<(), Report> {
.zcash_deserialize_into::<Block>()
.expect("block is structurally valid");
let canopy_activation_height = NetworkUpgrade::Canopy
.activation_height(network)
.expect("Canopy activation height is known");
// TODO: first halving, second halving, third halving, and very large halvings
if block::Height(height) > SLOW_START_INTERVAL {
if block::Height(height) >= canopy_activation_height {
check::subsidy_is_valid(&block, network).expect("subsidies should pass for this block");
}
}
@ -308,10 +309,10 @@ fn coinbase_validation_failure() -> Result<(), Report> {
zebra_test::init();
let network = Network::Mainnet;
// Get a block in the mainnet that is inside the founders reward period,
// Get a block in the mainnet that is inside the funding stream period,
// and delete the coinbase transaction
let block =
Arc::<Block>::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_415000_BYTES[..])
Arc::<Block>::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_1046400_BYTES[..])
.expect("block should deserialize");
let mut block = Arc::try_unwrap(block).expect("block should unwrap");
@ -328,9 +329,9 @@ fn coinbase_validation_failure() -> Result<(), Report> {
let expected = BlockError::Transaction(TransactionError::Subsidy(SubsidyError::NoCoinbase));
assert_eq!(expected, result);
// Get another founders reward block, and delete the coinbase transaction
// Get another funding stream block, and delete the coinbase transaction
let block =
Arc::<Block>::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_434873_BYTES[..])
Arc::<Block>::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_1046401_BYTES[..])
.expect("block should deserialize");
let mut block = Arc::try_unwrap(block).expect("block should unwrap");
@ -347,9 +348,9 @@ fn coinbase_validation_failure() -> Result<(), Report> {
let expected = BlockError::Transaction(TransactionError::Subsidy(SubsidyError::NoCoinbase));
assert_eq!(expected, result);
// Get another founders reward block, and duplicate the coinbase transaction
// Get another funding stream, and duplicate the coinbase transaction
let block =
Arc::<Block>::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_434873_BYTES[..])
Arc::<Block>::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_1180900_BYTES[..])
.expect("block should deserialize");
let mut block = Arc::try_unwrap(block).expect("block should unwrap");
@ -374,51 +375,6 @@ fn coinbase_validation_failure() -> Result<(), Report> {
Ok(())
}
#[test]
fn founders_reward_validation_failure() -> Result<(), Report> {
zebra_test::init();
let network = Network::Mainnet;
// Get a block in the mainnet that is inside the founders reward period.
let block =
Arc::<Block>::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_415000_BYTES[..])
.expect("block should deserialize");
// Build the new transaction with modified coinbase outputs
let tx = block
.transactions
.get(0)
.map(|transaction| {
let mut output = transaction.outputs()[0].clone();
output.value = Amount::try_from(i32::MAX).unwrap();
Transaction::V3 {
inputs: transaction.inputs().to_vec(),
outputs: vec![output],
lock_time: transaction.lock_time().unwrap_or_else(LockTime::unlocked),
expiry_height: Height(0),
joinsplit_data: None,
}
})
.unwrap();
// Build new block
let transactions: Vec<Arc<zebra_chain::transaction::Transaction>> = vec![Arc::new(tx)];
let block = Block {
header: block.header,
transactions,
};
// Validate it
let result = check::subsidy_is_valid(&block, network);
let expected = Err(BlockError::Transaction(TransactionError::Subsidy(
SubsidyError::FoundersRewardNotFound,
)));
assert_eq!(expected, result);
Ok(())
}
#[test]
fn funding_stream_validation() -> Result<(), Report> {
zebra_test::init();
@ -435,8 +391,12 @@ fn funding_stream_validation_for_network(network: Network) -> Result<(), Report>
Network::Testnet => zebra_test::vectors::TESTNET_BLOCKS.iter(),
};
let canopy_activation_height = NetworkUpgrade::Canopy
.activation_height(network)
.expect("Canopy activation height is known");
for (&height, block) in block_iter {
if Height(height) > SLOW_START_SHIFT {
if Height(height) >= canopy_activation_height {
let block = Block::zcash_deserialize(&block[..]).expect("block should deserialize");
// Validate

View File

@ -23,9 +23,6 @@ pub enum SubsidyError {
#[error("no coinbase transaction in block")]
NoCoinbase,
#[error("founders reward output not found")]
FoundersRewardNotFound,
#[error("funding stream expected output not found")]
FundingStreamNotFound,

View File

@ -1,4 +1,4 @@
//! Constants for Block Subsidy, Funding Streams, and Founders' Reward
//! Constants for Block Subsidy and Funding Streams
use lazy_static::lazy_static;
use std::collections::HashMap;
@ -38,13 +38,6 @@ pub const PRE_BLOSSOM_HALVING_INTERVAL: Height = Height(840_000);
pub const POST_BLOSSOM_HALVING_INTERVAL: Height =
Height((PRE_BLOSSOM_HALVING_INTERVAL.0 as u64 * BLOSSOM_POW_TARGET_SPACING_RATIO) as u32);
/// The divisor used to calculate the FoundersFraction.
///
/// Derivation: FOUNDERS_FRACTION_DIVISOR = 1/FoundersFraction
///
/// Usage: founders_reward = block_subsidy / FOUNDERS_FRACTION_DIVISOR
pub const FOUNDERS_FRACTION_DIVISOR: u64 = 5;
/// The first halving height in the testnet is at block height `1_116_000`
/// as specified in [protocol specification §7.10.1][7.10.1]
///