diff --git a/zebra-chain/src/transaction/builder.rs b/zebra-chain/src/transaction/builder.rs index 37397353aab..227658ad479 100644 --- a/zebra-chain/src/transaction/builder.rs +++ b/zebra-chain/src/transaction/builder.rs @@ -9,6 +9,119 @@ use crate::{ }; impl Transaction { + /// Returns a new version zfuture coinbase transaction for `network` and `height`, + /// which contains the specified `outputs`. + #[cfg(zcash_unstable = "nsm")] + pub fn new_zfuture_coinbase( + network: &Network, + height: Height, + outputs: impl IntoIterator, transparent::Script)>, + extra_coinbase_data: Vec, + like_zcashd: bool, + burn_amount: Option>, + ) -> Transaction { + let mut extra_data = None; + let mut sequence = None; + + // `zcashd` includes an extra byte after the coinbase height in the coinbase data, + // and a sequence number of u32::MAX. + if like_zcashd { + extra_data = Some(vec![0x00]); + sequence = Some(u32::MAX); + } + + // Override like_zcashd if extra_coinbase_data was supplied + if !extra_coinbase_data.is_empty() { + extra_data = Some(extra_coinbase_data); + } + + // # Consensus + // + // These consensus rules apply to v5 coinbase transactions after NU5 activation: + // + // > If effectiveVersion ≥ 5 then this condition MUST hold: + // > tx_in_count > 0 or nSpendsSapling > 0 or + // > (nActionsOrchard > 0 and enableSpendsOrchard = 1). + // + // > A coinbase transaction for a block at block height greater than 0 MUST have + // > a script that, as its first item, encodes the block height height as follows. ... + // > let heightBytes be the signed little-endian representation of height, + // > using the minimum nonzero number of bytes such that the most significant byte + // > is < 0x80. The length of heightBytes MUST be in the range {1 .. 5}. + // > Then the encoding is the length of heightBytes encoded as one byte, + // > followed by heightBytes itself. This matches the encoding used by Bitcoin + // > in the implementation of [BIP-34] + // > (but the description here is to be considered normative). + // + // > A coinbase transaction script MUST have length in {2 .. 100} bytes. + // + // Zebra adds extra coinbase data if configured to do so. + // + // Since we're not using a lock time, any sequence number is valid here. + // See `Transaction::lock_time()` for the relevant consensus rules. + // + // + let inputs = vec![transparent::Input::new_coinbase( + height, extra_data, sequence, + )]; + + // > The block subsidy is composed of a miner subsidy and a series of funding streams. + // + // + // + // > The total value in zatoshi of transparent outputs from a coinbase transaction, + // > minus vbalanceSapling, minus vbalanceOrchard, MUST NOT be greater than + // > the value in zatoshi of block subsidy plus the transaction fees + // > paid by transactions in this block. + // + // > If effectiveVersion ≥ 5 then this condition MUST hold: + // > tx_out_count > 0 or nOutputsSapling > 0 or + // > (nActionsOrchard > 0 and enableOutputsOrchard = 1). + // + // + let outputs: Vec<_> = outputs + .into_iter() + .map(|(amount, lock_script)| transparent::Output::new_coinbase(amount, lock_script)) + .collect(); + assert!( + !outputs.is_empty(), + "invalid coinbase transaction: must have at least one output" + ); + + Transaction::ZFuture { + // > The transaction version number MUST be 4 or 5. ... + // > If the transaction version number is 5 then the version group ID + // > MUST be 0x26A7270A. + // > If effectiveVersion ≥ 5, the nConsensusBranchId field MUST match the consensus + // > branch ID used for SIGHASH transaction hashes, as specified in [ZIP-244]. + network_upgrade: NetworkUpgrade::current(network, height), + + // There is no documented consensus rule for the lock time field in coinbase + // transactions, so we just leave it unlocked. (We could also set it to `height`.) + lock_time: LockTime::unlocked(), + + // > The nExpiryHeight field of a coinbase transaction MUST be equal to its + // > block height. + expiry_height: height, + + inputs, + outputs, + + // Zebra does not support shielded coinbase yet. + // + // > In a version 5 coinbase transaction, the enableSpendsOrchard flag MUST be 0. + // > In a version 5 transaction, the reserved bits 2 .. 7 of the flagsOrchard field + // > MUST be zero. + // + // See the Zcash spec for additional shielded coinbase consensus rules. + sapling_shielded_data: None, + orchard_shielded_data: None, + + // > The NSM burn_amount field [ZIP-233] must be set. It can be set to 0. + burn_amount: burn_amount.unwrap_or(Amount::zero()), + } + } + /// Returns a new version 5 coinbase transaction for `network` and `height`, /// which contains the specified `outputs`. pub fn new_v5_coinbase( diff --git a/zebra-rpc/src/methods/get_block_template_rpcs.rs b/zebra-rpc/src/methods/get_block_template_rpcs.rs index 2d50552cfec..28d3c45d4e3 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs.rs @@ -286,13 +286,13 @@ pub trait GetBlockTemplateRpc { address: String, ) -> BoxFuture>; - #[rpc(name = "generate")] /// Mine blocks immediately. Returns the block hashes of the generated blocks. /// /// # Parameters /// /// - `num_blocks`: (numeric, required, example=1) Number of blocks to be generated. /// + /// - `burn_amount`: (numeric, optional) The amount of money to be burned in a transaction [ZIP-233] /// # Notes /// /// Only works if the network of the running zebrad process is `Regtest`. @@ -300,7 +300,14 @@ pub trait GetBlockTemplateRpc { /// zcashd reference: [`generate`](https://zcash.github.io/rpc/generate.html) /// method: post /// tags: generating - fn generate(&self, num_blocks: u32) -> BoxFuture>>; + #[rpc(name = "generate")] + fn generate( + &self, + num_blocks: u32, + // the burn_amount field should be currently hidden behind zcash_unstable flag, but it doesn't compile due to the rpc macro + //#[cfg(zcash_unstable = "nsm")] + burn_amount: Option>, + ) -> BoxFuture>>; } /// RPC method implementations. @@ -882,6 +889,13 @@ where "selecting transactions for the template from the mempool" ); + #[cfg(zcash_unstable = "nsm")] + let burn_amount = if let Some(params) = parameters { + params.burn_amount + } else { + None + }; + // Randomly select some mempool transactions. let mempool_txs = zip317::select_mempool_transactions( &network, @@ -890,6 +904,8 @@ where mempool_txs, debug_like_zcashd, extra_coinbase_data.clone(), + #[cfg(zcash_unstable = "nsm")] + burn_amount, ) .await; @@ -902,7 +918,6 @@ where ); // - After this point, the template only depends on the previously fetched data. - let response = GetBlockTemplate::new( &network, &miner_address, @@ -912,6 +927,8 @@ where submit_old, debug_like_zcashd, extra_coinbase_data, + #[cfg(zcash_unstable = "nsm")] + burn_amount, ); Ok(response.into()) @@ -1406,7 +1423,12 @@ where .boxed() } - fn generate(&self, num_blocks: u32) -> BoxFuture>> { + fn generate( + &self, + num_blocks: u32, + //#[cfg(zcash_unstable = "nsm")] + burn_amount: Option>, + ) -> BoxFuture>> { let rpc: GetBlockTemplateRpcImpl< Mempool, State, @@ -1427,8 +1449,18 @@ where } let mut block_hashes = Vec::new(); + #[cfg(not(zcash_unstable = "nsm"))] + let params = None; + #[cfg(zcash_unstable = "nsm")] + let params = Some(get_block_template::JsonParameters { + burn_amount, + ..Default::default() + }); for _ in 0..num_blocks { - let block_template = rpc.get_block_template(None).await.map_server_error()?; + let block_template = rpc + .get_block_template(params.clone()) + .await + .map_server_error()?; let get_block_template::Response::TemplateMode(block_template) = block_template else { diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/get_block_template.rs b/zebra-rpc/src/methods/get_block_template_rpcs/get_block_template.rs index 8e9578180be..71a5dd63012 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/get_block_template.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/get_block_template.rs @@ -289,6 +289,7 @@ where /// /// If `like_zcashd` is true, try to match the coinbase transactions generated by `zcashd` /// in the `getblocktemplate` RPC. +#[allow(clippy::too_many_arguments)] pub fn generate_coinbase_and_roots( network: &Network, block_template_height: Height, @@ -297,6 +298,7 @@ pub fn generate_coinbase_and_roots( history_tree: Arc, like_zcashd: bool, extra_coinbase_data: Vec, + #[cfg(zcash_unstable = "nsm")] burn_amount: Option>, ) -> (TransactionTemplate, DefaultRoots) { // Generate the coinbase transaction let miner_fee = calculate_miner_fee(mempool_txs); @@ -307,6 +309,8 @@ pub fn generate_coinbase_and_roots( miner_fee, like_zcashd, extra_coinbase_data, + #[cfg(zcash_unstable = "nsm")] + burn_amount, ); // Calculate block default roots @@ -340,13 +344,57 @@ pub fn generate_coinbase_transaction( miner_fee: Amount, like_zcashd: bool, extra_coinbase_data: Vec, + #[cfg(zcash_unstable = "nsm")] burn_amount: Option>, ) -> UnminedTx { let outputs = standard_coinbase_outputs(network, height, miner_address, miner_fee, like_zcashd); if like_zcashd { + #[cfg(zcash_unstable = "nsm")] + { + let network_upgrade = NetworkUpgrade::current(network, height); + if network_upgrade < NetworkUpgrade::ZFuture { + Transaction::new_v4_coinbase( + network, + height, + outputs, + like_zcashd, + extra_coinbase_data, + ) + .into() + } else { + Transaction::new_zfuture_coinbase( + network, + height, + outputs, + extra_coinbase_data, + like_zcashd, + burn_amount, + ) + .into() + } + } + #[cfg(not(zcash_unstable = "nsm"))] Transaction::new_v4_coinbase(network, height, outputs, like_zcashd, extra_coinbase_data) .into() } else { + #[cfg(zcash_unstable = "nsm")] + { + let network_upgrade = NetworkUpgrade::current(network, height); + if network_upgrade < NetworkUpgrade::ZFuture { + Transaction::new_v5_coinbase(network, height, outputs, extra_coinbase_data).into() + } else { + Transaction::new_zfuture_coinbase( + network, + height, + outputs, + extra_coinbase_data, + like_zcashd, + burn_amount, + ) + .into() + } + } + #[cfg(not(zcash_unstable = "nsm"))] Transaction::new_v5_coinbase(network, height, outputs, extra_coinbase_data).into() } } diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template.rs index d7c31e11a81..adae2fbefa8 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template.rs @@ -4,7 +4,7 @@ use std::fmt; use zebra_chain::{ - amount, + amount::{self, Amount, NonNegative}, block::{ChainHistoryBlockTxAuthCommitmentHash, MAX_BLOCK_BYTES, ZCASH_BLOCK_VERSION}, parameters::Network, serialization::DateTime32, @@ -231,6 +231,7 @@ impl GetBlockTemplate { submit_old: Option, like_zcashd: bool, extra_coinbase_data: Vec, + #[cfg(zcash_unstable = "nsm")] burn_amount: Option>, ) -> Self { // Calculate the next block height. let next_block_height = @@ -271,6 +272,8 @@ impl GetBlockTemplate { chain_tip_and_local_time.history_tree.clone(), like_zcashd, extra_coinbase_data, + #[cfg(zcash_unstable = "nsm")] + burn_amount, ); // Convert difficulty diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/parameters.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/parameters.rs index 73e1ed820ba..0230a50ce1d 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/parameters.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/parameters.rs @@ -1,5 +1,7 @@ //! Parameter types for the `getblocktemplate` RPC. +use zebra_chain::amount::{Amount, NonNegative}; + use crate::methods::{get_block_template_rpcs::types::long_poll::LongPollId, hex_data::HexData}; /// Defines whether the RPC method should generate a block template or attempt to validate a block proposal. @@ -89,6 +91,10 @@ pub struct JsonParameters { /// currently unused. #[serde(rename = "workid")] pub _work_id: Option, + + /// The amount of money to be burned in a transaction [ZIP-233] + #[cfg(zcash_unstable = "nsm")] + pub burn_amount: Option>, } impl JsonParameters { diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/zip317.rs b/zebra-rpc/src/methods/get_block_template_rpcs/zip317.rs index 3f0979dc266..3563485d284 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/zip317.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/zip317.rs @@ -12,7 +12,7 @@ use rand::{ }; use zebra_chain::{ - amount::NegativeOrZero, + amount::{Amount, NegativeOrZero, NonNegative}, block::{Height, MAX_BLOCK_BYTES}, parameters::Network, transaction::{zip317::BLOCK_UNPAID_ACTION_LIMIT, VerifiedUnminedTx}, @@ -43,6 +43,7 @@ pub async fn select_mempool_transactions( mempool_txs: Vec, like_zcashd: bool, extra_coinbase_data: Vec, + #[cfg(zcash_unstable = "nsm")] burn_amount: Option>, ) -> Vec { // Use a fake coinbase transaction to break the dependency between transaction // selection, the miner fee, and the fee payment in the coinbase transaction. @@ -52,6 +53,8 @@ pub async fn select_mempool_transactions( miner_address, like_zcashd, extra_coinbase_data, + #[cfg(zcash_unstable = "nsm")] + burn_amount, ); // Setup the transaction lists. @@ -120,6 +123,7 @@ pub fn fake_coinbase_transaction( miner_address: &transparent::Address, like_zcashd: bool, extra_coinbase_data: Vec, + #[cfg(zcash_unstable = "nsm")] burn_amount: Option>, ) -> TransactionTemplate { // Block heights are encoded as variable-length (script) and `u32` (lock time, expiry height). // They can also change the `u32` consensus branch id. @@ -139,6 +143,8 @@ pub fn fake_coinbase_transaction( miner_fee, like_zcashd, extra_coinbase_data, + #[cfg(zcash_unstable = "nsm")] + burn_amount, ); TransactionTemplate::from_coinbase(&coinbase_tx, miner_fee) diff --git a/zebra-rpc/src/methods/tests/snapshots/get_blockchain_info@mainnet_10.snap b/zebra-rpc/src/methods/tests/snapshots/get_blockchain_info@mainnet_10.snap index e4d64069482..8932444dcb0 100644 --- a/zebra-rpc/src/methods/tests/snapshots/get_blockchain_info@mainnet_10.snap +++ b/zebra-rpc/src/methods/tests/snapshots/get_blockchain_info@mainnet_10.snap @@ -69,11 +69,6 @@ expression: info "name": "NU6", "activationheight": 2820000, "status": "pending" - }, - "ffffffff": { - "name": "ZFuture", - "activationheight": 3000000, - "status": "pending" } }, "consensus": { diff --git a/zebra-rpc/src/methods/tests/snapshots/get_blockchain_info@testnet_10.snap b/zebra-rpc/src/methods/tests/snapshots/get_blockchain_info@testnet_10.snap index fbe49412e53..3bea6c01509 100644 --- a/zebra-rpc/src/methods/tests/snapshots/get_blockchain_info@testnet_10.snap +++ b/zebra-rpc/src/methods/tests/snapshots/get_blockchain_info@testnet_10.snap @@ -69,11 +69,6 @@ expression: info "name": "NU6", "activationheight": 2976000, "status": "pending" - }, - "ffffffff": { - "name": "ZFuture", - "activationheight": 3000000, - "status": "pending" } }, "consensus": { diff --git a/zebrad/src/components/miner.rs b/zebrad/src/components/miner.rs index cb32cc91981..d9240abe71f 100644 --- a/zebrad/src/components/miner.rs +++ b/zebrad/src/components/miner.rs @@ -258,6 +258,8 @@ where capabilities: vec![LongPoll, CoinbaseTxn], long_poll_id: None, _work_id: None, + #[cfg(zcash_unstable = "nsm")] + burn_amount: None, }; // Shut down the task when all the template receivers are dropped, or Zebra shuts down. diff --git a/zebrad/tests/acceptance.rs b/zebrad/tests/acceptance.rs index cd3572ce3f2..5777437b98e 100644 --- a/zebrad/tests/acceptance.rs +++ b/zebrad/tests/acceptance.rs @@ -3448,6 +3448,8 @@ async fn nu6_funding_streams_and_coinbase_balance() -> Result<()> { history_tree.clone(), true, vec![], + #[cfg(zcash_unstable = "nsm")] + None, ); let block_template = GetBlockTemplate { @@ -3491,6 +3493,8 @@ async fn nu6_funding_streams_and_coinbase_balance() -> Result<()> { history_tree.clone(), true, vec![], + #[cfg(zcash_unstable = "nsm")] + None, ); let block_template = GetBlockTemplate {