change(consensus): Add lockbox funding stream (#8694)
* Addresses clippy lints * checks network magic and returns early from `is_regtest()` * Moves `subsidy.rs` to `zebra-chain`, refactors funding streams into structs, splits them into pre/post NU6 funding streams, and adds them as a field on `testnet::Parameters` * Replaces Vec with HashMap, adds `ConfiguredFundingStreams` type and conversion logic with constraints. Minor refactors * Empties recipients list * Adds a comment on num_addresses calculation being invalid for configured Testnets, but that being okay since configured testnet parameters are checked when they're being built * Documentation fixes, minor cleanup, renames a test, adds TODOs, and fixes test logic * Removes unnecessary `ParameterSubsidy` impl for &Network, adds docs and TODOs * Adds a "deferred" FundingStreamReceiver, adds a post-NU6 funding streams, updates the `miner_fees_are_valid()` and `subsidy_is_valid()` functions to check that the deferred pool contribution is valid and that there are no unclaimed block subsidies after NU6 activation, and adds some TODOs * adds `lockbox_input_value()` fn * Adds TODOs for linking to relevant ZIPs and updating height ranges * Adds `nu6_lockbox_funding_stream` acceptance test * updates funding stream values test to check post-NU6 funding streams too, adds Mainnet/Testnet NU6 activation heights, fixes lints/compilation issue * Reverts Mainnet/Testnet NU6 activation height definitions, updates `test_funding_stream_values()` to use a configured testnet with the post-NU6 Mainnet funding streams height range * reverts unnecessary refactor * appease clippy * Adds a test for `lockbox_input_value()` * Applies suggestions from code review * Fixes potential panic * Fixes bad merge * Update zebra-chain/src/parameters/network_upgrade.rs * Updates acceptance test to check that invalid blocks are rejected * Checks that the original valid block template at height 2 is accepted as a block submission * Reverts changes for coinbase should balance exactly ZIP * updates test name * Updates deferred pool funding stream name to "Lockbox", moves post-NU6 height ranges to constants, updates TODO * Updates `get_block_subsidy()` RPC method to exclude lockbox funding stream from `fundingstreams` field * Adds a TODO for updating `FundingStreamReceiver::name()` method docs * Updates `FundingStreamRecipient::new()` to accept an iterator of items instead of an option of an iterator, updates a comment quoting the coinbase transaction balance consensus rule to note that the current code is inconsistent with the protocol spec, adds a TODO for updating the quote there once the protocol spec has been updated. * Uses FPF Testnet address for post-NU6 testnet funding streams * Updates the NU6 consensus branch id --------- Co-authored-by: Pili Guerra <mpguerra@users.noreply.github.com>
This commit is contained in:
parent
45261a26eb
commit
e56ee4c792
|
@ -176,3 +176,7 @@ required-features = ["bench"]
|
|||
[[bench]]
|
||||
name = "redpallas"
|
||||
harness = false
|
||||
|
||||
[lints.rust]
|
||||
# TODO: Remove this once it's no longer needed for NU6.
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(zcash_unstable, values("nu6"))'] }
|
||||
|
|
|
@ -62,6 +62,9 @@ pub enum FundingStreamReceiver {
|
|||
|
||||
/// The Major Grants (Zcash Community Grants) funding stream.
|
||||
MajorGrants,
|
||||
/// The deferred pool contribution.
|
||||
// TODO: Add link to lockbox stream ZIP
|
||||
Deferred,
|
||||
}
|
||||
|
||||
impl FundingStreamReceiver {
|
||||
|
@ -69,11 +72,15 @@ impl FundingStreamReceiver {
|
|||
///
|
||||
/// [ZIP-1014]: https://zips.z.cash/zip-1014#abstract
|
||||
/// [`zcashd`]: https://github.com/zcash/zcash/blob/3f09cfa00a3c90336580a127e0096d99e25a38d6/src/consensus/funding.cpp#L13-L32
|
||||
// TODO: Update method documentation with a reference to https://zips.z.cash/draft-nuttycom-funding-allocation once its
|
||||
// status is updated to 'Proposed'.
|
||||
pub fn name(self) -> &'static str {
|
||||
match self {
|
||||
FundingStreamReceiver::Ecc => "Electric Coin Company",
|
||||
FundingStreamReceiver::ZcashFoundation => "Zcash Foundation",
|
||||
FundingStreamReceiver::MajorGrants => "Major Grants",
|
||||
// TODO: Find out what this should be called and update the funding stream name.
|
||||
FundingStreamReceiver::Deferred => "Lockbox",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -181,7 +188,7 @@ lazy_static! {
|
|||
recipients: [
|
||||
(
|
||||
FundingStreamReceiver::Ecc,
|
||||
FundingStreamRecipient::new(7, FUNDING_STREAM_ECC_ADDRESSES_MAINNET.iter()),
|
||||
FundingStreamRecipient::new(7, FUNDING_STREAM_ECC_ADDRESSES_MAINNET),
|
||||
),
|
||||
(
|
||||
FundingStreamReceiver::ZcashFoundation,
|
||||
|
@ -199,8 +206,21 @@ lazy_static! {
|
|||
/// The post-NU6 funding streams for Mainnet
|
||||
// TODO: Add a reference to lockbox stream ZIP, this is currently based on https://zips.z.cash/draft-nuttycom-funding-allocation
|
||||
pub static ref POST_NU6_FUNDING_STREAMS_MAINNET: FundingStreams = FundingStreams {
|
||||
height_range: Height(2_726_400)..Height(3_146_400),
|
||||
recipients: HashMap::new()
|
||||
// TODO: Adjust this height range and recipient list once a proposal is selected
|
||||
height_range: POST_NU6_FUNDING_STREAM_START_RANGE_MAINNET,
|
||||
recipients: [
|
||||
(
|
||||
FundingStreamReceiver::Deferred,
|
||||
FundingStreamRecipient::new::<[&str; 0], &str>(12, []),
|
||||
),
|
||||
(
|
||||
FundingStreamReceiver::MajorGrants,
|
||||
// TODO: Update these addresses
|
||||
FundingStreamRecipient::new(8, FUNDING_STREAM_MG_ADDRESSES_MAINNET),
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.collect()
|
||||
};
|
||||
|
||||
/// The pre-NU6 funding streams for Testnet as described in [protocol specification §7.10.1][7.10.1]
|
||||
|
@ -229,11 +249,45 @@ lazy_static! {
|
|||
// TODO: Add a reference to lockbox stream ZIP, this is currently based on the number of blocks between the
|
||||
// start and end heights for Mainnet in https://zips.z.cash/draft-nuttycom-funding-allocation
|
||||
pub static ref POST_NU6_FUNDING_STREAMS_TESTNET: FundingStreams = FundingStreams {
|
||||
height_range: Height(2_942_000)..Height(3_362_000),
|
||||
recipients: HashMap::new()
|
||||
// TODO: Adjust this height range and recipient list once a proposal is selected
|
||||
height_range: POST_NU6_FUNDING_STREAM_START_RANGE_TESTNET,
|
||||
recipients: [
|
||||
(
|
||||
FundingStreamReceiver::Deferred,
|
||||
FundingStreamRecipient::new::<[&str; 0], &str>(12, []),
|
||||
),
|
||||
(
|
||||
FundingStreamReceiver::MajorGrants,
|
||||
// TODO: Update these addresses
|
||||
FundingStreamRecipient::new(8, POST_NU6_FUNDING_STREAM_FPF_ADDRESSES_TESTNET),
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.collect()
|
||||
};
|
||||
}
|
||||
|
||||
/// The start height of post-NU6 funding streams on Mainnet
|
||||
// TODO: Add a reference to lockbox stream ZIP, this is currently based on https://zips.z.cash/draft-nuttycom-funding-allocation
|
||||
const POST_NU6_FUNDING_STREAM_START_HEIGHT_MAINNET: u32 = 2_726_400;
|
||||
|
||||
/// The start height of post-NU6 funding streams on Testnet
|
||||
// TODO: Add a reference to lockbox stream ZIP, this is currently based on https://zips.z.cash/draft-nuttycom-funding-allocation
|
||||
const POST_NU6_FUNDING_STREAM_START_HEIGHT_TESTNET: u32 = 2_942_000;
|
||||
|
||||
/// The number of blocks contained in the post-NU6 funding streams height ranges on Mainnet or Testnet.
|
||||
const POST_NU6_FUNDING_STREAM_NUM_BLOCKS: u32 = 420_000;
|
||||
|
||||
/// The post-NU6 funding stream height range on Mainnet
|
||||
const POST_NU6_FUNDING_STREAM_START_RANGE_MAINNET: std::ops::Range<Height> =
|
||||
Height(POST_NU6_FUNDING_STREAM_START_HEIGHT_MAINNET)
|
||||
..Height(POST_NU6_FUNDING_STREAM_START_HEIGHT_MAINNET + POST_NU6_FUNDING_STREAM_NUM_BLOCKS);
|
||||
|
||||
/// The post-NU6 funding stream height range on Testnet
|
||||
const POST_NU6_FUNDING_STREAM_START_RANGE_TESTNET: std::ops::Range<Height> =
|
||||
Height(POST_NU6_FUNDING_STREAM_START_HEIGHT_TESTNET)
|
||||
..Height(POST_NU6_FUNDING_STREAM_START_HEIGHT_TESTNET + POST_NU6_FUNDING_STREAM_NUM_BLOCKS);
|
||||
|
||||
/// Address change interval function here as a constant
|
||||
/// as described in [protocol specification §7.10.1][7.10.1].
|
||||
///
|
||||
|
@ -402,6 +456,18 @@ pub const FUNDING_STREAM_ZF_ADDRESSES_TESTNET: [&str; FUNDING_STREAMS_NUM_ADDRES
|
|||
pub const FUNDING_STREAM_MG_ADDRESSES_TESTNET: [&str; FUNDING_STREAMS_NUM_ADDRESSES_TESTNET] =
|
||||
["t2Gvxv2uNM7hbbACjNox4H6DjByoKZ2Fa3P"; FUNDING_STREAMS_NUM_ADDRESSES_TESTNET];
|
||||
|
||||
/// Number of addresses for each post-NU6 funding stream in the Testnet.
|
||||
/// In the spec ([protocol specification §7.10][7.10]) this is defined as: `fs.addressindex(fs.endheight - 1)`
|
||||
/// however we know this value beforehand so we prefer to make it a constant instead.
|
||||
///
|
||||
/// [7.10]: https://zips.z.cash/protocol/protocol.pdf#fundingstreams
|
||||
pub const POST_NU6_FUNDING_STREAMS_NUM_ADDRESSES_TESTNET: usize = 13;
|
||||
|
||||
/// List of addresses for the Major Grants post-NU6 funding stream in the Testnet administered by the Financial Privacy Fund (FPF).
|
||||
pub const POST_NU6_FUNDING_STREAM_FPF_ADDRESSES_TESTNET: [&str;
|
||||
POST_NU6_FUNDING_STREAMS_NUM_ADDRESSES_TESTNET] =
|
||||
["t2HifwjUj9uyxr9bknR8LFuQbc98c3vkXtu"; POST_NU6_FUNDING_STREAMS_NUM_ADDRESSES_TESTNET];
|
||||
|
||||
/// Returns the address change period
|
||||
/// as described in [protocol specification §7.10][7.10]
|
||||
///
|
||||
|
|
|
@ -68,7 +68,7 @@ pub struct ConfiguredFundingStreamRecipient {
|
|||
/// The numerator for each funding stream receiver category, see [`FundingStreamRecipient::numerator`] for more details.
|
||||
pub numerator: u64,
|
||||
/// Addresses for the funding stream recipient, see [`FundingStreamRecipient::addresses`] for more details.
|
||||
pub addresses: Vec<String>,
|
||||
pub addresses: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
impl ConfiguredFundingStreamRecipient {
|
||||
|
@ -76,7 +76,7 @@ impl ConfiguredFundingStreamRecipient {
|
|||
pub fn into_recipient(self) -> (FundingStreamReceiver, FundingStreamRecipient) {
|
||||
(
|
||||
self.receiver,
|
||||
FundingStreamRecipient::new(self.numerator, self.addresses),
|
||||
FundingStreamRecipient::new(self.numerator, self.addresses.unwrap_or_default()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -133,8 +133,12 @@ impl ConfiguredFundingStreams {
|
|||
))
|
||||
.expect("no overflow should happen in this sub") as usize;
|
||||
|
||||
for recipient in funding_streams.recipients().values() {
|
||||
// TODO: Make an exception for the `Deferred` receiver.
|
||||
for (&receiver, recipient) in funding_streams.recipients() {
|
||||
if receiver == FundingStreamReceiver::Deferred {
|
||||
// The `Deferred` receiver doesn't need any addresses.
|
||||
continue;
|
||||
}
|
||||
|
||||
assert!(
|
||||
recipient.addresses().len() >= expected_min_num_addresses,
|
||||
"recipients must have a sufficient number of addresses for height range, \
|
||||
|
|
|
@ -320,9 +320,11 @@ fn check_configured_funding_stream_constraints() {
|
|||
recipients: Some(vec![ConfiguredFundingStreamRecipient {
|
||||
receiver: FundingStreamReceiver::Ecc,
|
||||
numerator: 20,
|
||||
addresses: FUNDING_STREAM_ECC_ADDRESSES_TESTNET
|
||||
.map(Into::into)
|
||||
.to_vec(),
|
||||
addresses: Some(
|
||||
FUNDING_STREAM_ECC_ADDRESSES_TESTNET
|
||||
.map(Into::into)
|
||||
.to_vec(),
|
||||
),
|
||||
}]),
|
||||
..Default::default()
|
||||
},
|
||||
|
@ -330,9 +332,11 @@ fn check_configured_funding_stream_constraints() {
|
|||
recipients: Some(vec![ConfiguredFundingStreamRecipient {
|
||||
receiver: FundingStreamReceiver::Ecc,
|
||||
numerator: 100,
|
||||
addresses: FUNDING_STREAM_ECC_ADDRESSES_TESTNET
|
||||
.map(Into::into)
|
||||
.to_vec(),
|
||||
addresses: Some(
|
||||
FUNDING_STREAM_ECC_ADDRESSES_TESTNET
|
||||
.map(Into::into)
|
||||
.to_vec(),
|
||||
),
|
||||
}]),
|
||||
..Default::default()
|
||||
},
|
||||
|
@ -398,7 +402,7 @@ fn check_configured_funding_stream_constraints() {
|
|||
recipients: Some(vec![ConfiguredFundingStreamRecipient {
|
||||
receiver: FundingStreamReceiver::Ecc,
|
||||
numerator: 10,
|
||||
addresses: vec![],
|
||||
addresses: Some(vec![]),
|
||||
}]),
|
||||
..Default::default()
|
||||
});
|
||||
|
@ -410,9 +414,11 @@ fn check_configured_funding_stream_constraints() {
|
|||
recipients: Some(vec![ConfiguredFundingStreamRecipient {
|
||||
receiver: FundingStreamReceiver::Ecc,
|
||||
numerator: 101,
|
||||
addresses: FUNDING_STREAM_ECC_ADDRESSES_TESTNET
|
||||
.map(Into::into)
|
||||
.to_vec(),
|
||||
addresses: Some(
|
||||
FUNDING_STREAM_ECC_ADDRESSES_TESTNET
|
||||
.map(Into::into)
|
||||
.to_vec(),
|
||||
),
|
||||
}]),
|
||||
..Default::default()
|
||||
});
|
||||
|
@ -424,9 +430,11 @@ fn check_configured_funding_stream_constraints() {
|
|||
recipients: Some(vec![ConfiguredFundingStreamRecipient {
|
||||
receiver: FundingStreamReceiver::Ecc,
|
||||
numerator: 10,
|
||||
addresses: FUNDING_STREAM_ECC_ADDRESSES_MAINNET
|
||||
.map(Into::into)
|
||||
.to_vec(),
|
||||
addresses: Some(
|
||||
FUNDING_STREAM_ECC_ADDRESSES_MAINNET
|
||||
.map(Into::into)
|
||||
.to_vec(),
|
||||
),
|
||||
}]),
|
||||
..Default::default()
|
||||
});
|
||||
|
|
|
@ -88,7 +88,8 @@ pub(super) const MAINNET_ACTIVATION_HEIGHTS: &[(block::Height, NetworkUpgrade)]
|
|||
(block::Height(903_000), Heartwood),
|
||||
(block::Height(1_046_400), Canopy),
|
||||
(block::Height(1_687_104), Nu5),
|
||||
// TODO: Add NU6.
|
||||
// TODO: Add NU6
|
||||
// (block::Height(2_726_400), Nu6),
|
||||
];
|
||||
|
||||
/// Fake mainnet network upgrade activation heights, used in tests.
|
||||
|
@ -124,7 +125,8 @@ pub(super) const TESTNET_ACTIVATION_HEIGHTS: &[(block::Height, NetworkUpgrade)]
|
|||
(block::Height(903_800), Heartwood),
|
||||
(block::Height(1_028_500), Canopy),
|
||||
(block::Height(1_842_420), Nu5),
|
||||
// TODO: Add NU6.
|
||||
// TODO: Add NU6
|
||||
// (block::Height(2_942_000), Nu6),
|
||||
];
|
||||
|
||||
/// Fake testnet network upgrade activation heights, used in tests.
|
||||
|
@ -214,8 +216,7 @@ pub(crate) const CONSENSUS_BRANCH_IDS: &[(NetworkUpgrade, ConsensusBranchId)] =
|
|||
(Heartwood, ConsensusBranchId(0xf5b9230b)),
|
||||
(Canopy, ConsensusBranchId(0xe9ff75a6)),
|
||||
(Nu5, ConsensusBranchId(0xc2d6d0b4)),
|
||||
// TODO: Use the real consensus branch ID once it's specified.
|
||||
(Nu6, ConsensusBranchId(0xdeadc0de)),
|
||||
(Nu6, ConsensusBranchId(0xc8e71055)),
|
||||
];
|
||||
|
||||
/// The target block spacing before Blossom.
|
||||
|
@ -530,6 +531,8 @@ impl From<zcash_protocol::consensus::NetworkUpgrade> for NetworkUpgrade {
|
|||
zcash_protocol::consensus::NetworkUpgrade::Heartwood => Self::Heartwood,
|
||||
zcash_protocol::consensus::NetworkUpgrade::Canopy => Self::Canopy,
|
||||
zcash_protocol::consensus::NetworkUpgrade::Nu5 => Self::Nu5,
|
||||
#[cfg(zcash_unstable = "nu6")]
|
||||
zcash_protocol::consensus::NetworkUpgrade::Nu6 => Self::Nu6,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ use chrono::{DateTime, Utc};
|
|||
use zebra_chain::{
|
||||
amount::{Amount, Error as AmountError, NonNegative},
|
||||
block::{Block, Hash, Header, Height},
|
||||
parameters::{Network, NetworkUpgrade},
|
||||
parameters::{subsidy::FundingStreamReceiver, Network, NetworkUpgrade},
|
||||
transaction,
|
||||
work::{
|
||||
difficulty::{ExpandedDifficulty, ParameterDifficulty as _},
|
||||
|
@ -177,7 +177,7 @@ pub fn subsidy_is_valid(block: &Block, network: &Network) -> Result<(), BlockErr
|
|||
// 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 {
|
||||
} else if halving_div < 8 {
|
||||
// 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
|
||||
// ZIP-1014 only applies to mainnet, ZIP-214 contains the specific rules for testnet
|
||||
|
@ -194,8 +194,16 @@ pub fn subsidy_is_valid(block: &Block, network: &Network) -> Result<(), BlockErr
|
|||
//
|
||||
// https://zips.z.cash/protocol/protocol.pdf#fundingstreams
|
||||
for (receiver, expected_amount) in funding_streams {
|
||||
let address =
|
||||
subsidy::funding_streams::funding_stream_address(height, network, receiver);
|
||||
if receiver == FundingStreamReceiver::Deferred {
|
||||
// The deferred pool contribution is checked in `miner_fees_are_valid()`
|
||||
// TODO: Add link to lockbox stream ZIP
|
||||
continue;
|
||||
}
|
||||
|
||||
let address = subsidy::funding_streams::funding_stream_address(
|
||||
height, network, receiver,
|
||||
)
|
||||
.expect("funding stream receivers other than the deferred pool must have an address");
|
||||
|
||||
let has_expected_output =
|
||||
subsidy::funding_streams::filter_outputs_by_address(coinbase, address)
|
||||
|
@ -237,6 +245,12 @@ pub fn miner_fees_are_valid(
|
|||
let block_subsidy = subsidy::general::block_subsidy(height, network)
|
||||
.expect("a valid block subsidy for this height and network");
|
||||
|
||||
// TODO: Add link to lockbox stream ZIP
|
||||
let expected_deferred_amount = subsidy::funding_streams::funding_stream_values(height, network)
|
||||
.expect("we always expect a funding stream hashmap response even if empty")
|
||||
.remove(&FundingStreamReceiver::Deferred)
|
||||
.unwrap_or_default();
|
||||
|
||||
// # Consensus
|
||||
//
|
||||
// > The total value in zatoshi of transparent outputs from a coinbase transaction,
|
||||
|
@ -244,9 +258,16 @@ pub fn miner_fees_are_valid(
|
|||
// > in zatoshi of block subsidy plus the transaction fees paid by transactions in this block.
|
||||
//
|
||||
// https://zips.z.cash/protocol/protocol.pdf#txnconsensus
|
||||
//
|
||||
// The expected lockbox funding stream output of the coinbase transaction is also subtracted
|
||||
// from the block subsidy value plus the transaction fees paid by transactions in this block.
|
||||
//
|
||||
// TODO: Update the quote from the protocol specification once its been updated to reflect the changes in
|
||||
// https://zips.z.cash/draft-nuttycom-funding-allocation and https://zips.z.cash/draft-hopwood-coinbase-balance.
|
||||
let left = (transparent_value_balance - sapling_value_balance - orchard_value_balance)
|
||||
.map_err(|_| SubsidyError::SumOverflow)?;
|
||||
let right = (block_subsidy + block_miner_fees).map_err(|_| SubsidyError::SumOverflow)?;
|
||||
let right = (block_subsidy + block_miner_fees - expected_deferred_amount)
|
||||
.map_err(|_| SubsidyError::SumOverflow)?;
|
||||
|
||||
if left > right {
|
||||
Err(SubsidyError::InvalidMinerFees)?;
|
||||
|
|
|
@ -52,8 +52,17 @@ pub fn funding_stream_values(
|
|||
/// as described in [protocol specification §7.10][7.10]
|
||||
///
|
||||
/// [7.10]: https://zips.z.cash/protocol/protocol.pdf#fundingstreams
|
||||
fn funding_stream_address_index(height: Height, network: &Network) -> usize {
|
||||
fn funding_stream_address_index(
|
||||
height: Height,
|
||||
network: &Network,
|
||||
receiver: FundingStreamReceiver,
|
||||
) -> Option<usize> {
|
||||
if receiver == FundingStreamReceiver::Deferred {
|
||||
return None;
|
||||
}
|
||||
|
||||
let funding_streams = network.funding_streams(height);
|
||||
let num_addresses = funding_streams.recipient(receiver)?.addresses().len();
|
||||
|
||||
let index = 1u32
|
||||
.checked_add(funding_stream_address_period(height, network))
|
||||
|
@ -64,22 +73,10 @@ fn funding_stream_address_index(height: Height, network: &Network) -> usize {
|
|||
))
|
||||
.expect("no overflow should happen in this sub") as usize;
|
||||
|
||||
// Funding stream recipients may not have the same number of addresses on configured Testnets,
|
||||
// the number of addresses for each recipient should be validated for a configured height range
|
||||
// when configured Testnet parameters are built.
|
||||
let num_addresses = funding_streams
|
||||
.recipients()
|
||||
.values()
|
||||
.next()
|
||||
// TODO: Return an Option from this function and replace `.unwrap()` with `?`
|
||||
.unwrap()
|
||||
.addresses()
|
||||
.len();
|
||||
|
||||
assert!(index > 0 && index <= num_addresses);
|
||||
// spec formula will output an index starting at 1 but
|
||||
// Zebra indices for addresses start at zero, return converted.
|
||||
index - 1
|
||||
Some(index - 1)
|
||||
}
|
||||
|
||||
/// Return the address corresponding to given height, network and funding stream receiver.
|
||||
|
@ -90,17 +87,10 @@ pub fn funding_stream_address(
|
|||
height: Height,
|
||||
network: &Network,
|
||||
receiver: FundingStreamReceiver,
|
||||
) -> &transparent::Address {
|
||||
let index = funding_stream_address_index(height, network);
|
||||
) -> Option<&transparent::Address> {
|
||||
let index = funding_stream_address_index(height, network, receiver)?;
|
||||
let funding_streams = network.funding_streams(height);
|
||||
funding_streams
|
||||
.recipient(receiver)
|
||||
// TODO: Change return type to option and return None here instead of panicking
|
||||
.unwrap()
|
||||
.addresses()
|
||||
.get(index)
|
||||
// TODO: Change return type to option and return None here instead of panicking
|
||||
.unwrap()
|
||||
funding_streams.recipient(receiver)?.addresses().get(index)
|
||||
}
|
||||
|
||||
/// Return a human-readable name and a specification URL for the funding stream `receiver`.
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
//! Tests for funding streams.
|
||||
|
||||
use color_eyre::Report;
|
||||
use zebra_chain::parameters::{subsidy::FundingStreamReceiver, NetworkKind};
|
||||
use zebra_chain::parameters::{
|
||||
subsidy::FundingStreamReceiver,
|
||||
testnet::{
|
||||
self, ConfiguredActivationHeights, ConfiguredFundingStreamRecipient,
|
||||
ConfiguredFundingStreams,
|
||||
},
|
||||
NetworkKind,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
|
@ -45,7 +52,6 @@ fn test_funding_stream_values() -> Result<(), Report> {
|
|||
);
|
||||
|
||||
// funding stream period is ending
|
||||
// TODO: Check post-NU6 funding streams here as well.
|
||||
let range = network.pre_nu6_funding_streams().height_range();
|
||||
let end = range.end;
|
||||
let last = end - 1;
|
||||
|
@ -56,6 +62,57 @@ fn test_funding_stream_values() -> Result<(), Report> {
|
|||
);
|
||||
assert!(funding_stream_values(end, network)?.is_empty());
|
||||
|
||||
// TODO: Replace this with Mainnet once there's an NU6 activation height defined for Mainnet
|
||||
let network = testnet::Parameters::build()
|
||||
.with_activation_heights(ConfiguredActivationHeights {
|
||||
blossom: Some(Blossom.activation_height(network).unwrap().0),
|
||||
nu6: Some(POST_NU6_FUNDING_STREAMS_MAINNET.height_range().start.0),
|
||||
..Default::default()
|
||||
})
|
||||
.with_post_nu6_funding_streams(ConfiguredFundingStreams {
|
||||
// Start checking funding streams from block height 1
|
||||
height_range: Some(POST_NU6_FUNDING_STREAMS_MAINNET.height_range().clone()),
|
||||
// Use default post-NU6 recipients
|
||||
recipients: Some(
|
||||
POST_NU6_FUNDING_STREAMS_TESTNET
|
||||
.recipients()
|
||||
.iter()
|
||||
.map(|(&receiver, recipient)| ConfiguredFundingStreamRecipient {
|
||||
receiver,
|
||||
numerator: recipient.numerator(),
|
||||
addresses: Some(
|
||||
recipient
|
||||
.addresses()
|
||||
.iter()
|
||||
.map(|addr| addr.to_string())
|
||||
.collect(),
|
||||
),
|
||||
})
|
||||
.collect(),
|
||||
),
|
||||
})
|
||||
.to_network();
|
||||
|
||||
let mut hash_map = HashMap::new();
|
||||
hash_map.insert(
|
||||
FundingStreamReceiver::Deferred,
|
||||
Amount::try_from(18_750_000)?,
|
||||
);
|
||||
hash_map.insert(
|
||||
FundingStreamReceiver::MajorGrants,
|
||||
Amount::try_from(12_500_000)?,
|
||||
);
|
||||
|
||||
let nu6_height = Nu6.activation_height(&network).unwrap();
|
||||
|
||||
for height in [
|
||||
nu6_height,
|
||||
Height(nu6_height.0 + 1),
|
||||
Height(nu6_height.0 + 1),
|
||||
] {
|
||||
assert_eq!(funding_stream_values(height, &network).unwrap(), hash_map);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -118,10 +118,44 @@ pub fn output_amounts(transaction: &Transaction) -> HashSet<Amount<NonNegative>>
|
|||
.collect()
|
||||
}
|
||||
|
||||
/// Lockbox funding stream total input value for a block height.
|
||||
///
|
||||
/// Assumes a constant funding stream amount per block.
|
||||
// TODO: Cache the lockbox value balance in zebra-state (will be required for tracking lockbox
|
||||
// value balance after the Zcash Sustainability Fund ZIPs or after a ZIP for spending from the deferred pool)
|
||||
#[allow(dead_code)]
|
||||
fn lockbox_input_value(network: &Network, height: Height) -> Amount<NonNegative> {
|
||||
let Some(nu6_activation_height) = Nu6.activation_height(network) else {
|
||||
return Amount::zero();
|
||||
};
|
||||
|
||||
let &deferred_amount_per_block = funding_stream_values(nu6_activation_height, network)
|
||||
.expect("we always expect a funding stream hashmap response even if empty")
|
||||
.get(&FundingStreamReceiver::Deferred)
|
||||
.expect("we expect a lockbox funding stream after NU5");
|
||||
|
||||
let post_nu6_funding_stream_height_range = network.post_nu6_funding_streams().height_range();
|
||||
|
||||
// `min(height, last_height_with_deferred_pool_contribution) - (nu6_activation_height - 1)`,
|
||||
// We decrement NU6 activation height since it's an inclusive lower bound.
|
||||
// Funding stream height range end bound is not incremented since it's an exclusive end bound
|
||||
let num_blocks_with_lockbox_output = (height.0 + 1)
|
||||
.min(post_nu6_funding_stream_height_range.end.0)
|
||||
.checked_sub(post_nu6_funding_stream_height_range.start.0)
|
||||
.unwrap_or_default();
|
||||
|
||||
(deferred_amount_per_block * num_blocks_with_lockbox_output.into())
|
||||
.expect("lockbox input value should fit in Amount")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use color_eyre::Report;
|
||||
use zebra_chain::parameters::testnet::{
|
||||
self, ConfiguredActivationHeights, ConfiguredFundingStreamRecipient,
|
||||
ConfiguredFundingStreams,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn halving_test() -> Result<(), Report> {
|
||||
|
@ -391,4 +425,71 @@ mod test {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_lockbox_input_value() -> Result<(), Report> {
|
||||
let _init_guard = zebra_test::init();
|
||||
|
||||
let network = testnet::Parameters::build()
|
||||
.with_activation_heights(ConfiguredActivationHeights {
|
||||
blossom: Some(Blossom.activation_height(&Network::Mainnet).unwrap().0),
|
||||
nu6: Some(POST_NU6_FUNDING_STREAMS_MAINNET.height_range().start.0),
|
||||
..Default::default()
|
||||
})
|
||||
.with_post_nu6_funding_streams(ConfiguredFundingStreams {
|
||||
// Start checking funding streams from block height 1
|
||||
height_range: Some(POST_NU6_FUNDING_STREAMS_MAINNET.height_range().clone()),
|
||||
// Use default post-NU6 recipients
|
||||
recipients: Some(
|
||||
POST_NU6_FUNDING_STREAMS_TESTNET
|
||||
.recipients()
|
||||
.iter()
|
||||
.map(|(&receiver, recipient)| ConfiguredFundingStreamRecipient {
|
||||
receiver,
|
||||
numerator: recipient.numerator(),
|
||||
addresses: Some(
|
||||
recipient
|
||||
.addresses()
|
||||
.iter()
|
||||
.map(|addr| addr.to_string())
|
||||
.collect(),
|
||||
),
|
||||
})
|
||||
.collect(),
|
||||
),
|
||||
})
|
||||
.to_network();
|
||||
|
||||
let nu6_height = Nu6.activation_height(&network).unwrap();
|
||||
let post_nu6_funding_streams = network.post_nu6_funding_streams();
|
||||
let height_range = post_nu6_funding_streams.height_range();
|
||||
|
||||
let last_funding_stream_height = post_nu6_funding_streams
|
||||
.height_range()
|
||||
.end
|
||||
.previous()
|
||||
.expect("the previous height should be valid");
|
||||
|
||||
assert_eq!(
|
||||
Amount::<NonNegative>::zero(),
|
||||
lockbox_input_value(&network, Height::MIN)
|
||||
);
|
||||
|
||||
let expected_lockbox_value: Amount<NonNegative> = Amount::try_from(18_750_000)?;
|
||||
assert_eq!(
|
||||
expected_lockbox_value,
|
||||
lockbox_input_value(&network, nu6_height)
|
||||
);
|
||||
|
||||
let num_blocks_total = height_range.end.0 - height_range.start.0;
|
||||
let expected_input_per_block: Amount<NonNegative> = Amount::try_from(18_750_000)?;
|
||||
let expected_lockbox_value = (expected_input_per_block * num_blocks_total.into())?;
|
||||
|
||||
assert_eq!(
|
||||
expected_lockbox_value,
|
||||
lockbox_input_value(&network, last_funding_stream_height)
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -636,12 +636,7 @@ where
|
|||
//
|
||||
// Optional TODO:
|
||||
// - add `async changed()` method to ChainSyncStatus (like `ChainTip`)
|
||||
// TODO:
|
||||
// - Add a `disable_peers` field to `Network` to check instead of `disable_pow()` (#8361)
|
||||
// - Check the field in `sync_status` so it applies to the mempool as well.
|
||||
if !network.disable_pow() {
|
||||
check_synced_to_tip(&network, latest_chain_tip.clone(), sync_status.clone())?;
|
||||
}
|
||||
check_synced_to_tip(&network, latest_chain_tip.clone(), sync_status.clone())?;
|
||||
// TODO: return an error if we have no peers, like `zcashd` does,
|
||||
// and add a developer config that mines regardless of how many peers we have.
|
||||
// https://github.com/zcash/zcash/blob/6fdd9f1b81d3b228326c9826fa10696fc516444b/src/miner.cpp#L865-L880
|
||||
|
@ -1197,9 +1192,9 @@ where
|
|||
})?;
|
||||
let mut funding_streams: Vec<_> = funding_streams
|
||||
.iter()
|
||||
.map(|(receiver, value)| {
|
||||
let address = funding_stream_address(height, &network, *receiver);
|
||||
(*receiver, FundingStream::new(*receiver, *value, address))
|
||||
.filter_map(|(receiver, value)| {
|
||||
let address = funding_stream_address(height, &network, *receiver)?;
|
||||
Some((*receiver, FundingStream::new(*receiver, *value, address)))
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
|
|
@ -154,6 +154,7 @@ where
|
|||
// - State and syncer checks
|
||||
|
||||
/// Returns an error if Zebra is not synced to the consensus chain tip.
|
||||
/// Returns early with `Ok(())` if Proof-of-Work is disabled on the provided `network`.
|
||||
/// This error might be incorrect if the local clock is skewed.
|
||||
pub fn check_synced_to_tip<Tip, SyncStatus>(
|
||||
network: &Network,
|
||||
|
@ -164,6 +165,13 @@ where
|
|||
Tip: ChainTip + Clone + Send + Sync + 'static,
|
||||
SyncStatus: ChainSyncStatus + Clone + Send + Sync + 'static,
|
||||
{
|
||||
// TODO:
|
||||
// - Add a `disable_peers` field to `Network` to check instead of `disable_pow()` (#8361)
|
||||
// - Check the field in `sync_status` so it applies to the mempool as well.
|
||||
if network.disable_pow() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// The tip estimate may not be the same as the one coming from the state
|
||||
// but this is ok for an estimate
|
||||
let (estimated_distance_to_chain_tip, local_tip_height) = latest_chain_tip
|
||||
|
@ -376,11 +384,11 @@ pub fn standard_coinbase_outputs(
|
|||
(Amount<NonNegative>, &transparent::Address),
|
||||
> = funding_streams
|
||||
.into_iter()
|
||||
.map(|(receiver, amount)| {
|
||||
(
|
||||
.filter_map(|(receiver, amount)| {
|
||||
Some((
|
||||
receiver,
|
||||
(amount, funding_stream_address(height, network, receiver)),
|
||||
)
|
||||
(amount, funding_stream_address(height, network, receiver)?),
|
||||
))
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
|
|
@ -59,6 +59,11 @@ impl ProposalResponse {
|
|||
|
||||
ProposalResponse::Rejected(final_error.to_string())
|
||||
}
|
||||
|
||||
/// Returns true if self is [`ProposalResponse::Valid`]
|
||||
pub fn is_valid(&self) -> bool {
|
||||
matches!(self, Self::Valid)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ProposalResponse> for Response {
|
||||
|
|
|
@ -299,4 +299,5 @@ zebra-grpc = { path = "../zebra-grpc", version = "0.1.0-alpha.5" }
|
|||
zebra-utils = { path = "../zebra-utils", version = "1.0.0-beta.38" }
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tokio_unstable)'] }
|
||||
# TODO: Remove 'cfg(zcash_unstable, values("nu6"))' once it's no longer needed for NU6.
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tokio_unstable)', 'cfg(zcash_unstable, values("nu6"))'] }
|
||||
|
|
|
@ -3228,3 +3228,256 @@ async fn trusted_chain_sync_handles_forks_correctly() -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test successful block template submission as a block proposal or submission on a custom Testnet.
|
||||
///
|
||||
/// This test can be run locally with:
|
||||
/// `RUSTFLAGS='--cfg zcash_unstable="nu6"' cargo test --package zebrad --test acceptance --features getblocktemplate-rpcs -- nu6_funding_streams --exact --show-output`
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
#[cfg(all(feature = "getblocktemplate-rpcs", zcash_unstable = "nu6"))]
|
||||
async fn nu6_funding_streams() -> Result<()> {
|
||||
use zebra_chain::{
|
||||
chain_sync_status::MockSyncStatus,
|
||||
parameters::{
|
||||
subsidy::{FundingStreamReceiver, FUNDING_STREAM_MG_ADDRESSES_TESTNET},
|
||||
testnet::{
|
||||
self, ConfiguredActivationHeights, ConfiguredFundingStreamRecipient,
|
||||
ConfiguredFundingStreams,
|
||||
},
|
||||
NetworkUpgrade,
|
||||
},
|
||||
serialization::ZcashSerialize,
|
||||
work::difficulty::U256,
|
||||
};
|
||||
use zebra_network::address_book_peers::MockAddressBookPeers;
|
||||
use zebra_node_services::mempool;
|
||||
use zebra_rpc::methods::{
|
||||
get_block_template_rpcs::{
|
||||
get_block_template::{
|
||||
fetch_state_tip_and_local_time, generate_coinbase_and_roots,
|
||||
proposal_block_from_template, GetBlockTemplate, GetBlockTemplateRequestMode,
|
||||
},
|
||||
types::get_block_template,
|
||||
types::submit_block,
|
||||
},
|
||||
hex_data::HexData,
|
||||
GetBlockTemplateRpc, GetBlockTemplateRpcImpl,
|
||||
};
|
||||
use zebra_test::mock_service::MockService;
|
||||
let _init_guard = zebra_test::init();
|
||||
|
||||
tracing::info!("running nu6_funding_streams_and_coinbase_balance test");
|
||||
|
||||
let base_network_params = testnet::Parameters::build()
|
||||
// Regtest genesis hash
|
||||
.with_genesis_hash("029f11d80ef9765602235e1bc9727e3eb6ba20839319f761fee920d63401e327")
|
||||
.with_target_difficulty_limit(U256::from_big_endian(&[0x0f; 32]))
|
||||
.with_disable_pow(true)
|
||||
.with_slow_start_interval(Height::MIN)
|
||||
.with_activation_heights(ConfiguredActivationHeights {
|
||||
nu6: Some(1),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let network = base_network_params
|
||||
.clone()
|
||||
.with_post_nu6_funding_streams(ConfiguredFundingStreams {
|
||||
// Start checking funding streams from block height 1
|
||||
height_range: Some(Height(1)..Height(100)),
|
||||
// Use default post-NU6 recipients
|
||||
recipients: None,
|
||||
})
|
||||
.to_network();
|
||||
|
||||
tracing::info!("built configured Testnet, starting state service and block verifier");
|
||||
|
||||
let default_test_config = default_test_config(&network)?;
|
||||
let mining_config = default_test_config.mining;
|
||||
let miner_address = mining_config
|
||||
.miner_address
|
||||
.clone()
|
||||
.expect("hard-coded config should have a miner address");
|
||||
|
||||
let (state, read_state, latest_chain_tip, _chain_tip_change) =
|
||||
zebra_state::init_test_services(&network);
|
||||
|
||||
let (
|
||||
block_verifier_router,
|
||||
_transaction_verifier,
|
||||
_parameter_download_task_handle,
|
||||
_max_checkpoint_height,
|
||||
) = zebra_consensus::router::init(zebra_consensus::Config::default(), &network, state.clone())
|
||||
.await;
|
||||
|
||||
tracing::info!("started state service and block verifier, committing Regtest genesis block");
|
||||
|
||||
let genesis_hash = block_verifier_router
|
||||
.clone()
|
||||
.oneshot(zebra_consensus::Request::Commit(regtest_genesis_block()))
|
||||
.await
|
||||
.expect("should validate Regtest genesis block");
|
||||
|
||||
let mut mempool = MockService::build()
|
||||
.with_max_request_delay(Duration::from_secs(5))
|
||||
.for_unit_tests();
|
||||
let mut mock_sync_status = MockSyncStatus::default();
|
||||
mock_sync_status.set_is_close_to_tip(true);
|
||||
|
||||
let get_block_template_rpc_impl = GetBlockTemplateRpcImpl::new(
|
||||
&network,
|
||||
mining_config,
|
||||
mempool.clone(),
|
||||
read_state.clone(),
|
||||
latest_chain_tip,
|
||||
block_verifier_router,
|
||||
mock_sync_status,
|
||||
MockAddressBookPeers::default(),
|
||||
);
|
||||
|
||||
let make_mock_mempool_request_handler = || async move {
|
||||
mempool
|
||||
.expect_request(mempool::Request::FullTransactions)
|
||||
.await
|
||||
.respond(mempool::Response::FullTransactions {
|
||||
transactions: vec![],
|
||||
// tip hash needs to match chain info for long poll requests
|
||||
last_seen_tip_hash: genesis_hash,
|
||||
});
|
||||
};
|
||||
|
||||
let block_template_fut = get_block_template_rpc_impl.get_block_template(None);
|
||||
let mock_mempool_request_handler = make_mock_mempool_request_handler.clone()();
|
||||
let (block_template, _) = tokio::join!(block_template_fut, mock_mempool_request_handler);
|
||||
let get_block_template::Response::TemplateMode(block_template) =
|
||||
block_template.expect("unexpected error in getblocktemplate RPC call")
|
||||
else {
|
||||
panic!("this getblocktemplate call without parameters should return the `TemplateMode` variant of the response")
|
||||
};
|
||||
|
||||
let proposal_block = proposal_block_from_template(&block_template, None, NetworkUpgrade::Nu6)?;
|
||||
let hex_proposal_block = HexData(proposal_block.zcash_serialize_to_vec()?);
|
||||
|
||||
// Check that the block template is a valid block proposal
|
||||
let get_block_template::Response::ProposalMode(block_proposal_result) =
|
||||
get_block_template_rpc_impl
|
||||
.get_block_template(Some(get_block_template::JsonParameters {
|
||||
mode: GetBlockTemplateRequestMode::Proposal,
|
||||
data: Some(hex_proposal_block),
|
||||
..Default::default()
|
||||
}))
|
||||
.await?
|
||||
else {
|
||||
panic!(
|
||||
"this getblocktemplate call should return the `ProposalMode` variant of the response"
|
||||
)
|
||||
};
|
||||
|
||||
assert!(
|
||||
block_proposal_result.is_valid(),
|
||||
"block proposal should succeed"
|
||||
);
|
||||
|
||||
// Submit the same block
|
||||
let submit_block_response = get_block_template_rpc_impl
|
||||
.submit_block(HexData(proposal_block.zcash_serialize_to_vec()?), None)
|
||||
.await?;
|
||||
|
||||
assert_eq!(
|
||||
submit_block_response,
|
||||
submit_block::Response::Accepted,
|
||||
"valid block should be accepted"
|
||||
);
|
||||
|
||||
// Use an invalid coinbase transaction (with an output value greater than the `block_subsidy + miner_fees - expected_lockbox_funding_stream`)
|
||||
|
||||
let make_configured_recipients_with_lockbox_numerator = |numerator| {
|
||||
Some(vec![
|
||||
ConfiguredFundingStreamRecipient {
|
||||
receiver: FundingStreamReceiver::Deferred,
|
||||
numerator,
|
||||
addresses: None,
|
||||
},
|
||||
ConfiguredFundingStreamRecipient {
|
||||
receiver: FundingStreamReceiver::MajorGrants,
|
||||
numerator: 8,
|
||||
addresses: Some(
|
||||
FUNDING_STREAM_MG_ADDRESSES_TESTNET
|
||||
.map(ToString::to_string)
|
||||
.to_vec(),
|
||||
),
|
||||
},
|
||||
])
|
||||
};
|
||||
|
||||
// Gets the next block template
|
||||
let block_template_fut = get_block_template_rpc_impl.get_block_template(None);
|
||||
let mock_mempool_request_handler = make_mock_mempool_request_handler.clone()();
|
||||
let (block_template, _) = tokio::join!(block_template_fut, mock_mempool_request_handler);
|
||||
let get_block_template::Response::TemplateMode(block_template) =
|
||||
block_template.expect("unexpected error in getblocktemplate RPC call")
|
||||
else {
|
||||
panic!("this getblocktemplate call without parameters should return the `TemplateMode` variant of the response")
|
||||
};
|
||||
|
||||
let valid_original_block_template = block_template.clone();
|
||||
|
||||
let zebra_state::GetBlockTemplateChainInfo { history_tree, .. } =
|
||||
fetch_state_tip_and_local_time(read_state.clone()).await?;
|
||||
|
||||
let network = base_network_params
|
||||
.clone()
|
||||
.with_post_nu6_funding_streams(ConfiguredFundingStreams {
|
||||
height_range: Some(Height(1)..Height(100)),
|
||||
recipients: make_configured_recipients_with_lockbox_numerator(0),
|
||||
})
|
||||
.to_network();
|
||||
|
||||
let (coinbase_txn, default_roots) = generate_coinbase_and_roots(
|
||||
&network,
|
||||
Height(block_template.height),
|
||||
&miner_address,
|
||||
&[],
|
||||
history_tree.clone(),
|
||||
true,
|
||||
vec![],
|
||||
);
|
||||
|
||||
let block_template = GetBlockTemplate {
|
||||
coinbase_txn,
|
||||
block_commitments_hash: default_roots.block_commitments_hash,
|
||||
light_client_root_hash: default_roots.block_commitments_hash,
|
||||
final_sapling_root_hash: default_roots.block_commitments_hash,
|
||||
default_roots,
|
||||
..(*block_template)
|
||||
};
|
||||
|
||||
let proposal_block = proposal_block_from_template(&block_template, None, NetworkUpgrade::Nu6)?;
|
||||
|
||||
// Submit the invalid block with an excessive coinbase output value
|
||||
let submit_block_response = get_block_template_rpc_impl
|
||||
.submit_block(HexData(proposal_block.zcash_serialize_to_vec()?), None)
|
||||
.await?;
|
||||
|
||||
tracing::info!(?submit_block_response, "submitted invalid block");
|
||||
|
||||
assert_eq!(
|
||||
submit_block_response,
|
||||
submit_block::Response::ErrorResponse(submit_block::ErrorResponse::Rejected),
|
||||
"invalid block with excessive coinbase output value should be rejected"
|
||||
);
|
||||
|
||||
// Check that the original block template can be submitted successfully
|
||||
let proposal_block =
|
||||
proposal_block_from_template(&valid_original_block_template, None, NetworkUpgrade::Nu6)?;
|
||||
let submit_block_response = get_block_template_rpc_impl
|
||||
.submit_block(HexData(proposal_block.zcash_serialize_to_vec()?), None)
|
||||
.await?;
|
||||
|
||||
assert_eq!(
|
||||
submit_block_response,
|
||||
submit_block::Response::Accepted,
|
||||
"valid block should be accepted"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue