Skip to content

Commit

Permalink
Added transaction version ZFuture with the burn_amount field into the…
Browse files Browse the repository at this point in the history
… get_block_template RPC call.
  • Loading branch information
mariopil committed Oct 30, 2024
1 parent 76a5494 commit 8c0b74d
Show file tree
Hide file tree
Showing 10 changed files with 221 additions and 17 deletions.
113 changes: 113 additions & 0 deletions zebra-chain/src/transaction/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Item = (Amount<NonNegative>, transparent::Script)>,
extra_coinbase_data: Vec<u8>,
like_zcashd: bool,
burn_amount: Option<Amount<NonNegative>>,
) -> 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.
//
// <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
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.
//
// <https://zips.z.cash/protocol/protocol.pdf#subsidyconcepts>
//
// > 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).
//
// <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
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(
Expand Down
42 changes: 37 additions & 5 deletions zebra-rpc/src/methods/get_block_template_rpcs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -286,21 +286,28 @@ pub trait GetBlockTemplateRpc {
address: String,
) -> BoxFuture<Result<unified_address::Response>>;

#[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`.
///
/// zcashd reference: [`generate`](https://zcash.github.io/rpc/generate.html)
/// method: post
/// tags: generating
fn generate(&self, num_blocks: u32) -> BoxFuture<Result<Vec<GetBlockHash>>>;
#[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<Amount<NonNegative>>,
) -> BoxFuture<Result<Vec<GetBlockHash>>>;
}

/// RPC method implementations.
Expand Down Expand Up @@ -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,
Expand All @@ -890,6 +904,8 @@ where
mempool_txs,
debug_like_zcashd,
extra_coinbase_data.clone(),
#[cfg(zcash_unstable = "nsm")]
burn_amount,
)
.await;

Expand All @@ -902,7 +918,6 @@ where
);

// - After this point, the template only depends on the previously fetched data.

let response = GetBlockTemplate::new(
&network,
&miner_address,
Expand All @@ -912,6 +927,8 @@ where
submit_old,
debug_like_zcashd,
extra_coinbase_data,
#[cfg(zcash_unstable = "nsm")]
burn_amount,
);

Ok(response.into())
Expand Down Expand Up @@ -1406,7 +1423,12 @@ where
.boxed()
}

fn generate(&self, num_blocks: u32) -> BoxFuture<Result<Vec<GetBlockHash>>> {
fn generate(
&self,
num_blocks: u32,
//#[cfg(zcash_unstable = "nsm")]
burn_amount: Option<Amount<NonNegative>>,
) -> BoxFuture<Result<Vec<GetBlockHash>>> {
let rpc: GetBlockTemplateRpcImpl<
Mempool,
State,
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -297,6 +298,7 @@ pub fn generate_coinbase_and_roots(
history_tree: Arc<zebra_chain::history_tree::HistoryTree>,
like_zcashd: bool,
extra_coinbase_data: Vec<u8>,
#[cfg(zcash_unstable = "nsm")] burn_amount: Option<Amount<NonNegative>>,
) -> (TransactionTemplate<NegativeOrZero>, DefaultRoots) {
// Generate the coinbase transaction
let miner_fee = calculate_miner_fee(mempool_txs);
Expand All @@ -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
Expand Down Expand Up @@ -340,13 +344,57 @@ pub fn generate_coinbase_transaction(
miner_fee: Amount<NonNegative>,
like_zcashd: bool,
extra_coinbase_data: Vec<u8>,
#[cfg(zcash_unstable = "nsm")] burn_amount: Option<Amount<NonNegative>>,
) -> 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()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -231,6 +231,7 @@ impl GetBlockTemplate {
submit_old: Option<bool>,
like_zcashd: bool,
extra_coinbase_data: Vec<u8>,
#[cfg(zcash_unstable = "nsm")] burn_amount: Option<Amount<NonNegative>>,
) -> Self {
// Calculate the next block height.
let next_block_height =
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -89,6 +91,10 @@ pub struct JsonParameters {
/// currently unused.
#[serde(rename = "workid")]
pub _work_id: Option<String>,

/// The amount of money to be burned in a transaction [ZIP-233]
#[cfg(zcash_unstable = "nsm")]
pub burn_amount: Option<Amount<NonNegative>>,
}

impl JsonParameters {
Expand Down
8 changes: 7 additions & 1 deletion zebra-rpc/src/methods/get_block_template_rpcs/zip317.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -43,6 +43,7 @@ pub async fn select_mempool_transactions(
mempool_txs: Vec<VerifiedUnminedTx>,
like_zcashd: bool,
extra_coinbase_data: Vec<u8>,
#[cfg(zcash_unstable = "nsm")] burn_amount: Option<Amount<NonNegative>>,
) -> Vec<VerifiedUnminedTx> {
// Use a fake coinbase transaction to break the dependency between transaction
// selection, the miner fee, and the fee payment in the coinbase transaction.
Expand All @@ -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.
Expand Down Expand Up @@ -120,6 +123,7 @@ pub fn fake_coinbase_transaction(
miner_address: &transparent::Address,
like_zcashd: bool,
extra_coinbase_data: Vec<u8>,
#[cfg(zcash_unstable = "nsm")] burn_amount: Option<Amount<NonNegative>>,
) -> TransactionTemplate<NegativeOrZero> {
// Block heights are encoded as variable-length (script) and `u32` (lock time, expiry height).
// They can also change the `u32` consensus branch id.
Expand All @@ -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)
Expand Down
Loading

0 comments on commit 8c0b74d

Please sign in to comment.