docs: Transaction consensus rules: Size rules (#3461)
* refactor transaction size consensus rules * quote mssing consensus rule * nit Co-authored-by: teor <teor@riseup.net> * move consensus rule doc Co-authored-by: teor <teor@riseup.net> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
This commit is contained in:
parent
fc3cba24f8
commit
29ad801a35
|
|
@ -183,8 +183,12 @@ impl TrustedPreallocate for Action {
|
||||||
// and the signature is required,
|
// and the signature is required,
|
||||||
// a valid max allocation can never exceed this size
|
// a valid max allocation can never exceed this size
|
||||||
const MAX: u64 = (MAX_BLOCK_BYTES - 1) / AUTHORIZED_ACTION_SIZE;
|
const MAX: u64 = (MAX_BLOCK_BYTES - 1) / AUTHORIZED_ACTION_SIZE;
|
||||||
|
// # Consensus
|
||||||
|
//
|
||||||
// > [NU5 onward] nSpendsSapling, nOutputsSapling, and nActionsOrchard MUST all be less than 2^16.
|
// > [NU5 onward] nSpendsSapling, nOutputsSapling, and nActionsOrchard MUST all be less than 2^16.
|
||||||
// https://zips.z.cash/protocol/protocol.pdf#txnencodingandconsensus
|
//
|
||||||
|
// https://zips.z.cash/protocol/protocol.pdf#txnconsensus
|
||||||
|
//
|
||||||
// This acts as nActionsOrchard and is therefore subject to the rule.
|
// This acts as nActionsOrchard and is therefore subject to the rule.
|
||||||
// The maximum value is actually smaller due to the block size limit,
|
// The maximum value is actually smaller due to the block size limit,
|
||||||
// but we ensure the 2^16 limit with a static assertion.
|
// but we ensure the 2^16 limit with a static assertion.
|
||||||
|
|
@ -206,13 +210,15 @@ bitflags! {
|
||||||
/// The spend and output flags are passed to the `Halo2Proof` verifier, which verifies
|
/// The spend and output flags are passed to the `Halo2Proof` verifier, which verifies
|
||||||
/// the relevant note spending and creation consensus rules.
|
/// the relevant note spending and creation consensus rules.
|
||||||
///
|
///
|
||||||
/// Consensus rules:
|
/// # Consensus
|
||||||
|
///
|
||||||
|
/// > [NU5 onward] In a version 5 transaction, the reserved bits 2..7 of the flagsOrchard
|
||||||
|
/// > field MUST be zero.
|
||||||
|
///
|
||||||
|
/// <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
|
||||||
///
|
///
|
||||||
/// - "In a version 5 transaction, the reserved bits 2..7 of the flagsOrchard field MUST be zero."
|
|
||||||
/// ([`bitflags`](https://docs.rs/bitflags/1.2.1/bitflags/index.html) restricts its values to the
|
/// ([`bitflags`](https://docs.rs/bitflags/1.2.1/bitflags/index.html) restricts its values to the
|
||||||
/// set of valid flags)
|
/// set of valid flags)
|
||||||
/// - "In a version 5 coinbase transaction, the enableSpendsOrchard flag MUST be 0."
|
|
||||||
/// (Checked in zebra-consensus)
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
pub struct Flags: u8 {
|
pub struct Flags: u8 {
|
||||||
/// Enable spending non-zero valued Orchard notes.
|
/// Enable spending non-zero valued Orchard notes.
|
||||||
|
|
|
||||||
|
|
@ -196,8 +196,12 @@ impl TrustedPreallocate for OutputInTransactionV4 {
|
||||||
// Since a serialized Vec<Output> uses at least one byte for its length,
|
// Since a serialized Vec<Output> uses at least one byte for its length,
|
||||||
// the max allocation can never exceed (MAX_BLOCK_BYTES - 1) / OUTPUT_SIZE
|
// the max allocation can never exceed (MAX_BLOCK_BYTES - 1) / OUTPUT_SIZE
|
||||||
const MAX: u64 = (MAX_BLOCK_BYTES - 1) / OUTPUT_SIZE;
|
const MAX: u64 = (MAX_BLOCK_BYTES - 1) / OUTPUT_SIZE;
|
||||||
|
// # Consensus
|
||||||
|
//
|
||||||
// > [NU5 onward] nSpendsSapling, nOutputsSapling, and nActionsOrchard MUST all be less than 2^16.
|
// > [NU5 onward] nSpendsSapling, nOutputsSapling, and nActionsOrchard MUST all be less than 2^16.
|
||||||
// https://zips.z.cash/protocol/protocol.pdf#txnencodingandconsensus
|
//
|
||||||
|
// https://zips.z.cash/protocol/protocol.pdf#txnconsensus
|
||||||
|
//
|
||||||
// This acts as nOutputsSapling and is therefore subject to the rule.
|
// This acts as nOutputsSapling and is therefore subject to the rule.
|
||||||
// The maximum value is actually smaller due to the block size limit,
|
// The maximum value is actually smaller due to the block size limit,
|
||||||
// but we ensure the 2^16 limit with a static assertion.
|
// but we ensure the 2^16 limit with a static assertion.
|
||||||
|
|
|
||||||
|
|
@ -90,21 +90,6 @@ where
|
||||||
AnchorV: AnchorVariant + Clone,
|
AnchorV: AnchorVariant + Clone,
|
||||||
{
|
{
|
||||||
/// The net value of Sapling spend transfers minus output transfers.
|
/// The net value of Sapling spend transfers minus output transfers.
|
||||||
///
|
|
||||||
/// [`ShieldedData`] validates this [value balance consensus
|
|
||||||
/// rule](https://zips.z.cash/protocol/nu5.pdf#txnencodingandconsensus):
|
|
||||||
///
|
|
||||||
/// "If effectiveVersion = 4 and there are no Spend descriptions or Output
|
|
||||||
/// descriptions, then valueBalanceSapling MUST be 0."
|
|
||||||
///
|
|
||||||
/// During deserialization, this rule is checked when there are no spends and
|
|
||||||
/// no outputs.
|
|
||||||
///
|
|
||||||
/// During serialization, this rule is structurally validated by [`ShieldedData`].
|
|
||||||
/// `value_balance` is a field in [`ShieldedData`], which must have at least
|
|
||||||
/// one spend or output in its `transfers` field. If [`ShieldedData`] is `None`
|
|
||||||
/// then there can not possibly be any spends or outputs, and the
|
|
||||||
/// `value_balance` is always serialized as zero.
|
|
||||||
pub value_balance: Amount,
|
pub value_balance: Amount,
|
||||||
|
|
||||||
/// A bundle of spends and outputs, containing at least one spend or
|
/// A bundle of spends and outputs, containing at least one spend or
|
||||||
|
|
|
||||||
|
|
@ -323,8 +323,12 @@ impl TrustedPreallocate for SpendPrefixInTransactionV5 {
|
||||||
// and the associated fields are required,
|
// and the associated fields are required,
|
||||||
// a valid max allocation can never exceed this size
|
// a valid max allocation can never exceed this size
|
||||||
const MAX: u64 = (MAX_BLOCK_BYTES - 1) / SHARED_ANCHOR_SPEND_SIZE;
|
const MAX: u64 = (MAX_BLOCK_BYTES - 1) / SHARED_ANCHOR_SPEND_SIZE;
|
||||||
|
// # Consensus
|
||||||
|
//
|
||||||
// > [NU5 onward] nSpendsSapling, nOutputsSapling, and nActionsOrchard MUST all be less than 2^16.
|
// > [NU5 onward] nSpendsSapling, nOutputsSapling, and nActionsOrchard MUST all be less than 2^16.
|
||||||
// https://zips.z.cash/protocol/protocol.pdf#txnencodingandconsensus
|
//
|
||||||
|
// https://zips.z.cash/protocol/protocol.pdf#txnconsensus
|
||||||
|
//
|
||||||
// This acts as nSpendsSapling and is therefore subject to the rule.
|
// This acts as nSpendsSapling and is therefore subject to the rule.
|
||||||
// The maximum value is actually smaller due to the block size limit,
|
// The maximum value is actually smaller due to the block size limit,
|
||||||
// but we ensure the 2^16 limit with a static assertion.
|
// but we ensure the 2^16 limit with a static assertion.
|
||||||
|
|
|
||||||
|
|
@ -223,14 +223,6 @@ impl Transaction {
|
||||||
// other properties
|
// other properties
|
||||||
|
|
||||||
/// Does this transaction have transparent or shielded inputs?
|
/// Does this transaction have transparent or shielded inputs?
|
||||||
///
|
|
||||||
/// "[Sapling onward] If effectiveVersion < 5, then at least one of tx_in_count,
|
|
||||||
/// nSpendsSapling, and nJoinSplit MUST be nonzero.
|
|
||||||
///
|
|
||||||
/// [NU5 onward] If effectiveVersion ≥ 5 then this condition MUST hold:
|
|
||||||
/// tx_in_count > 0 or nSpendsSapling > 0 or (nActionsOrchard > 0 and enableSpendsOrchard = 1)."
|
|
||||||
///
|
|
||||||
/// https://zips.z.cash/protocol/protocol.pdf#txnconsensus
|
|
||||||
pub fn has_transparent_or_shielded_inputs(&self) -> bool {
|
pub fn has_transparent_or_shielded_inputs(&self) -> bool {
|
||||||
!self.inputs().is_empty() || self.has_shielded_inputs()
|
!self.inputs().is_empty() || self.has_shielded_inputs()
|
||||||
}
|
}
|
||||||
|
|
@ -249,14 +241,6 @@ impl Transaction {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Does this transaction have transparent or shielded outputs?
|
/// Does this transaction have transparent or shielded outputs?
|
||||||
///
|
|
||||||
/// "[Sapling onward] If effectiveVersion < 5, then at least one of tx_out_count,
|
|
||||||
/// nOutputsSapling, and nJoinSplit MUST be nonzero.
|
|
||||||
///
|
|
||||||
/// [NU5 onward] 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
|
|
||||||
pub fn has_transparent_or_shielded_outputs(&self) -> bool {
|
pub fn has_transparent_or_shielded_outputs(&self) -> bool {
|
||||||
!self.outputs().is_empty() || self.has_shielded_outputs()
|
!self.outputs().is_empty() || self.has_shielded_outputs()
|
||||||
}
|
}
|
||||||
|
|
@ -275,11 +259,6 @@ impl Transaction {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Does this transaction has at least one flag when we have at least one orchard action?
|
/// Does this transaction has at least one flag when we have at least one orchard action?
|
||||||
///
|
|
||||||
/// [NU5 onward] If effectiveVersion >= 5 and nActionsOrchard > 0, then at least one
|
|
||||||
/// of enableSpendsOrchard and enableOutputsOrchard MUST be 1.
|
|
||||||
///
|
|
||||||
/// https://zips.z.cash/protocol/protocol.pdf#txnconsensus
|
|
||||||
pub fn has_enough_orchard_flags(&self) -> bool {
|
pub fn has_enough_orchard_flags(&self) -> bool {
|
||||||
if self.version() < 5 || self.orchard_actions().count() == 0 {
|
if self.version() < 5 || self.orchard_actions().count() == 0 {
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -567,6 +567,17 @@ impl ZcashSerialize for Transaction {
|
||||||
|
|
||||||
impl ZcashDeserialize for Transaction {
|
impl ZcashDeserialize for Transaction {
|
||||||
fn zcash_deserialize<R: io::Read>(reader: R) -> Result<Self, SerializationError> {
|
fn zcash_deserialize<R: io::Read>(reader: R) -> Result<Self, SerializationError> {
|
||||||
|
// # Consensus
|
||||||
|
//
|
||||||
|
// > [Pre-Sapling] The encoded size of the transaction MUST be less than or
|
||||||
|
// > equal to 100000 bytes.
|
||||||
|
//
|
||||||
|
// https://zips.z.cash/protocol/protocol.pdf#txnconsensus
|
||||||
|
//
|
||||||
|
// Zebra does not verify this rule because we checkpoint up to Canopy blocks, but:
|
||||||
|
// Since transactions must get mined into a block to be useful,
|
||||||
|
// we reject transactions that are larger than blocks.
|
||||||
|
//
|
||||||
// If the limit is reached, we'll get an UnexpectedEof error.
|
// If the limit is reached, we'll get an UnexpectedEof error.
|
||||||
let mut limited_reader = reader.take(MAX_BLOCK_BYTES);
|
let mut limited_reader = reader.take(MAX_BLOCK_BYTES);
|
||||||
|
|
||||||
|
|
@ -687,9 +698,12 @@ impl ZcashDeserialize for Transaction {
|
||||||
outputs: shielded_outputs.try_into().expect("checked for outputs"),
|
outputs: shielded_outputs.try_into().expect("checked for outputs"),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// There are no shielded outputs and no shielded spends, so the value balance
|
// # Consensus
|
||||||
// MUST be zero:
|
//
|
||||||
// https://zips.z.cash/protocol/protocol.pdf#txnencodingandconsensus
|
// > [Sapling onward] If effectiveVersion = 4 and there are no Spend
|
||||||
|
// > descriptions or Output descriptions, then valueBalanceSapling MUST be 0.
|
||||||
|
//
|
||||||
|
// https://zips.z.cash/protocol/protocol.pdf#txnconsensus
|
||||||
if value_balance != 0 {
|
if value_balance != 0 {
|
||||||
return Err(SerializationError::BadTransactionBalance);
|
return Err(SerializationError::BadTransactionBalance);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,19 +60,27 @@ pub fn lock_time_has_passed(
|
||||||
|
|
||||||
/// Checks that the transaction has inputs and outputs.
|
/// Checks that the transaction has inputs and outputs.
|
||||||
///
|
///
|
||||||
|
/// # Consensus
|
||||||
|
///
|
||||||
/// For `Transaction::V4`:
|
/// For `Transaction::V4`:
|
||||||
/// * At least one of `tx_in_count`, `nSpendsSapling`, and `nJoinSplit` MUST be non-zero.
|
///
|
||||||
/// * At least one of `tx_out_count`, `nOutputsSapling`, and `nJoinSplit` MUST be non-zero.
|
/// > [Sapling onward] If effectiveVersion < 5, then at least one of
|
||||||
|
/// > tx_in_count, nSpendsSapling, and nJoinSplit MUST be nonzero.
|
||||||
|
///
|
||||||
|
/// > [Sapling onward] If effectiveVersion < 5, then at least one of
|
||||||
|
/// > tx_out_count, nOutputsSapling, and nJoinSplit MUST be nonzero.
|
||||||
///
|
///
|
||||||
/// For `Transaction::V5`:
|
/// For `Transaction::V5`:
|
||||||
/// * This condition must hold: `tx_in_count` > 0 or `nSpendsSapling` > 0 or
|
///
|
||||||
/// (`nActionsOrchard` > 0 and `enableSpendsOrchard` = 1)
|
/// > [NU5 onward] If effectiveVersion >= 5 then this condition MUST hold:
|
||||||
/// * This condition must hold: `tx_out_count` > 0 or `nOutputsSapling` > 0 or
|
/// > tx_in_count > 0 or nSpendsSapling > 0 or (nActionsOrchard > 0 and enableSpendsOrchard = 1).
|
||||||
/// (`nActionsOrchard` > 0 and `enableOutputsOrchard` = 1)
|
///
|
||||||
|
/// > [NU5 onward] 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>
|
||||||
///
|
///
|
||||||
/// This check counts both `Coinbase` and `PrevOut` transparent inputs.
|
/// This check counts both `Coinbase` and `PrevOut` transparent inputs.
|
||||||
///
|
|
||||||
/// https://zips.z.cash/protocol/protocol.pdf#txnencodingandconsensus
|
|
||||||
pub fn has_inputs_and_outputs(tx: &Transaction) -> Result<(), TransactionError> {
|
pub fn has_inputs_and_outputs(tx: &Transaction) -> Result<(), TransactionError> {
|
||||||
if !tx.has_transparent_or_shielded_inputs() {
|
if !tx.has_transparent_or_shielded_inputs() {
|
||||||
Err(TransactionError::NoInputs)
|
Err(TransactionError::NoInputs)
|
||||||
|
|
@ -85,11 +93,13 @@ pub fn has_inputs_and_outputs(tx: &Transaction) -> Result<(), TransactionError>
|
||||||
|
|
||||||
/// Checks that the transaction has enough orchard flags.
|
/// Checks that the transaction has enough orchard flags.
|
||||||
///
|
///
|
||||||
/// For `Transaction::V5` only:
|
/// # Consensus
|
||||||
/// * If `orchard_actions_count` > 0 then at least one of
|
|
||||||
/// `ENABLE_SPENDS|ENABLE_OUTPUTS` must be active.
|
|
||||||
///
|
///
|
||||||
/// https://zips.z.cash/protocol/protocol.pdf#txnencodingandconsensus
|
/// For `Transaction::V5` only:
|
||||||
|
///
|
||||||
|
/// > [NU5 onward] If effectiveVersion >= 5 and nActionsOrchard > 0, then at least one of enableSpendsOrchard and enableOutputsOrchard MUST be 1.
|
||||||
|
///
|
||||||
|
/// <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
|
||||||
pub fn has_enough_orchard_flags(tx: &Transaction) -> Result<(), TransactionError> {
|
pub fn has_enough_orchard_flags(tx: &Transaction) -> Result<(), TransactionError> {
|
||||||
if !tx.has_enough_orchard_flags() {
|
if !tx.has_enough_orchard_flags() {
|
||||||
return Err(TransactionError::NotEnoughFlags);
|
return Err(TransactionError::NotEnoughFlags);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue