From e703bd3512372d7c8f4b02f640f539a0551a88a0 Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Mon, 7 Feb 2022 23:20:08 -0300 Subject: [PATCH] docs: Transaction consensus rules: Coinbase rules (#3464) * refactor coinbase transaction consensus rules docs * add missing rule * nit Co-authored-by: teor Co-authored-by: teor Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- zebra-chain/src/transparent/serialize.rs | 15 ++++++++++++ zebra-consensus/src/block/check.rs | 10 ++++---- zebra-consensus/src/transaction/check.rs | 27 ++++++++++++++++------ zebra-state/src/service/check/utxo.rs | 17 ++++++++------ zebra-state/src/service/finalized_state.rs | 7 ++++-- 5 files changed, 56 insertions(+), 20 deletions(-) diff --git a/zebra-chain/src/transparent/serialize.rs b/zebra-chain/src/transparent/serialize.rs index 54bdc86b8..0a9d01915 100644 --- a/zebra-chain/src/transparent/serialize.rs +++ b/zebra-chain/src/transparent/serialize.rs @@ -65,6 +65,21 @@ impl ZcashDeserialize for OutPoint { /// /// The height may consume `0..=5` bytes at the stat of the coinbase data. /// The genesis block does not include an encoded coinbase height. +/// +/// # Consensus +/// +/// > 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. +/// > For height in the range {1..16}, the encoding is a single byte of value +/// > 0x50 height. Otherwise, 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). +/// +/// pub(crate) fn parse_coinbase_height( mut data: Vec, ) -> Result<(block::Height, CoinbaseData), SerializationError> { diff --git a/zebra-consensus/src/block/check.rs b/zebra-consensus/src/block/check.rs index 5449813a6..3635211c7 100644 --- a/zebra-consensus/src/block/check.rs +++ b/zebra-consensus/src/block/check.rs @@ -184,10 +184,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"); - // Consensus rule: 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. + // # Consensus + // + // > 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. + // // https://zips.z.cash/protocol/protocol.pdf#txnconsensus let left = (transparent_value_balance - sapling_value_balance - orchard_value_balance) .map_err(|_| SubsidyError::SumOverflow)?; diff --git a/zebra-consensus/src/transaction/check.rs b/zebra-consensus/src/transaction/check.rs index 86a0761c7..9ea9b245a 100644 --- a/zebra-consensus/src/transaction/check.rs +++ b/zebra-consensus/src/transaction/check.rs @@ -99,14 +99,20 @@ pub fn has_enough_orchard_flags(tx: &Transaction) -> Result<(), TransactionError /// Check that a coinbase transaction has no PrevOut inputs, JoinSplits, or spends. /// -/// A coinbase transaction MUST NOT have any transparent inputs, JoinSplit descriptions, -/// or Spend descriptions. +/// # Consensus /// -/// In a version 5 coinbase transaction, the enableSpendsOrchard flag MUST be 0. +/// > A coinbase transaction MUST NOT have any transparent inputs with non-null prevout fields, +/// > JoinSplit descriptions, or Spend descriptions. +/// +/// > [NU5 onward] In a version 5 coinbase transaction, the enableSpendsOrchard flag MUST be 0. /// /// This check only counts `PrevOut` transparent inputs. /// -/// https://zips.z.cash/protocol/protocol.pdf#txnencodingandconsensus +/// > [Pre-Heartwood] A coinbase transaction also MUST NOT have any Output descriptions. +/// +/// Zebra does not validate this last rule explicitly because we checkpoint until Canopy activation. +/// +/// pub fn coinbase_tx_no_prevout_joinsplit_spend(tx: &Transaction) -> Result<(), TransactionError> { if tx.has_valid_coinbase_transaction_inputs() { if tx.contains_prevout_input() { @@ -245,6 +251,8 @@ where /// /// This is used to validate coinbase transactions: /// +/// # Consensus +/// /// > [Heartwood onward] All Sapling and Orchard outputs in coinbase transactions MUST decrypt to a note /// > plaintext, i.e. the procedure in § 4.19.3 ‘Decryption using a Full Viewing Key ( Sapling and Orchard )’ on p. 67 /// > does not return ⊥, using a sequence of 32 zero bytes as the outgoing viewing key. (This implies that before @@ -255,7 +263,8 @@ where /// > according to the preceding rule MUST have note plaintext lead byte equal to 0x02. (This applies even during /// > the "grace period" specified in [ZIP-212].) /// -/// [3.10]: https://zips.z.cash/protocol/protocol.pdf#coinbasetransactions +/// +/// /// [ZIP-212]: https://zips.z.cash/zip-0212#consensus-rule-change-for-coinbase-transactions /// /// TODO: Currently, a 0x01 lead byte is allowed in the "grace period" mentioned since we're @@ -298,8 +307,12 @@ pub fn coinbase_expiry_height( // If Nu5 does not have a height, apply the pre-Nu5 rule. None => validate_expiry_height_max(expiry_height, true, block_height, coinbase), Some(activation_height) => { - // Consensus rule: from NU5 activation, the nExpiryHeight field of a - // coinbase transaction MUST be set equal to the block height. + // # Consensus + // + // > [NU5 onward] The nExpiryHeight field of a coinbase transaction MUST be equal + // > to its block height. + // + // https://zips.z.cash/protocol/protocol.pdf#txnconsensus if *block_height >= activation_height { match expiry_height { None => Err(TransactionError::CoinbaseExpiryBlockHeight { diff --git a/zebra-state/src/service/check/utxo.rs b/zebra-state/src/service/check/utxo.rs index 927aa4b70..17294a358 100644 --- a/zebra-state/src/service/check/utxo.rs +++ b/zebra-state/src/service/check/utxo.rs @@ -168,16 +168,19 @@ fn transparent_spend_chain_order( /// Check that `utxo` is spendable, based on the coinbase `spend_restriction`. /// -/// "A transaction with one or more transparent inputs from coinbase transactions -/// MUST have no transparent outputs (i.e.tx_out_count MUST be 0)." +/// # Consensus /// -/// "A transaction MUST NOT spend a transparent output of a coinbase transaction -/// from a block less than 100 blocks prior to the spend. +/// > A transaction with one or more transparent inputs from coinbase transactions +/// > MUST have no transparent outputs (i.e. tx_out_count MUST be 0). +/// > Inputs from coinbase transactions include Founders’ Reward outputs and +/// > funding stream outputs. /// -/// Note that transparent outputs of coinbase transactions include Founders’ -/// Reward outputs and transparent funding stream outputs." +/// > A transaction MUST NOT spend a transparent output of a coinbase transaction +/// > from a block less than 100 blocks prior to the spend. +/// > Note that transparent outputs of coinbase transactions include +/// > Founders’ Reward outputs and transparent funding stream outputs. /// -/// https://zips.z.cash/protocol/protocol.pdf#txnencodingandconsensus +/// pub fn transparent_coinbase_spend( outpoint: transparent::OutPoint, spend_restriction: transparent::CoinbaseSpendRestriction, diff --git a/zebra-state/src/service/finalized_state.rs b/zebra-state/src/service/finalized_state.rs index 34a7a999a..2cf011c89 100644 --- a/zebra-state/src/service/finalized_state.rs +++ b/zebra-state/src/service/finalized_state.rs @@ -344,8 +344,11 @@ impl FinalizedState { batch.zs_insert(height_by_hash, hash, height); batch.zs_insert(block_by_height, height, &block); - // "A transaction MUST NOT spend an output of the genesis block coinbase transaction. - // (There is one such zero-valued output, on each of Testnet and Mainnet .)" + // # Consensus + // + // > A transaction MUST NOT spend an output of the genesis block coinbase transaction. + // > (There is one such zero-valued output, on each of Testnet and Mainnet.) + // // https://zips.z.cash/protocol/protocol.pdf#txnconsensus if block.header.previous_block_hash == GENESIS_PREVIOUS_BLOCK_HASH { // Insert empty note commitment trees. Note that these can't be