ZIP-211: Validate Disabling Addition of New Value to the Sprout Value Pool (#2399)
* add disabled sprout pool check * change method name * change error name * fix typo * make the success test case in other tx than the coinbase * use new `height` method instead of deriving `PartialOrd` in `NetworkUpgrade` * move check of network upgrade into function, rename, docs * increase test coverage * fix comment
This commit is contained in:
parent
515dc4bf5c
commit
e4ab01dde0
|
|
@ -22,7 +22,7 @@ pub use sapling::FieldNotPresent;
|
||||||
pub use sighash::HashType;
|
pub use sighash::HashType;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
block, orchard,
|
amount, block, orchard,
|
||||||
parameters::NetworkUpgrade,
|
parameters::NetworkUpgrade,
|
||||||
primitives::{Bctv14Proof, Groth16Proof},
|
primitives::{Bctv14Proof, Groth16Proof},
|
||||||
sapling, sprout, transparent,
|
sapling, sprout, transparent,
|
||||||
|
|
@ -294,6 +294,54 @@ impl Transaction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the `vpub_old` fields from `JoinSplit`s in this transaction, regardless of version.
|
||||||
|
///
|
||||||
|
/// This value is removed from the transparent value pool of this transaction, and added to the
|
||||||
|
/// sprout value pool.
|
||||||
|
pub fn sprout_pool_added_values(
|
||||||
|
&self,
|
||||||
|
) -> Box<dyn Iterator<Item = &amount::Amount<amount::NonNegative>> + '_> {
|
||||||
|
match self {
|
||||||
|
// JoinSplits with Bctv14 Proofs
|
||||||
|
Transaction::V2 {
|
||||||
|
joinsplit_data: Some(joinsplit_data),
|
||||||
|
..
|
||||||
|
}
|
||||||
|
| Transaction::V3 {
|
||||||
|
joinsplit_data: Some(joinsplit_data),
|
||||||
|
..
|
||||||
|
} => Box::new(
|
||||||
|
joinsplit_data
|
||||||
|
.joinsplits()
|
||||||
|
.map(|joinsplit| &joinsplit.vpub_old),
|
||||||
|
),
|
||||||
|
// JoinSplits with Groth Proofs
|
||||||
|
Transaction::V4 {
|
||||||
|
joinsplit_data: Some(joinsplit_data),
|
||||||
|
..
|
||||||
|
} => Box::new(
|
||||||
|
joinsplit_data
|
||||||
|
.joinsplits()
|
||||||
|
.map(|joinsplit| &joinsplit.vpub_old),
|
||||||
|
),
|
||||||
|
// No JoinSplits
|
||||||
|
Transaction::V1 { .. }
|
||||||
|
| Transaction::V2 {
|
||||||
|
joinsplit_data: None,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
| Transaction::V3 {
|
||||||
|
joinsplit_data: None,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
| Transaction::V4 {
|
||||||
|
joinsplit_data: None,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
| Transaction::V5 { .. } => Box::new(std::iter::empty()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Access the sprout::Nullifiers in this transaction, regardless of version.
|
/// Access the sprout::Nullifiers in this transaction, regardless of version.
|
||||||
pub fn sprout_nullifiers(&self) -> Box<dyn Iterator<Item = &sprout::Nullifier> + '_> {
|
pub fn sprout_nullifiers(&self) -> Box<dyn Iterator<Item = &sprout::Nullifier> + '_> {
|
||||||
// This function returns a boxed iterator because the different
|
// This function returns a boxed iterator because the different
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,9 @@ pub enum TransactionError {
|
||||||
// temporary error type until #1186 is fixed
|
// temporary error type until #1186 is fixed
|
||||||
#[error("Downcast from BoxError to redjubjub::Error failed")]
|
#[error("Downcast from BoxError to redjubjub::Error failed")]
|
||||||
InternalDowncastError(String),
|
InternalDowncastError(String),
|
||||||
|
|
||||||
|
#[error("adding to the sprout pool is disabled after Canopy")]
|
||||||
|
DisabledAddToSproutPool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<BoxError> for TransactionError {
|
impl From<BoxError> for TransactionError {
|
||||||
|
|
|
||||||
|
|
@ -174,6 +174,10 @@ where
|
||||||
check::coinbase_tx_no_prevout_joinsplit_spend(&tx)?;
|
check::coinbase_tx_no_prevout_joinsplit_spend(&tx)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [Canopy onward]: `vpub_old` MUST be zero.
|
||||||
|
// https://zips.z.cash/protocol/protocol.pdf#joinsplitdesc
|
||||||
|
check::disabled_add_to_sprout_pool(&tx, req.height(), network)?;
|
||||||
|
|
||||||
// "The consensus rules applied to valueBalance, vShieldedOutput, and bindingSig
|
// "The consensus rules applied to valueBalance, vShieldedOutput, and bindingSig
|
||||||
// in non-coinbase transactions MUST also be applied to coinbase transactions."
|
// in non-coinbase transactions MUST also be applied to coinbase transactions."
|
||||||
//
|
//
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,18 @@
|
||||||
//! Code in this file can freely assume that no pre-V4 transactions are present.
|
//! Code in this file can freely assume that no pre-V4 transactions are present.
|
||||||
|
|
||||||
use zebra_chain::{
|
use zebra_chain::{
|
||||||
|
amount::{Amount, NonNegative},
|
||||||
|
block::Height,
|
||||||
orchard::Flags,
|
orchard::Flags,
|
||||||
|
parameters::{Network, NetworkUpgrade},
|
||||||
sapling::{Output, PerSpendAnchor, Spend},
|
sapling::{Output, PerSpendAnchor, Spend},
|
||||||
transaction::Transaction,
|
transaction::Transaction,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::error::TransactionError;
|
use crate::error::TransactionError;
|
||||||
|
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
/// Checks that the transaction has inputs and outputs.
|
/// Checks that the transaction has inputs and outputs.
|
||||||
///
|
///
|
||||||
/// For `Transaction::V4`:
|
/// For `Transaction::V4`:
|
||||||
|
|
@ -119,3 +124,33 @@ pub fn output_cv_epk_not_small_order(output: &Output) -> Result<(), TransactionE
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if a transaction is adding to the sprout pool after Canopy
|
||||||
|
/// network upgrade given a block height and a network.
|
||||||
|
///
|
||||||
|
/// https://zips.z.cash/zip-0211
|
||||||
|
/// https://zips.z.cash/protocol/protocol.pdf#joinsplitdesc
|
||||||
|
pub fn disabled_add_to_sprout_pool(
|
||||||
|
tx: &Transaction,
|
||||||
|
height: Height,
|
||||||
|
network: Network,
|
||||||
|
) -> Result<(), TransactionError> {
|
||||||
|
let canopy_activation_height = NetworkUpgrade::Canopy
|
||||||
|
.activation_height(network)
|
||||||
|
.expect("Canopy activation height must be present for both networks");
|
||||||
|
|
||||||
|
// [Canopy onward]: `vpub_old` MUST be zero.
|
||||||
|
// https://zips.z.cash/protocol/protocol.pdf#joinsplitdesc
|
||||||
|
if height >= canopy_activation_height {
|
||||||
|
let zero = Amount::<NonNegative>::try_from(0).expect("an amount of 0 is always valid");
|
||||||
|
|
||||||
|
let tx_sprout_pool = tx.sprout_pool_added_values();
|
||||||
|
for vpub_old in tx_sprout_pool {
|
||||||
|
if *vpub_old != zero {
|
||||||
|
return Err(TransactionError::DisabledAddToSproutPool);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use std::{collections::HashMap, convert::TryFrom, convert::TryInto, sync::Arc};
|
||||||
use tower::{service_fn, ServiceExt};
|
use tower::{service_fn, ServiceExt};
|
||||||
|
|
||||||
use zebra_chain::{
|
use zebra_chain::{
|
||||||
amount::Amount,
|
amount::{Amount, NonNegative},
|
||||||
block, orchard,
|
block, orchard,
|
||||||
parameters::{Network, NetworkUpgrade},
|
parameters::{Network, NetworkUpgrade},
|
||||||
primitives::{ed25519, x25519, Groth16Proof},
|
primitives::{ed25519, x25519, Groth16Proof},
|
||||||
|
|
@ -929,3 +929,61 @@ fn mock_sprout_join_split_data() -> (JoinSplitData<Groth16Proof>, ed25519::Signi
|
||||||
|
|
||||||
(joinsplit_data, signing_key)
|
(joinsplit_data, signing_key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn add_to_sprout_pool_after_nu() {
|
||||||
|
zebra_test::init();
|
||||||
|
|
||||||
|
// get a block that we know it haves a transaction with `vpub_old` field greater than 0.
|
||||||
|
let block: Arc<_> = zebra_chain::block::Block::zcash_deserialize(
|
||||||
|
&zebra_test::vectors::BLOCK_MAINNET_419199_BYTES[..],
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.into();
|
||||||
|
|
||||||
|
// create a block height at canopy activation.
|
||||||
|
let network = Network::Mainnet;
|
||||||
|
let block_height = NetworkUpgrade::Canopy.activation_height(network).unwrap();
|
||||||
|
|
||||||
|
// create a zero amount.
|
||||||
|
let zero = Amount::<NonNegative>::try_from(0).expect("an amount of 0 is always valid");
|
||||||
|
|
||||||
|
// the coinbase transaction should pass the check.
|
||||||
|
assert_eq!(
|
||||||
|
check::disabled_add_to_sprout_pool(&block.transactions[0], block_height, network),
|
||||||
|
Ok(())
|
||||||
|
);
|
||||||
|
|
||||||
|
// the 2nd transaction has no joinsplits, should pass the check.
|
||||||
|
assert_eq!(block.transactions[1].joinsplit_count(), 0);
|
||||||
|
assert_eq!(
|
||||||
|
check::disabled_add_to_sprout_pool(&block.transactions[1], block_height, network),
|
||||||
|
Ok(())
|
||||||
|
);
|
||||||
|
|
||||||
|
// the 5th transaction has joinsplits and the `vpub_old` cumulative is greater than 0,
|
||||||
|
// should fail the check.
|
||||||
|
assert!(block.transactions[4].joinsplit_count() > 0);
|
||||||
|
let vpub_old: Amount<NonNegative> = block.transactions[4]
|
||||||
|
.sprout_pool_added_values()
|
||||||
|
.fold(zero, |acc, &x| (acc + x).unwrap());
|
||||||
|
assert!(vpub_old > zero);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
check::disabled_add_to_sprout_pool(&block.transactions[3], block_height, network),
|
||||||
|
Err(TransactionError::DisabledAddToSproutPool)
|
||||||
|
);
|
||||||
|
|
||||||
|
// the 8th transaction has joinsplits and the `vpub_old` cumulative is 0,
|
||||||
|
// should pass the check.
|
||||||
|
assert!(block.transactions[7].joinsplit_count() > 0);
|
||||||
|
let vpub_old: Amount<NonNegative> = block.transactions[7]
|
||||||
|
.sprout_pool_added_values()
|
||||||
|
.fold(zero, |acc, &x| (acc + x).unwrap());
|
||||||
|
assert_eq!(vpub_old, zero);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
check::disabled_add_to_sprout_pool(&block.transactions[7], block_height, network),
|
||||||
|
Ok(())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue