#![allow(clippy::unwrap_in_result)]
use std::collections::HashMap;
use crate::parameters::NetworkUpgrade::*;
use crate::parameters::{
network::error::ParametersBuilderError,
testnet::{ConfiguredActivationHeights, Parameters, ParametersBuilder, RegtestParameters},
};
use crate::parameters::{subsidy::FundingStreamReceiver, NetworkKind};
use crate::{
amount::{Amount, DeferredPoolBalanceChange},
block::Block,
transaction::Transaction,
transparent::{Address, Output},
};
use color_eyre::Report;
use super::*;
#[test]
fn test_funding_stream_values() -> Result<(), Report> {
let _init_guard = zebra_test::init();
let network = &Network::Mainnet;
let canopy_activation_height = Canopy.activation_height(network).unwrap();
let nu6_activation_height = Nu6.activation_height(network).unwrap();
let nu6_1_activation_height = Nu6_1.activation_height(network).unwrap();
let dev_fund_height_range = network.all_funding_streams()[0].height_range();
let nu6_fund_height_range = network.all_funding_streams()[1].height_range();
let nu6_1_fund_height_range = network.all_funding_streams()[2].height_range();
let nu6_fund_end = Height(3_146_400);
let nu6_1_fund_end = Height(4_406_400);
assert_eq!(canopy_activation_height, Height(1_046_400));
assert_eq!(nu6_activation_height, Height(2_726_400));
assert_eq!(nu6_1_activation_height, Height(3_146_400));
assert_eq!(dev_fund_height_range.start, canopy_activation_height);
assert_eq!(dev_fund_height_range.end, nu6_activation_height);
assert_eq!(nu6_fund_height_range.start, nu6_activation_height);
assert_eq!(nu6_fund_height_range.end, nu6_fund_end);
assert_eq!(nu6_1_fund_height_range.start, nu6_1_activation_height);
assert_eq!(nu6_1_fund_height_range.end, nu6_1_fund_end);
assert_eq!(dev_fund_height_range.end, nu6_fund_height_range.start);
let mut expected_dev_fund = HashMap::new();
expected_dev_fund.insert(FundingStreamReceiver::Ecc, Amount::try_from(21_875_000)?);
expected_dev_fund.insert(
FundingStreamReceiver::ZcashFoundation,
Amount::try_from(15_625_000)?,
);
expected_dev_fund.insert(
FundingStreamReceiver::MajorGrants,
Amount::try_from(25_000_000)?,
);
let expected_dev_fund = expected_dev_fund;
let mut expected_nu6_fund = HashMap::new();
expected_nu6_fund.insert(
FundingStreamReceiver::Deferred,
Amount::try_from(18_750_000)?,
);
expected_nu6_fund.insert(
FundingStreamReceiver::MajorGrants,
Amount::try_from(12_500_000)?,
);
let expected_nu6_fund = expected_nu6_fund;
for height in [
dev_fund_height_range.start.previous().unwrap(),
dev_fund_height_range.start,
dev_fund_height_range.start.next().unwrap(),
dev_fund_height_range.end.previous().unwrap(),
dev_fund_height_range.end,
dev_fund_height_range.end.next().unwrap(),
nu6_fund_height_range.start.previous().unwrap(),
nu6_fund_height_range.start,
nu6_fund_height_range.start.next().unwrap(),
nu6_fund_height_range.end.previous().unwrap(),
nu6_fund_height_range.end,
nu6_fund_height_range.end.next().unwrap(),
nu6_1_fund_height_range.start.previous().unwrap(),
nu6_1_fund_height_range.start,
nu6_1_fund_height_range.start.next().unwrap(),
nu6_1_fund_height_range.end.previous().unwrap(),
nu6_1_fund_height_range.end,
nu6_1_fund_height_range.end.next().unwrap(),
] {
let fsv = funding_stream_values(
height,
network,
crate::parameters::subsidy::block_subsidy_with_parent_nsm_value_balance(
height,
network,
Amount::zero(),
)?,
)
.unwrap();
if height < canopy_activation_height {
assert!(fsv.is_empty());
} else if height < nu6_activation_height {
assert_eq!(fsv, expected_dev_fund);
} else if height < nu6_1_fund_end {
assert_eq!(fsv, expected_nu6_fund);
} else {
assert!(fsv.is_empty());
}
}
Ok(())
}
#[test]
fn test_funding_stream_addresses() -> Result<(), Report> {
let _init_guard = zebra_test::init();
for network in Network::iter() {
for (receiver, recipient) in network
.all_funding_streams()
.iter()
.flat_map(|fs| fs.recipients())
{
for address in recipient.addresses() {
let expected_network_kind = match network.kind() {
NetworkKind::Mainnet => NetworkKind::Mainnet,
NetworkKind::Testnet | NetworkKind::Regtest => NetworkKind::Testnet,
};
assert_eq!(
address.network_kind(),
expected_network_kind,
"incorrect network for {receiver:?} funding stream address constant: {address}",
);
}
}
}
Ok(())
}
#[test]
fn test_funding_stream_ranges_dont_overlap() -> Result<(), Report> {
let _init_guard = zebra_test::init();
for network in Network::iter() {
let funding_streams = network.all_funding_streams();
for i in 0..funding_streams.len() {
for j in (i + 1)..funding_streams.len() {
let range_a = funding_streams[i].height_range();
let range_b = funding_streams[j].height_range();
assert!(
!(range_a.start < range_b.end && range_b.start < range_a.end),
"Funding streams {i} and {j} overlap: {range_a:?} and {range_b:?}",
);
}
}
}
Ok(())
}
#[test]
fn nu7_funding_periods_preserve_partial_periods_and_the_third_halving() -> Result<(), Report> {
let builder = Parameters::build().with_activation_heights(ConfiguredActivationHeights {
nu7: Some(4_200_000),
..Network::new_default_testnet().activation_list().into()
})?;
let network = builder.clone().to_network()?;
for (height, period) in [
(4_199_999, 136),
(4_200_000, 136),
(4_231_000, 136),
(4_292_999, 136),
(4_293_000, 137),
(4_397_999, 137),
(4_398_000, 138),
] {
assert_eq!(
funding_stream_address_period(Height(height), &network),
period
);
}
assert_eq!(height_for_halving(3, &network), Some(Height(5_028_000)));
for height in [4_476_000, 5_027_999] {
let height = Height(height);
let values =
funding_stream_values(height, &network, scheduled_block_subsidy(height, &network)?)?;
assert_eq!(
values[&FundingStreamReceiver::MajorGrants].zatoshis(),
4_166_666
);
assert_eq!(
values[&FundingStreamReceiver::Deferred].zatoshis(),
6_249_999
);
}
assert!(network.funding_streams(Height(5_028_000)).is_none());
for (activation, end) in [(Some(4_300_002), 4_827_996), (None, 4_476_000)] {
let network = builder
.clone()
.with_activation_heights(ConfiguredActivationHeights {
nu7: activation,
..Network::new_default_testnet().activation_list().into()
})?
.to_network()?;
assert!(network.funding_streams(Height(end).previous()?).is_some());
assert!(network.funding_streams(Height(end)).is_none());
}
let inherited = builder
.clone()
.with_funding_streams(vec![
Default::default(),
Default::default(),
Default::default(),
])
.to_network()?;
assert!(inherited.funding_streams(Height(5_027_999)).is_some());
let explicit = builder
.with_funding_streams(
constants::testnet::FUNDING_STREAMS
.iter()
.map(Into::into)
.collect(),
)
.to_network()?;
assert!(explicit.funding_streams(Height(4_476_000)).is_none());
for activation in [4_476_000, 4_476_003] {
let network = Parameters::build()
.with_activation_heights(ConfiguredActivationHeights {
nu7: Some(activation),
..Network::new_default_testnet().activation_list().into()
})?
.to_network()?;
assert!(network.funding_streams(Height(activation)).is_none());
}
Ok(())
}
#[test]
fn nu7_funding_addresses_change_at_the_stretched_period_boundary() -> Result<(), Report> {
let addresses = [
constants::testnet::FUNDING_STREAM_ECC_ADDRESSES[0],
constants::testnet::FUNDING_STREAM_ZF_ADDRESSES[0],
];
let streams = FundingStreams::new(
Height(3_536_500)..Height(5_028_000),
[(
FundingStreamReceiver::MajorGrants,
FundingStreamRecipient::new(8, (0..27).map(|index| addresses[index % 2])),
)]
.into_iter()
.collect(),
);
let network = Parameters::build()
.with_activation_heights(ConfiguredActivationHeights {
nu7: Some(4_200_000),
..Network::new_default_testnet().activation_list().into()
})?
.with_funding_streams(vec![(&streams).into()])
.to_network()?;
for (height, expected) in [
(4_199_999, addresses[1]),
(4_200_000, addresses[1]),
(4_292_999, addresses[1]),
(4_293_000, addresses[0]),
(4_397_999, addresses[0]),
(4_398_000, addresses[1]),
] {
assert_eq!(
funding_stream_address(Height(height), &network, FundingStreamReceiver::MajorGrants),
Some(&expected.parse()?),
);
}
Ok(())
}
#[test]
fn cumulative_issuance_matches_the_schedule() -> Result<(), Report> {
assert_eq!(
cumulative_scheduled_issuance(Height(2_726_399), &Network::Mainnet)?.zatoshis(),
15_750_000 * 100_000_000i64,
);
let network = Parameters::build()
.with_slow_start_interval(Height(20))
.with_activation_heights(ConfiguredActivationHeights {
blossom: Some(130),
nu7: Some(250),
..Default::default()
})?
.with_halving_interval(48)?
.with_funding_streams(Vec::new())
.to_network()?;
let mut sum = Amount::zero();
for height in 0..900 {
sum = (sum + scheduled_block_subsidy(Height(height), &network)?)?;
assert_eq!(
cumulative_scheduled_issuance(Height(height), &network)?,
sum
);
}
Ok(())
}
#[test]
fn cumulative_issuance_stops_at_the_halving_limit_during_slow_start() -> Result<(), Report> {
let network = Parameters::build()
.with_slow_start_interval(Height(200))
.with_activation_heights(ConfiguredActivationHeights {
blossom: Some(200),
nu7: Some(201),
..Default::default()
})?
.with_halving_interval(1)?
.with_funding_streams(Vec::new())
.to_network()?;
let scheduled = (0..=200).try_fold(Amount::zero(), |sum, height| {
Ok::<_, SubsidyError>((sum + scheduled_block_subsidy(Height(height), &network)?)?)
})?;
assert_eq!(scheduled.zatoshis(), 83_937_500_000);
assert_eq!(
cumulative_scheduled_issuance(Height(200), &network)?,
scheduled
);
Ok(())
}
#[test]
fn nsm_reissuance_starts_at_the_first_reference_crossing() -> Result<(), Report> {
let reserve = Amount::try_from(1u64)?;
for (public, activation, expected) in [
(Network::Mainnet, 3_543_000, 8_940_474),
(Network::new_default_testnet(), 4_200_000, 7_835_274),
(Network::new_default_testnet(), 4_465_026, 7_305_222),
] {
let network = Parameters::build()
.with_activation_heights(ConfiguredActivationHeights {
nu7: Some(activation),
..public.activation_list().into()
})?
.clear_funding_streams()
.with_lockbox_disbursements(Vec::new())
.with_initial_nsm_value_balance(reserve)
.to_network()?;
let crossing = network
.nsm_reissuance_height()
.expect("the reference curve crosses");
assert_eq!(crossing, Height(expected));
assert_eq!(
block_subsidy_with_parent_nsm_value_balance(
Height(activation),
&network,
Amount::zero()
)?,
scheduled_block_subsidy(Height(activation), &network)?,
);
for (height, crossed) in [(crossing.previous()?, false), (crossing, true)] {
let issued = cumulative_scheduled_issuance_zatoshis(height.previous()?, &network)?;
let reference = (u128::try_from(MAX_MONEY)? - u128::from(issued)) * 1_375;
let reference = reference.div_ceil(u128::from(BLOCK_SUBSIDY_FRACTION_DENOMINATOR));
assert_eq!(
reference < u128::from(u64::from(scheduled_block_subsidy(height, &network)?)),
crossed,
);
assert_eq!(
u64::from(additional_block_subsidy(height, &network, reserve)),
u64::from(crossed),
);
}
}
let no_crossing = Parameters::build()
.with_activation_heights(ConfiguredActivationHeights {
nu7: Some(20_000_001),
..Network::new_default_testnet().activation_list().into()
})?
.clear_funding_streams()
.with_lockbox_disbursements(Vec::new())
.to_network()?;
assert_eq!(no_crossing.nsm_reissuance_height(), None);
assert_eq!(
additional_block_subsidy(Height::MAX, &no_crossing, reserve).zatoshis(),
0
);
Ok(())
}
#[test]
fn nu7_monetary_reference_vectors() -> Result<(), Report> {
let mainnet_example = Parameters::build()
.with_activation_heights(ConfiguredActivationHeights {
nu7: Some(3_543_000),
..Network::Mainnet.activation_list().into()
})?
.clear_funding_streams()
.with_lockbox_disbursements(Vec::new())
.to_network()?;
for (network, third_halving, reissuance, vectors) in [
(
Network::new_default_testnet(),
4_497_948,
7_305_222,
[
(
4_465_025,
156_250_000u64,
1_835_785_156_250_000u64,
36_329_542u128,
),
(4_465_026, 52_083_333, 1_835_785_312_500_000, 36_329_520),
(4_497_947, 52_083_333, 1_837_499_947_905_693, 36_093_758),
(4_497_948, 26_041_666, 1_837_499_999_989_026, 36_093_751),
(7_305_221, 26_041_666, 1_910_606_065_825_844, 26_041_666),
(7_305_222, 26_041_666, 1_910_606_091_867_510, 26_041_663),
(7_305_223, 26_041_666, 1_910_606_117_909_176, 26_041_659),
],
),
(
mainnet_example,
6_133_200,
8_940_474,
[
(3_542_999, 156_250_000, 1_702_593_593_750_000, 54_643_381),
(3_543_000, 52_083_333, 1_702_593_750_000_000, 54_643_360),
(6_133_199, 52_083_333, 1_837_499_947_053_267, 36_093_758),
(6_133_200, 26_041_666, 1_837_499_999_136_600, 36_093_751),
(8_940_473, 26_041_666, 1_910_606_064_973_418, 26_041_667),
(8_940_474, 26_041_666, 1_910_606_091_015_084, 26_041_663),
(8_940_475, 26_041_666, 1_910_606_117_056_750, 26_041_659),
],
),
] {
assert_eq!(height_for_halving(3, &network), Some(Height(third_halving)));
assert_eq!(network.nsm_reissuance_height(), Some(Height(reissuance)));
for (height, scheduled, cumulative, reference) in vectors {
let height = Height(height);
assert_eq!(
u64::from(scheduled_block_subsidy(height, &network)?),
scheduled
);
let issued = cumulative_scheduled_issuance_zatoshis(height.previous()?, &network)?;
assert_eq!(issued, cumulative);
assert_eq!(
((2_100_000_000_000_000u128 - u128::from(issued)) * 1_375).div_ceil(10_000_000_000),
reference,
);
if height.0 >= third_halving {
assert_eq!(reference < u128::from(scheduled), height.0 >= reissuance);
}
}
for (reserve, additional) in [
(0u64, 0u64),
(1, 1),
(10_000_000_000, 1_375),
(10_000_000_001, 1_376),
] {
let reserve = reserve.try_into()?;
assert_eq!(
additional_block_subsidy(Height(reissuance - 1), &network, reserve).zatoshis(),
0,
);
assert_eq!(
u64::from(additional_block_subsidy(
Height(reissuance),
&network,
reserve
)),
additional,
);
}
}
Ok(())
}
#[test]
fn public_nsm_seeds_match_historical_unclaimed_issuance() -> Result<(), Report> {
for (network, height, pools) in [
(
Network::Mainnet,
2_726_399,
[
1_402_654_579_762_796u64,
2_605_536_175_709,
101_161_389_852_194,
68_541_635_763_781,
],
),
(
Network::new_default_testnet(),
2_975_999,
[
1_433_024_042_538_559u64,
42_832_983_037_484,
123_413_239_739_335,
3_798_966_269_665,
],
),
] {
let scheduled = cumulative_scheduled_issuance_zatoshis(Height(height), &network)?;
assert_eq!(
u64::from(network.initial_nsm_value_balance()),
scheduled - pools.into_iter().sum::<u64>(),
);
}
Ok(())
}
#[test]
fn reissuance_height_requires_nu7_and_a_valid_height() {
assert_eq!(
Parameters::build()
.with_activation_heights(ConfiguredActivationHeights {
nu7: None,
..Network::new_default_testnet().activation_list().into()
})
.unwrap()
.with_funding_streams(Vec::new())
.with_nsm_reissuance_height(Height(1))
.to_network()
.unwrap_err(),
ParametersBuilderError::NsmReissuanceHeightBeforeNu7,
);
for height in [Height(0), Height(99), Height(Height::MAX.0 + 1)] {
let builder = Parameters::build()
.with_activation_heights(ConfiguredActivationHeights {
nu7: Some(100),
..Default::default()
})
.unwrap()
.with_nsm_reissuance_height(height)
.with_funding_streams(Vec::new());
let expected = if height == Height::MIN || height > Height::MAX {
ParametersBuilderError::InvalidActivationHeight
} else {
ParametersBuilderError::NsmReissuanceHeightBeforeNu7
};
assert_subsidy_error(builder, expected);
}
}
#[test]
fn halving_intervals_reject_unserializable_and_unsupported_heights() {
for interval in [
0,
HeightDiff::from(Height::MAX.0) + 1,
HeightDiff::from(u32::MAX) + 1,
HeightDiff::MAX / 6 + 1,
] {
assert_eq!(
Parameters::build()
.with_halving_interval(interval)
.unwrap_err(),
ParametersBuilderError::InvalidHalvingInterval,
);
}
}
#[test]
fn funding_streams_require_a_nonzero_address_period() -> Result<(), Report> {
let builder = Parameters::build().with_halving_interval(1)?;
assert_subsidy_error(
builder.clone(),
ParametersBuilderError::InvalidHalvingInterval,
);
let streamless = builder
.with_funding_streams(Vec::new())
.extend_funding_streams()?
.to_network()?;
let first_halving = streamless.height_for_first_halving();
assert_eq!(halving(first_halving, &streamless), 1);
assert_eq!(halving(first_halving.previous()?, &streamless), 0);
Ok(())
}
#[test]
fn near_maximum_first_halvings_are_rejected() -> Result<(), Report> {
let maximum_interval = (HeightDiff::from(Height::MAX.0) + 7) / 6;
let builder = Parameters::build()
.with_slow_start_interval(Height(0))
.with_activation_heights(ConfiguredActivationHeights {
blossom: Some(1),
nu7: Some(2),
..Default::default()
})?;
for interval in [maximum_interval, maximum_interval + 1] {
let invalid = builder
.clone()
.with_halving_interval(interval)?
.with_funding_streams(Vec::new());
assert!(invalid.clone().extend_funding_streams().is_err());
assert!(invalid.to_network().is_err());
}
Ok(())
}
#[test]
fn scheduled_issuance_must_fit_the_monetary_cap() -> Result<(), Report> {
for (slow_start, interval) in [
(0, HeightDiff::from(Height::MAX.0 / 2)),
(0, PRE_BLOSSOM_HALVING_INTERVAL + 1),
(20_000, PRE_BLOSSOM_HALVING_INTERVAL),
] {
let builder = Parameters::build()
.with_slow_start_interval(Height(slow_start))
.with_activation_heights(ConfiguredActivationHeights {
nu6: Some(1),
..Default::default()
})?
.with_halving_interval(interval)?
.with_funding_streams(Vec::new());
assert_subsidy_error(builder, ParametersBuilderError::InvalidSubsidySchedule);
}
Ok(())
}
#[test]
fn reissuance_requires_a_positive_integer_coefficient() -> Result<(), Report> {
let interval = 1_155_280_001;
let builder = Parameters::build()
.with_slow_start_interval(Height(0))
.with_activation_heights(ConfiguredActivationHeights {
blossom: Some(interval),
nu7: Some(interval + 1),
..Default::default()
})?
.with_halving_interval(HeightDiff::from(interval))?
.with_funding_streams(Vec::new());
assert_subsidy_error(
builder.clone(),
ParametersBuilderError::InvalidHalvingInterval,
);
assert_subsidy_error(
builder.with_nsm_reissuance_height(Height(interval + 1)),
ParametersBuilderError::InvalidHalvingInterval,
);
Ok(())
}
#[test]
fn monetary_validation_preserves_valid_schedules() -> Result<(), Report> {
let custom = Parameters::build()
.with_slow_start_interval(Height(0))
.with_activation_heights(ConfiguredActivationHeights {
nu6: Some(1),
..Default::default()
})?
.with_funding_streams(Vec::new())
.extend_funding_streams()?
.to_network()?;
let regtest = Network::new_configured_testnet(Parameters::new_regtest(RegtestParameters {
activation_heights: ConfiguredActivationHeights {
nu7: Some(1),
..Default::default()
},
nsm_reissuance_height: Some(Height(1)),
..Default::default()
})?);
for network in [
Network::Mainnet,
Parameters::build().extend_funding_streams()?.to_network()?,
Network::new_configured_testnet(Parameters::new_regtest(Default::default())?),
custom,
regtest.clone(),
] {
let total = cumulative_scheduled_issuance_zatoshis(Height::MAX, &network)?;
let genesis = u64::from(scheduled_block_subsidy(Height::MIN, &network)?);
assert!(i128::from(total - genesis) <= i128::from(crate::amount::MAX_MONEY));
assert_eq!(
scheduled_block_subsidy(Height::MAX, &network)?,
Amount::<NonNegative>::zero(),
);
}
assert_eq!(
additional_block_subsidy(Height(1), ®test, Amount::try_from(1)?),
Amount::<NonNegative>::try_from(1)?,
);
Ok(())
}
#[test]
fn negative_halving_indices_are_rejected_before_issuance_validation() -> Result<(), Report> {
let builder = Parameters::build()
.with_slow_start_interval(Height(20))
.with_activation_heights(ConfiguredActivationHeights {
blossom: Some(1),
canopy: Some(1),
..Default::default()
})?
.with_halving_interval(1)?
.with_funding_streams(Vec::new());
assert_subsidy_error(builder, ParametersBuilderError::InvalidHalvingInterval);
Ok(())
}
#[test]
fn funding_address_shortages_are_fallible_after_spacing_changes() -> Result<(), Report> {
let streams = FundingStreams::new(
Height(1_028_500)..Height(2_796_000),
[(
FundingStreamReceiver::MajorGrants,
FundingStreamRecipient::new(8, [constants::testnet::FUNDING_STREAM_MG_ADDRESSES[0]]),
)]
.into_iter()
.collect(),
);
let builder = Parameters::build()
.with_activation_heights(ConfiguredActivationHeights {
blossom: Some(584_000),
nu7: Some(1_000_000),
..Default::default()
})?
.with_funding_streams(vec![(&streams).into()]);
assert!(builder.clone().to_network().is_err());
let network = builder.extend_funding_streams()?.to_network()?;
assert_eq!(network.height_for_first_halving(), Height(1_348_000));
let range = Height(1_028_500)..Height(2_796_000);
assert_eq!(
crate::parameters::subsidy::funding_stream_address_period(range.end.previous()?, &network)
- crate::parameters::subsidy::funding_stream_address_period(range.start, &network)
+ 1,
18,
);
Ok(())
}
fn assert_subsidy_error(builder: ParametersBuilder, expected: ParametersBuilderError) {
assert_eq!(
builder.clone().extend_funding_streams().unwrap_err(),
expected,
);
assert_eq!(builder.to_network().unwrap_err(), expected);
}
#[test]
fn halving_heights_invert_slow_start_across_spacing_changes() -> Result<(), Report> {
for (blossom, nu7, first_halving) in [
(100, None, 60),
(60, None, 60),
(50, None, 70),
(50, Some(69), 72),
(50, Some(70), 70),
(50, Some(71), 70),
] {
let network = Parameters::build()
.with_slow_start_interval(Height(20))
.with_activation_heights(ConfiguredActivationHeights {
blossom: Some(blossom),
canopy: Some(blossom),
nu7,
..Default::default()
})?
.with_halving_interval(50)?
.with_funding_streams(Vec::new())
.to_network()?;
assert_eq!(network.height_for_first_halving(), Height(first_halving));
for index in 1..=4 {
let height = height_for_halving(index, &network).unwrap();
assert_eq!(halving(height, &network), index);
assert_eq!(halving(height.previous()?, &network), index - 1);
}
}
Ok(())
}
#[test]
fn zero_subsidy_still_requires_fixed_lockbox_disbursements() -> Result<(), Report> {
use std::sync::Arc;
use crate::{
block::Header,
parameters::testnet::{
ConfiguredFundingStreamRecipient, ConfiguredFundingStreams,
ConfiguredLockboxDisbursement,
},
serialization::ZcashDeserializeInto,
transaction::LockTime,
};
let height = Height(2_000);
let address: Address = "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz".parse()?;
let amount = Amount::<NonNegative>::try_from(100)?;
let network = Parameters::build()
.with_slow_start_interval(Height(0))
.with_activation_heights(ConfiguredActivationHeights {
blossom: Some(1),
canopy: Some(1),
nu6_1: Some(height.0),
..Default::default()
})?
.with_halving_interval(24)?
.with_funding_streams(vec![ConfiguredFundingStreams {
height_range: Some(height..height.next()?),
recipients: Some(vec![ConfiguredFundingStreamRecipient {
receiver: FundingStreamReceiver::ZcashFoundation,
numerator: 5,
addresses: Some(vec![address.to_string()]),
}]),
}])
.with_lockbox_disbursements(vec![ConfiguredLockboxDisbursement {
address: address.to_string(),
amount,
}])
.to_network()?;
let subsidy = scheduled_block_subsidy(height, &network)?;
assert_eq!(subsidy, Amount::<NonNegative>::zero());
let header: Header = zebra_test::vectors::DUMMY_HEADER.zcash_deserialize_into()?;
let make_block = |outputs| Block {
header: Arc::new(header),
transactions: vec![Arc::new(Transaction::test_v1(
vec![transparent::Input::Coinbase {
height,
data: vec![0],
sequence: u32::MAX,
}],
outputs,
LockTime::unlocked(),
))],
};
assert_eq!(
subsidy_is_valid(&make_block(Vec::new()), &network, subsidy),
Err(CoinbaseTransactionError::Subsidy(
SubsidyError::OneTimeLockboxDisbursementNotFound
)),
);
let paid = make_block(vec![Output::new(amount, address.script())]);
let deferred = DeferredPoolBalanceChange::new(Amount::try_from(-100)?);
assert_eq!(subsidy_is_valid(&paid, &network, subsidy)?, deferred);
miner_fees_are_valid(
&paid.transactions[0],
height,
Amount::zero(),
subsidy,
deferred,
&network,
)?;
Ok(())
}