diff --git a/Cargo.lock b/Cargo.lock index 8ea8b8fe872..a0d49a3945e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1317,7 +1317,7 @@ dependencies = [ [[package]] name = "equihash" version = "0.2.0" -source = "git+https://github.com/zcash/librustzcash/?branch=main#40ca428c6081c61d5a2bf3f2053eb9e18219ca95" +source = "git+https://github.com/eigerco/librustzcash/?branch=zsf_on_main#280f4408dd3657ec5a32dcafa079414367445cc6" dependencies = [ "blake2b_simd", "byteorder", @@ -1352,7 +1352,7 @@ dependencies = [ [[package]] name = "f4jumble" version = "0.1.0" -source = "git+https://github.com/zcash/librustzcash/?branch=main#40ca428c6081c61d5a2bf3f2053eb9e18219ca95" +source = "git+https://github.com/eigerco/librustzcash/?branch=zsf_on_main#280f4408dd3657ec5a32dcafa079414367445cc6" dependencies = [ "blake2b_simd", ] @@ -5550,8 +5550,8 @@ checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546" [[package]] name = "zcash_address" -version = "0.4.0" -source = "git+https://github.com/zcash/librustzcash/?branch=main#40ca428c6081c61d5a2bf3f2053eb9e18219ca95" +version = "0.3.2" +source = "git+https://github.com/eigerco/librustzcash/?branch=zsf_on_main#280f4408dd3657ec5a32dcafa079414367445cc6" dependencies = [ "bech32", "bs58", @@ -5562,8 +5562,8 @@ dependencies = [ [[package]] name = "zcash_client_backend" -version = "0.13.0" -source = "git+https://github.com/zcash/librustzcash/?branch=main#40ca428c6081c61d5a2bf3f2053eb9e18219ca95" +version = "0.12.1" +source = "git+https://github.com/eigerco/librustzcash/?branch=zsf_on_main#280f4408dd3657ec5a32dcafa079414367445cc6" dependencies = [ "base64 0.21.7", "bech32", @@ -5601,8 +5601,8 @@ dependencies = [ [[package]] name = "zcash_encoding" -version = "0.2.1" -source = "git+https://github.com/zcash/librustzcash/?branch=main#40ca428c6081c61d5a2bf3f2053eb9e18219ca95" +version = "0.2.0" +source = "git+https://github.com/eigerco/librustzcash/?branch=zsf_on_main#280f4408dd3657ec5a32dcafa079414367445cc6" dependencies = [ "byteorder", "nonempty", @@ -5611,7 +5611,7 @@ dependencies = [ [[package]] name = "zcash_history" version = "0.4.0" -source = "git+https://github.com/zcash/librustzcash/?branch=main#40ca428c6081c61d5a2bf3f2053eb9e18219ca95" +source = "git+https://github.com/eigerco/librustzcash/?branch=zsf_on_main#280f4408dd3657ec5a32dcafa079414367445cc6" dependencies = [ "blake2b_simd", "byteorder", @@ -5620,8 +5620,8 @@ dependencies = [ [[package]] name = "zcash_keys" -version = "0.3.0" -source = "git+https://github.com/zcash/librustzcash/?branch=main#40ca428c6081c61d5a2bf3f2053eb9e18219ca95" +version = "0.2.0" +source = "git+https://github.com/eigerco/librustzcash/?branch=zsf_on_main#280f4408dd3657ec5a32dcafa079414367445cc6" dependencies = [ "bech32", "blake2b_simd", @@ -5658,8 +5658,8 @@ dependencies = [ [[package]] name = "zcash_primitives" -version = "0.16.0" -source = "git+https://github.com/zcash/librustzcash/?branch=main#40ca428c6081c61d5a2bf3f2053eb9e18219ca95" +version = "0.15.1" +source = "git+https://github.com/eigerco/librustzcash/?branch=zsf_on_main#280f4408dd3657ec5a32dcafa079414367445cc6" dependencies = [ "aes", "bip32", @@ -5667,7 +5667,7 @@ dependencies = [ "bs58", "byteorder", "document-features", - "equihash 0.2.0 (git+https://github.com/zcash/librustzcash/?branch=main)", + "equihash 0.2.0 (git+https://github.com/eigerco/librustzcash/?branch=zsf_on_main)", "ff", "fpe", "group", @@ -5696,8 +5696,8 @@ dependencies = [ [[package]] name = "zcash_proofs" -version = "0.16.0" -source = "git+https://github.com/zcash/librustzcash/?branch=main#40ca428c6081c61d5a2bf3f2053eb9e18219ca95" +version = "0.15.0" +source = "git+https://github.com/eigerco/librustzcash/?branch=zsf_on_main#280f4408dd3657ec5a32dcafa079414367445cc6" dependencies = [ "bellman", "blake2b_simd", @@ -5718,8 +5718,8 @@ dependencies = [ [[package]] name = "zcash_protocol" -version = "0.2.0" -source = "git+https://github.com/zcash/librustzcash/?branch=main#40ca428c6081c61d5a2bf3f2053eb9e18219ca95" +version = "0.1.1" +source = "git+https://github.com/eigerco/librustzcash/?branch=zsf_on_main#280f4408dd3657ec5a32dcafa079414367445cc6" dependencies = [ "document-features", "memuse", @@ -6252,8 +6252,8 @@ dependencies = [ [[package]] name = "zip321" -version = "0.1.0" -source = "git+https://github.com/zcash/librustzcash/?branch=main#40ca428c6081c61d5a2bf3f2053eb9e18219ca95" +version = "0.0.0" +source = "git+https://github.com/eigerco/librustzcash/?branch=zsf_on_main#280f4408dd3657ec5a32dcafa079414367445cc6" dependencies = [ "base64 0.21.7", "nom", diff --git a/Cargo.toml b/Cargo.toml index f310a4c30bd..4256c4f5774 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,14 +26,14 @@ incrementalmerkletree = "0.6.0" orchard = "0.9.0" sapling-crypto = "0.2.0" # TODO: Revert to a release once librustzcash is released (#8749). -zcash_address = { git = "https://github.com/zcash/librustzcash/", branch = "main" } -zcash_client_backend = { git = "https://github.com/zcash/librustzcash/", branch = "main" } -zcash_encoding = { git = "https://github.com/zcash/librustzcash/", branch = "main" } -zcash_history = { git = "https://github.com/zcash/librustzcash/", branch = "main" } -zcash_keys = { git = "https://github.com/zcash/librustzcash/", branch = "main" } -zcash_primitives = { git = "https://github.com/zcash/librustzcash/", branch = "main" } -zcash_proofs = { git = "https://github.com/zcash/librustzcash/", branch = "main" } -zcash_protocol = { git = "https://github.com/zcash/librustzcash/", branch = "main" } +zcash_address = { git = "https://github.com/eigerco/librustzcash/", branch = "zsf_on_main" } +zcash_client_backend = { git = "https://github.com/eigerco/librustzcash/", branch = "zsf_on_main" } +zcash_encoding = { git = "https://github.com/eigerco/librustzcash/", branch = "zsf_on_main" } +zcash_history = { git = "https://github.com/eigerco/librustzcash/", branch = "zsf_on_main" } +zcash_keys = { git = "https://github.com/eigerco/librustzcash/", branch = "zsf_on_main" } +zcash_primitives = { git = "https://github.com/eigerco/librustzcash/", branch = "zsf_on_main" } +zcash_proofs = { git = "https://github.com/eigerco/librustzcash/", branch = "zsf_on_main" } +zcash_protocol = { git = "https://github.com/eigerco/librustzcash/", branch = "zsf_on_main" } [workspace.metadata.release] diff --git a/zebra-chain/Cargo.toml b/zebra-chain/Cargo.toml index 9b144676fe1..27678a04088 100644 --- a/zebra-chain/Cargo.toml +++ b/zebra-chain/Cargo.toml @@ -17,6 +17,8 @@ categories = ["asynchronous", "cryptography::cryptocurrencies", "encoding"] [features] default = [] +zsf = [] + # Production features that activate extra functionality # Consensus-critical conversion from JSON to Zcash types diff --git a/zebra-chain/src/amount.rs b/zebra-chain/src/amount.rs index f4a81c14893..8f965246e94 100644 --- a/zebra-chain/src/amount.rs +++ b/zebra-chain/src/amount.rs @@ -557,7 +557,7 @@ impl Constraint for NonNegative { /// -MAX_MONEY..=0, /// ); /// ``` -#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Default)] pub struct NegativeOrZero; impl Constraint for NegativeOrZero { diff --git a/zebra-chain/src/block/commitment.rs b/zebra-chain/src/block/commitment.rs index 2cb09e75b22..237186dcd16 100644 --- a/zebra-chain/src/block/commitment.rs +++ b/zebra-chain/src/block/commitment.rs @@ -139,6 +139,10 @@ impl Commitment { (Nu5 | Nu6, _) => Ok(ChainHistoryBlockTxAuthCommitment( ChainHistoryBlockTxAuthCommitmentHash(bytes), )), + #[cfg(feature = "zsf")] + (ZFuture, _) => Ok(ChainHistoryBlockTxAuthCommitment( + ChainHistoryBlockTxAuthCommitmentHash(bytes), + )), } } diff --git a/zebra-chain/src/history_tree.rs b/zebra-chain/src/history_tree.rs index 91fa3a17628..5b4353a4b06 100644 --- a/zebra-chain/src/history_tree.rs +++ b/zebra-chain/src/history_tree.rs @@ -112,6 +112,17 @@ impl NonEmptyHistoryTree { )?; InnerHistoryTree::OrchardOnward(tree) } + #[cfg(feature = "zsf")] + NetworkUpgrade::ZFuture => { + let tree = Tree::::new_from_cache( + network, + network_upgrade, + size, + &peaks, + &Default::default(), + )?; + InnerHistoryTree::OrchardOnward(tree) + } }; Ok(Self { network: network.clone(), @@ -165,6 +176,16 @@ impl NonEmptyHistoryTree { )?; (InnerHistoryTree::OrchardOnward(tree), entry) } + #[cfg(feature = "zsf")] + NetworkUpgrade::ZFuture => { + let (tree, entry) = Tree::::new_from_block( + network, + block, + sapling_root, + orchard_root, + )?; + (InnerHistoryTree::OrchardOnward(tree), entry) + } }; let mut peaks = BTreeMap::new(); peaks.insert(0u32, entry); diff --git a/zebra-chain/src/parameters/network/testnet.rs b/zebra-chain/src/parameters/network/testnet.rs index 80cb8419c77..b7ec998132e 100644 --- a/zebra-chain/src/parameters/network/testnet.rs +++ b/zebra-chain/src/parameters/network/testnet.rs @@ -194,6 +194,9 @@ pub struct ConfiguredActivationHeights { /// Activation height for `NU6` network upgrade. #[serde(rename = "NU6")] pub nu6: Option, + #[cfg(feature = "zsf")] + #[serde(rename = "ZFuture")] + pub zfuture: Option, } /// Builder for the [`Parameters`] struct. @@ -314,6 +317,8 @@ impl ParametersBuilder { canopy, nu5, nu6, + #[cfg(feature = "zsf")] + zfuture, }: ConfiguredActivationHeights, ) -> Self { use NetworkUpgrade::*; @@ -322,7 +327,7 @@ impl ParametersBuilder { // // These must be in order so that later network upgrades overwrite prior ones // if multiple network upgrades are configured with the same activation height. - let activation_heights: BTreeMap<_, _> = before_overwinter + let activation_heights = before_overwinter .into_iter() .map(|h| (h, BeforeOverwinter)) .chain(overwinter.into_iter().map(|h| (h, Overwinter))) @@ -331,7 +336,13 @@ impl ParametersBuilder { .chain(heartwood.into_iter().map(|h| (h, Heartwood))) .chain(canopy.into_iter().map(|h| (h, Canopy))) .chain(nu5.into_iter().map(|h| (h, Nu5))) - .chain(nu6.into_iter().map(|h| (h, Nu6))) + .chain(nu6.into_iter().map(|h| (h, Nu6))); + + #[cfg(feature = "zsf")] + let activation_heights = + activation_heights.chain(zfuture.into_iter().map(|h| (h, ZFuture))); + + let activation_heights: BTreeMap<_, _> = activation_heights .map(|(h, nu)| (h.try_into().expect("activation height must be valid"), nu)) .collect(); @@ -538,6 +549,8 @@ impl Parameters { canopy: Some(1), nu5: nu5_activation_height, nu6: nu6_activation_height, + #[cfg(feature = "zsf")] + zfuture: nu5_activation_height.map(|height| height + 1), ..Default::default() }) .finish() diff --git a/zebra-chain/src/parameters/network/tests/vectors.rs b/zebra-chain/src/parameters/network/tests/vectors.rs index c839a26c116..ddfd3021357 100644 --- a/zebra-chain/src/parameters/network/tests/vectors.rs +++ b/zebra-chain/src/parameters/network/tests/vectors.rs @@ -1,7 +1,7 @@ //! Fixed test vectors for the network consensus parameters. +use zcash_primitives::consensus::NetworkConstants as _; use zcash_primitives::consensus::{self as zp_consensus, Parameters}; -use zcash_protocol::consensus::NetworkConstants as _; use crate::{ block::Height, @@ -31,6 +31,8 @@ fn check_parameters_impl() { zp_consensus::NetworkUpgrade::Heartwood, zp_consensus::NetworkUpgrade::Canopy, zp_consensus::NetworkUpgrade::Nu5, + #[cfg(feature = "zsf")] + zp_consensus::NetworkUpgrade::ZFuture, ]; for (network, zp_network) in [ @@ -109,7 +111,10 @@ fn activates_network_upgrades_correctly() { let expected_activation_height = 1; let network = testnet::Parameters::build() .with_activation_heights(ConfiguredActivationHeights { + #[cfg(not(feature = "zsf"))] nu6: Some(expected_activation_height), + #[cfg(feature = "zsf")] + zfuture: Some(expected_activation_height), ..Default::default() }) .to_network(); @@ -141,6 +146,8 @@ fn activates_network_upgrades_correctly() { (Height(1), NetworkUpgrade::Canopy), // TODO: Remove this once the testnet parameters are being serialized. (Height(100), NetworkUpgrade::Nu5), + #[cfg(feature = "zsf")] + (Height(101), NetworkUpgrade::ZFuture), ]; for (network, expected_activation_heights) in [ diff --git a/zebra-chain/src/parameters/network_upgrade.rs b/zebra-chain/src/parameters/network_upgrade.rs index c3f62e9a645..a5b9d75bdad 100644 --- a/zebra-chain/src/parameters/network_upgrade.rs +++ b/zebra-chain/src/parameters/network_upgrade.rs @@ -15,6 +15,7 @@ use hex::{FromHex, ToHex}; use proptest_derive::Arbitrary; /// A list of network upgrades in the order that they must be activated. +#[cfg(not(feature = "zsf"))] pub const NETWORK_UPGRADES_IN_ORDER: [NetworkUpgrade; 9] = [ Genesis, BeforeOverwinter, @@ -27,6 +28,21 @@ pub const NETWORK_UPGRADES_IN_ORDER: [NetworkUpgrade; 9] = [ Nu6, ]; +#[cfg(feature = "zsf")] +pub const NETWORK_UPGRADES_IN_ORDER: [NetworkUpgrade; 10] = [ + Genesis, + BeforeOverwinter, + Overwinter, + Sapling, + Blossom, + Heartwood, + Canopy, + Nu5, + Nu6, + #[cfg(feature = "zsf")] + ZFuture, +]; + /// A Zcash network upgrade. /// /// Network upgrades change the Zcash network protocol or consensus rules. Note that they have no @@ -60,6 +76,10 @@ pub enum NetworkUpgrade { Nu5, /// The Zcash protocol after the NU6 upgrade. Nu6, + #[cfg(feature = "zsf")] + #[serde(rename = "ZFuture")] + #[allow(non_snake_case)] + ZFuture, } impl fmt::Display for NetworkUpgrade { @@ -88,8 +108,10 @@ pub(super) const MAINNET_ACTIVATION_HEIGHTS: &[(block::Height, NetworkUpgrade)] (block::Height(903_000), Heartwood), (block::Height(1_046_400), Canopy), (block::Height(1_687_104), Nu5), - // TODO: Add NU6 - // (block::Height(2_726_400), Nu6), + // TODO: Update NU6 activation height once it's been specified. + (block::Height(2_820_000), Nu6), + #[cfg(feature = "zsf")] + (block::Height(3_000_000), ZFuture), ]; /// Fake mainnet network upgrade activation heights, used in tests. @@ -104,6 +126,8 @@ const FAKE_MAINNET_ACTIVATION_HEIGHTS: &[(block::Height, NetworkUpgrade)] = &[ (block::Height(30), Canopy), (block::Height(35), Nu5), (block::Height(40), Nu6), + #[cfg(feature = "zsf")] + (block::Height(45), ZFuture), ]; /// Testnet network upgrade activation heights. @@ -125,7 +149,10 @@ pub(super) const TESTNET_ACTIVATION_HEIGHTS: &[(block::Height, NetworkUpgrade)] (block::Height(903_800), Heartwood), (block::Height(1_028_500), Canopy), (block::Height(1_842_420), Nu5), - // TODO: Add NU6 activation height once it's been specified. + // TODO: Update NU6 activation height once it's been specified. + (block::Height(2_900_000), Nu6), + #[cfg(feature = "zsf")] + (block::Height(3_000_000), ZFuture), ]; /// Fake testnet network upgrade activation heights, used in tests. @@ -140,6 +167,8 @@ const FAKE_TESTNET_ACTIVATION_HEIGHTS: &[(block::Height, NetworkUpgrade)] = &[ (block::Height(30), Canopy), (block::Height(35), Nu5), (block::Height(40), Nu6), + #[cfg(feature = "zsf")] + (block::Height(45), ZFuture), ]; /// The Consensus Branch Id, used to bind transactions and blocks to a @@ -216,6 +245,8 @@ pub(crate) const CONSENSUS_BRANCH_IDS: &[(NetworkUpgrade, ConsensusBranchId)] = (Canopy, ConsensusBranchId(0xe9ff75a6)), (Nu5, ConsensusBranchId(0xc2d6d0b4)), (Nu6, ConsensusBranchId(0xc8e71055)), + #[cfg(feature = "zsf")] + (ZFuture, ConsensusBranchId(0xffff_ffff)), ]; /// The target block spacing before Blossom. @@ -332,7 +363,12 @@ impl NetworkUpgrade { Heartwood => Some(Canopy), Canopy => Some(Nu5), Nu5 => Some(Nu6), + #[cfg(not(feature = "zsf"))] Nu6 => None, + #[cfg(feature = "zsf")] + Nu6 => Some(ZFuture), + #[cfg(feature = "zsf")] + ZFuture => None, } } @@ -410,6 +446,8 @@ impl NetworkUpgrade { let spacing_seconds = match self { Genesis | BeforeOverwinter | Overwinter | Sapling => PRE_BLOSSOM_POW_TARGET_SPACING, Blossom | Heartwood | Canopy | Nu5 | Nu6 => POST_BLOSSOM_POW_TARGET_SPACING.into(), + #[cfg(feature = "zsf")] + ZFuture => POST_BLOSSOM_POW_TARGET_SPACING.into(), }; Duration::seconds(spacing_seconds) @@ -531,6 +569,8 @@ impl From for NetworkUpgrade { zcash_protocol::consensus::NetworkUpgrade::Canopy => Self::Canopy, zcash_protocol::consensus::NetworkUpgrade::Nu5 => Self::Nu5, zcash_protocol::consensus::NetworkUpgrade::Nu6 => Self::Nu6, + #[cfg(feature = "zsf")] + zcash_protocol::consensus::NetworkUpgrade::ZFuture => Self::ZFuture, } } } diff --git a/zebra-chain/src/parameters/transaction.rs b/zebra-chain/src/parameters/transaction.rs index bab59e794db..065cb473e91 100644 --- a/zebra-chain/src/parameters/transaction.rs +++ b/zebra-chain/src/parameters/transaction.rs @@ -11,3 +11,8 @@ pub const SAPLING_VERSION_GROUP_ID: u32 = 0x892F_2085; /// Orchard transactions must use transaction version 5 and this version /// group ID. Sapling transactions can use v4 or v5 transactions. pub const TX_V5_VERSION_GROUP_ID: u32 = 0x26A7_270A; + +/// The version group ID for version ZFUTURE transactions. +#[cfg(feature = "zsf")] +pub const TX_ZFUTURE_VERSION_GROUP_ID: u32 = + zcash_primitives::transaction::ZFUTURE_VERSION_GROUP_ID; diff --git a/zebra-chain/src/primitives/zcash_history.rs b/zebra-chain/src/primitives/zcash_history.rs index e8ca97d63f8..619b50f8f43 100644 --- a/zebra-chain/src/primitives/zcash_history.rs +++ b/zebra-chain/src/primitives/zcash_history.rs @@ -290,6 +290,21 @@ impl Version for zcash_history::V1 { end_height: height.0 as u64, sapling_tx: sapling_tx_count, }, + #[cfg(feature = "zsf")] + NetworkUpgrade::ZFuture => zcash_history::NodeData { + consensus_branch_id: branch_id.into(), + subtree_commitment: block_hash, + start_time: time, + end_time: time, + start_target: target, + end_target: target, + start_sapling_root: sapling_root, + end_sapling_root: sapling_root, + subtree_total_work: work, + start_height: height.0 as u64, + end_height: height.0 as u64, + sapling_tx: sapling_tx_count, + }, } } } diff --git a/zebra-chain/src/primitives/zcash_primitives.rs b/zebra-chain/src/primitives/zcash_primitives.rs index 7ab2f32d751..a74932ba941 100644 --- a/zebra-chain/src/primitives/zcash_primitives.rs +++ b/zebra-chain/src/primitives/zcash_primitives.rs @@ -165,6 +165,10 @@ impl TryFrom<&Transaction> for zp_tx::Transaction { Transaction::V5 { network_upgrade, .. } => network_upgrade, + #[cfg(feature = "zsf")] + Transaction::ZFuture { + network_upgrade, .. + } => network_upgrade, Transaction::V1 { .. } | Transaction::V2 { .. } | Transaction::V3 { .. } diff --git a/zebra-chain/src/transaction.rs b/zebra-chain/src/transaction.rs index 3df3edc8d53..8d4c194315d 100644 --- a/zebra-chain/src/transaction.rs +++ b/zebra-chain/src/transaction.rs @@ -37,6 +37,8 @@ pub use sighash::{HashType, SigHash, SigHasher}; pub use unmined::{ zip317, UnminedTx, UnminedTxId, VerifiedUnminedTx, MEMPOOL_TRANSACTION_COST_THRESHOLD, }; +#[cfg(feature = "zsf")] +use zcash_primitives::transaction::ZFUTURE_TX_VERSION; use crate::{ amount::{Amount, Error as AmountError, NegativeAllowed, NonNegative}, @@ -142,6 +144,30 @@ pub enum Transaction { /// The orchard data for this transaction, if any. orchard_shielded_data: Option, }, + /// A future version of transaction + #[cfg(feature = "zsf")] + #[allow(non_snake_case)] + ZFuture { + /// The Network Upgrade for this transaction. + /// + /// Derived from the ConsensusBranchId field. + network_upgrade: NetworkUpgrade, + /// The earliest time or block height that this transaction can be added to the + /// chain. + lock_time: LockTime, + /// The latest block height that this transaction can be added to the chain. + expiry_height: block::Height, + /// The transparent inputs to the transaction. + inputs: Vec, + /// The transparent outputs from the transaction. + outputs: Vec, + /// The sapling shielded data for this transaction, if any. + sapling_shielded_data: Option>, + /// The orchard data for this transaction, if any. + orchard_shielded_data: Option, + /// The Zcash Sustainability Fund deposit amount for this transaction, if any. + zsf_deposit: Amount, + }, } impl fmt::Display for Transaction { @@ -168,6 +194,8 @@ impl fmt::Display for Transaction { fmter.field("sapling_spends", &self.sapling_spends_per_anchor().count()); fmter.field("sapling_outputs", &self.sapling_outputs().count()); fmter.field("orchard_actions", &self.orchard_actions().count()); + #[cfg(feature = "zsf")] + fmter.field("zsf_deposit", &self.zsf_deposit()); fmter.field("unmined_id", &self.unmined_id()); @@ -248,6 +276,8 @@ impl Transaction { | Transaction::V3 { .. } | Transaction::V4 { .. } => None, Transaction::V5 { .. } => Some(AuthDigest::from(self)), + #[cfg(feature = "zsf")] + Transaction::ZFuture { .. } => Some(AuthDigest::from(self)), } } @@ -276,6 +306,11 @@ impl Transaction { !self.outputs().is_empty() || self.has_shielded_outputs() } + #[cfg(feature = "zsf")] + pub fn has_zsf_deposit(&self) -> bool { + self.zsf_deposit() > Amount::::zero() + } + /// Does this transaction have shielded outputs? /// /// See [`Self::has_transparent_or_shielded_outputs`] for details. @@ -321,6 +356,8 @@ impl Transaction { match self { Transaction::V1 { .. } | Transaction::V2 { .. } => false, Transaction::V3 { .. } | Transaction::V4 { .. } | Transaction::V5 { .. } => true, + #[cfg(feature = "zsf")] + Transaction::ZFuture { .. } => true, } } @@ -332,6 +369,8 @@ impl Transaction { Transaction::V3 { .. } => 3, Transaction::V4 { .. } => 4, Transaction::V5 { .. } => 5, + #[cfg(feature = "zsf")] + Transaction::ZFuture { .. } => ZFUTURE_TX_VERSION, } } @@ -343,6 +382,8 @@ impl Transaction { | Transaction::V3 { lock_time, .. } | Transaction::V4 { lock_time, .. } | Transaction::V5 { lock_time, .. } => *lock_time, + #[cfg(feature = "zsf")] + Transaction::ZFuture { lock_time, .. } => *lock_time, }; // `zcashd` checks that the block height is greater than the lock height. @@ -390,6 +431,8 @@ impl Transaction { | Transaction::V3 { lock_time, .. } | Transaction::V4 { lock_time, .. } | Transaction::V5 { lock_time, .. } => *lock_time, + #[cfg(feature = "zsf")] + Transaction::ZFuture { lock_time, .. } => *lock_time, }; let mut lock_time_bytes = Vec::new(); lock_time @@ -426,6 +469,14 @@ impl Transaction { block::Height(0) => None, block::Height(expiry_height) => Some(block::Height(*expiry_height)), }, + #[cfg(feature = "zsf")] + Transaction::ZFuture { expiry_height, .. } => match expiry_height { + // Consensus rule: + // > No limit: To set no limit on transactions (so that they do not expire), nExpiryHeight should be set to 0. + // https://zips.z.cash/zip-0203#specification + block::Height(0) => None, + block::Height(expiry_height) => Some(block::Height(*expiry_height)), + }, } } @@ -452,6 +503,11 @@ impl Transaction { ref mut expiry_height, .. } => expiry_height, + #[cfg(feature = "zsf")] + Transaction::ZFuture { + ref mut expiry_height, + .. + } => expiry_height, } } @@ -468,6 +524,10 @@ impl Transaction { Transaction::V5 { network_upgrade, .. } => Some(*network_upgrade), + #[cfg(feature = "zsf")] + Transaction::ZFuture { + network_upgrade, .. + } => Some(*network_upgrade), } } @@ -481,6 +541,8 @@ impl Transaction { Transaction::V3 { ref inputs, .. } => inputs, Transaction::V4 { ref inputs, .. } => inputs, Transaction::V5 { ref inputs, .. } => inputs, + #[cfg(feature = "zsf")] + Transaction::ZFuture { ref inputs, .. } => inputs, } } @@ -493,6 +555,8 @@ impl Transaction { Transaction::V3 { ref mut inputs, .. } => inputs, Transaction::V4 { ref mut inputs, .. } => inputs, Transaction::V5 { ref mut inputs, .. } => inputs, + #[cfg(feature = "zsf")] + Transaction::ZFuture { ref mut inputs, .. } => inputs, } } @@ -511,6 +575,8 @@ impl Transaction { Transaction::V3 { ref outputs, .. } => outputs, Transaction::V4 { ref outputs, .. } => outputs, Transaction::V5 { ref outputs, .. } => outputs, + #[cfg(feature = "zsf")] + Transaction::ZFuture { ref outputs, .. } => outputs, } } @@ -533,6 +599,10 @@ impl Transaction { Transaction::V5 { ref mut outputs, .. } => outputs, + #[cfg(feature = "zsf")] + Transaction::ZFuture { + ref mut outputs, .. + } => outputs, } } @@ -581,6 +651,8 @@ impl Transaction { .. } | Transaction::V5 { .. } => Box::new(std::iter::empty()), + #[cfg(feature = "zsf")] + Transaction::ZFuture { .. } => Box::new(std::iter::empty()), } } @@ -616,6 +688,8 @@ impl Transaction { .. } | Transaction::V5 { .. } => 0, + #[cfg(feature = "zsf")] + Transaction::ZFuture { .. } => 0, } } @@ -655,6 +729,8 @@ impl Transaction { .. } | Transaction::V5 { .. } => Box::new(std::iter::empty()), + #[cfg(feature = "zsf")] + Transaction::ZFuture { .. } => Box::new(std::iter::empty()), } } @@ -691,6 +767,8 @@ impl Transaction { .. } | Transaction::V5 { .. } => None, + #[cfg(feature = "zsf")] + Transaction::ZFuture { .. } => None, } } @@ -699,6 +777,8 @@ impl Transaction { match self { // No JoinSplits Transaction::V1 { .. } | Transaction::V5 { .. } => false, + #[cfg(feature = "zsf")] + Transaction::ZFuture { .. } => false, // JoinSplits-on-BCTV14 Transaction::V2 { joinsplit_data, .. } | Transaction::V3 { joinsplit_data, .. } => { @@ -746,6 +826,8 @@ impl Transaction { } | Transaction::V1 { .. } | Transaction::V5 { .. } => Box::new(std::iter::empty()), + #[cfg(feature = "zsf")] + Transaction::ZFuture { .. } => Box::new(std::iter::empty()), } } @@ -766,6 +848,11 @@ impl Transaction { sapling_shielded_data: Some(sapling_shielded_data), .. } => Box::new(sapling_shielded_data.anchors()), + #[cfg(feature = "zsf")] + Transaction::ZFuture { + sapling_shielded_data: Some(sapling_shielded_data), + .. + } => Box::new(sapling_shielded_data.anchors()), // No Spends Transaction::V1 { .. } @@ -779,6 +866,11 @@ impl Transaction { sapling_shielded_data: None, .. } => Box::new(std::iter::empty()), + #[cfg(feature = "zsf")] + Transaction::ZFuture { + sapling_shielded_data: None, + .. + } => Box::new(std::iter::empty()), } } @@ -804,6 +896,11 @@ impl Transaction { sapling_shielded_data: Some(sapling_shielded_data), .. } => Box::new(sapling_shielded_data.spends_per_anchor()), + #[cfg(feature = "zsf")] + Transaction::ZFuture { + sapling_shielded_data: Some(sapling_shielded_data), + .. + } => Box::new(sapling_shielded_data.spends_per_anchor()), // No Spends Transaction::V1 { .. } @@ -817,6 +914,11 @@ impl Transaction { sapling_shielded_data: None, .. } => Box::new(std::iter::empty()), + #[cfg(feature = "zsf")] + Transaction::ZFuture { + sapling_shielded_data: None, + .. + } => Box::new(std::iter::empty()), } } @@ -832,6 +934,11 @@ impl Transaction { sapling_shielded_data: Some(sapling_shielded_data), .. } => Box::new(sapling_shielded_data.outputs()), + #[cfg(feature = "zsf")] + Transaction::ZFuture { + sapling_shielded_data: Some(sapling_shielded_data), + .. + } => Box::new(sapling_shielded_data.outputs()), // No Outputs Transaction::V1 { .. } @@ -845,6 +952,11 @@ impl Transaction { sapling_shielded_data: None, .. } => Box::new(std::iter::empty()), + #[cfg(feature = "zsf")] + Transaction::ZFuture { + sapling_shielded_data: None, + .. + } => Box::new(std::iter::empty()), } } @@ -862,6 +974,11 @@ impl Transaction { sapling_shielded_data: Some(sapling_shielded_data), .. } => Box::new(sapling_shielded_data.nullifiers()), + #[cfg(feature = "zsf")] + Transaction::ZFuture { + sapling_shielded_data: Some(sapling_shielded_data), + .. + } => Box::new(sapling_shielded_data.nullifiers()), // No Spends Transaction::V1 { .. } @@ -875,6 +992,11 @@ impl Transaction { sapling_shielded_data: None, .. } => Box::new(std::iter::empty()), + #[cfg(feature = "zsf")] + Transaction::ZFuture { + sapling_shielded_data: None, + .. + } => Box::new(std::iter::empty()), } } @@ -892,6 +1014,11 @@ impl Transaction { sapling_shielded_data: Some(sapling_shielded_data), .. } => Box::new(sapling_shielded_data.note_commitments()), + #[cfg(feature = "zsf")] + Transaction::ZFuture { + sapling_shielded_data: Some(sapling_shielded_data), + .. + } => Box::new(sapling_shielded_data.note_commitments()), // No Spends Transaction::V1 { .. } @@ -905,6 +1032,11 @@ impl Transaction { sapling_shielded_data: None, .. } => Box::new(std::iter::empty()), + #[cfg(feature = "zsf")] + Transaction::ZFuture { + sapling_shielded_data: None, + .. + } => Box::new(std::iter::empty()), } } @@ -920,6 +1052,11 @@ impl Transaction { sapling_shielded_data, .. } => sapling_shielded_data.is_some(), + #[cfg(feature = "zsf")] + Transaction::ZFuture { + sapling_shielded_data, + .. + } => sapling_shielded_data.is_some(), } } @@ -934,6 +1071,11 @@ impl Transaction { orchard_shielded_data, .. } => orchard_shielded_data.as_ref(), + #[cfg(feature = "zsf")] + Transaction::ZFuture { + orchard_shielded_data, + .. + } => orchard_shielded_data.as_ref(), // No Orchard shielded data Transaction::V1 { .. } @@ -952,6 +1094,11 @@ impl Transaction { orchard_shielded_data: Some(orchard_shielded_data), .. } => Some(orchard_shielded_data), + #[cfg(feature = "zsf")] + Transaction::ZFuture { + orchard_shielded_data: Some(orchard_shielded_data), + .. + } => Some(orchard_shielded_data), Transaction::V1 { .. } | Transaction::V2 { .. } @@ -961,6 +1108,11 @@ impl Transaction { orchard_shielded_data: None, .. } => None, + #[cfg(feature = "zsf")] + Transaction::ZFuture { + orchard_shielded_data: None, + .. + } => None, } } @@ -1087,6 +1239,8 @@ impl Transaction { .. } | Transaction::V5 { .. } => Box::new(std::iter::empty()), + #[cfg(feature = "zsf")] + Transaction::ZFuture { .. } => Box::new(std::iter::empty()), } } @@ -1136,6 +1290,8 @@ impl Transaction { .. } | Transaction::V5 { .. } => Box::new(std::iter::empty()), + #[cfg(feature = "zsf")] + Transaction::ZFuture { .. } => Box::new(std::iter::empty()), } } @@ -1183,6 +1339,8 @@ impl Transaction { .. } | Transaction::V5 { .. } => Box::new(std::iter::empty()), + #[cfg(feature = "zsf")] + Transaction::ZFuture { .. } => Box::new(std::iter::empty()), } } @@ -1232,6 +1390,8 @@ impl Transaction { .. } | Transaction::V5 { .. } => Box::new(std::iter::empty()), + #[cfg(feature = "zsf")] + Transaction::ZFuture { .. } => Box::new(std::iter::empty()), } } @@ -1273,6 +1433,8 @@ impl Transaction { .. } | Transaction::V5 { .. } => Box::new(iter::empty()), + #[cfg(feature = "zsf")] + Transaction::ZFuture { .. } => Box::new(iter::empty()), }; joinsplit_value_balances.map(ValueBalance::from_sprout_amount) @@ -1314,6 +1476,11 @@ impl Transaction { sapling_shielded_data: Some(sapling_shielded_data), .. } => sapling_shielded_data.value_balance, + #[cfg(feature = "zsf")] + Transaction::ZFuture { + sapling_shielded_data: Some(sapling_shielded_data), + .. + } => sapling_shielded_data.value_balance, Transaction::V1 { .. } | Transaction::V2 { .. } @@ -1326,6 +1493,11 @@ impl Transaction { sapling_shielded_data: None, .. } => Amount::zero(), + #[cfg(feature = "zsf")] + Transaction::ZFuture { + sapling_shielded_data: None, + .. + } => Amount::zero(), }; ValueBalance::from_sapling_amount(sapling_value_balance) @@ -1346,6 +1518,11 @@ impl Transaction { sapling_shielded_data: Some(sapling_shielded_data), .. } => Some(&mut sapling_shielded_data.value_balance), + #[cfg(feature = "zsf")] + Transaction::ZFuture { + sapling_shielded_data: Some(sapling_shielded_data), + .. + } => Some(&mut sapling_shielded_data.value_balance), Transaction::V1 { .. } | Transaction::V2 { .. } | Transaction::V3 { .. } @@ -1357,6 +1534,11 @@ impl Transaction { sapling_shielded_data: None, .. } => None, + #[cfg(feature = "zsf")] + Transaction::ZFuture { + sapling_shielded_data: None, + .. + } => None, } } @@ -1390,7 +1572,23 @@ impl Transaction { .map(|shielded_data| &mut shielded_data.value_balance) } + /// Return the zsf deposit balance, the change in the transaction value + /// pool due to the zsf deposit. + #[cfg(feature = "tx-v6")] + pub fn zsf_deposit_balance(&self) -> ValueBalance { + match self { + Transaction::V6 { zsf_deposit, .. } => { + ValueBalance::from_zsf_amount(zsf_deposit.constrain().unwrap()) + } + _ => ValueBalance::zero(), + } + } + /// Returns the value balances for this transaction using the provided transparent outputs. + /// Get the value balances for this transaction, + /// using the transparent outputs spent in this transaction. + /// + /// See `value_balance` for details. pub(crate) fn value_balance_from_outputs( &self, outputs: &HashMap, @@ -1427,4 +1625,17 @@ impl Transaction { ) -> Result, ValueBalanceError> { self.value_balance_from_outputs(&outputs_from_utxos(utxos.clone())) } + + /// Access the transparent inputs of this transaction, regardless of version. + #[cfg(feature = "zsf")] + pub fn zsf_deposit(&self) -> Amount { + match self { + Transaction::V1 { .. } + | Transaction::V2 { .. } + | Transaction::V3 { .. } + | Transaction::V4 { .. } + | Transaction::V5 { .. } => Amount::zero(), + Transaction::ZFuture { zsf_deposit, .. } => *zsf_deposit, + } + } } diff --git a/zebra-chain/src/transaction/arbitrary.rs b/zebra-chain/src/transaction/arbitrary.rs index cf4aa7a9552..803391afc2c 100644 --- a/zebra-chain/src/transaction/arbitrary.rs +++ b/zebra-chain/src/transaction/arbitrary.rs @@ -783,6 +783,12 @@ impl Arbitrary for Transaction { Self::v5_strategy(ledger_state) ] .boxed(), + #[cfg(feature = "zsf")] + NetworkUpgrade::ZFuture => prop_oneof![ + Self::v4_strategy(ledger_state.clone()), + Self::v5_strategy(ledger_state) + ] + .boxed(), } } @@ -918,6 +924,8 @@ pub fn transaction_to_fake_v5( orchard_shielded_data: None, }, v5 @ V5 { .. } => v5.clone(), + #[cfg(feature = "zsf")] + ZFuture { .. } => todo!(), } } diff --git a/zebra-chain/src/transaction/serialize.rs b/zebra-chain/src/transaction/serialize.rs index ba65dd054b3..2146c4a6669 100644 --- a/zebra-chain/src/transaction/serialize.rs +++ b/zebra-chain/src/transaction/serialize.rs @@ -7,6 +7,11 @@ use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use halo2::pasta::group::ff::PrimeField; use hex::FromHex; use reddsa::{orchard::Binding, orchard::SpendAuth, Signature}; +#[cfg(feature = "zsf")] +use zcash_primitives::transaction::ZFUTURE_VERSION_GROUP_ID; + +#[cfg(feature = "zsf")] +use crate::parameters::TX_ZFUTURE_VERSION_GROUP_ID; use crate::{ amount, @@ -673,6 +678,54 @@ impl ZcashSerialize for Transaction { // `proofsOrchard`, `vSpendAuthSigsOrchard`, and `bindingSigOrchard`. orchard_shielded_data.zcash_serialize(&mut writer)?; } + + #[cfg(feature = "zsf")] + Transaction::ZFuture { + network_upgrade, + lock_time, + expiry_height, + inputs, + outputs, + sapling_shielded_data, + orchard_shielded_data, + zsf_deposit, + } => { + // Denoted as `nVersionGroupId` in the spec. + writer.write_u32::(TX_ZFUTURE_VERSION_GROUP_ID)?; + + // Denoted as `nConsensusBranchId` in the spec. + writer.write_u32::(u32::from( + network_upgrade + .branch_id() + .expect("valid transactions must have a network upgrade with a branch id"), + ))?; + + // Denoted as `lock_time` in the spec. + lock_time.zcash_serialize(&mut writer)?; + + // Denoted as `nExpiryHeight` in the spec. + writer.write_u32::(expiry_height.0)?; + + // Denoted as `tx_in_count` and `tx_in` in the spec. + inputs.zcash_serialize(&mut writer)?; + + // Denoted as `tx_out_count` and `tx_out` in the spec. + outputs.zcash_serialize(&mut writer)?; + + // A bundle of fields denoted in the spec as `nSpendsSapling`, `vSpendsSapling`, + // `nOutputsSapling`,`vOutputsSapling`, `valueBalanceSapling`, `anchorSapling`, + // `vSpendProofsSapling`, `vSpendAuthSigsSapling`, `vOutputProofsSapling` and + // `bindingSigSapling`. + sapling_shielded_data.zcash_serialize(&mut writer)?; + + // A bundle of fields denoted in the spec as `nActionsOrchard`, `vActionsOrchard`, + // `flagsOrchard`,`valueBalanceOrchard`, `anchorOrchard`, `sizeProofsOrchard`, + // `proofsOrchard`, `vSpendAuthSigsOrchard`, and `bindingSigOrchard`. + orchard_shielded_data.zcash_serialize(&mut writer)?; + + // Denoted as `zsf_deposit` in the spec. + zsf_deposit.zcash_serialize(&mut writer)?; + } } Ok(()) } @@ -929,6 +982,60 @@ impl ZcashDeserialize for Transaction { orchard_shielded_data, }) } + #[cfg(feature = "zsf")] + (ZFUTURE_TX_VERSION, true) => { + // Denoted as `nVersionGroupId` in the spec. + let id = limited_reader.read_u32::()?; + if id != ZFUTURE_VERSION_GROUP_ID { + return Err(SerializationError::Parse("expected TX_V6_VERSION_GROUP_ID")); + } + // Denoted as `nConsensusBranchId` in the spec. + // Convert it to a NetworkUpgrade + let network_upgrade = + NetworkUpgrade::from_branch_id(limited_reader.read_u32::()?) + .ok_or_else(|| { + SerializationError::Parse( + "expected a valid network upgrade from the consensus branch id", + ) + })?; + + // Denoted as `lock_time` in the spec. + let lock_time = LockTime::zcash_deserialize(&mut limited_reader)?; + + // Denoted as `nExpiryHeight` in the spec. + let expiry_height = block::Height(limited_reader.read_u32::()?); + + // Denoted as `tx_in_count` and `tx_in` in the spec. + let inputs = Vec::zcash_deserialize(&mut limited_reader)?; + + // Denoted as `tx_out_count` and `tx_out` in the spec. + let outputs = Vec::zcash_deserialize(&mut limited_reader)?; + + // A bundle of fields denoted in the spec as `nSpendsSapling`, `vSpendsSapling`, + // `nOutputsSapling`,`vOutputsSapling`, `valueBalanceSapling`, `anchorSapling`, + // `vSpendProofsSapling`, `vSpendAuthSigsSapling`, `vOutputProofsSapling` and + // `bindingSigSapling`. + let sapling_shielded_data = (&mut limited_reader).zcash_deserialize_into()?; + + // A bundle of fields denoted in the spec as `nActionsOrchard`, `vActionsOrchard`, + // `flagsOrchard`,`valueBalanceOrchard`, `anchorOrchard`, `sizeProofsOrchard`, + // `proofsOrchard`, `vSpendAuthSigsOrchard`, and `bindingSigOrchard`. + let orchard_shielded_data = (&mut limited_reader).zcash_deserialize_into()?; + + // Denoted as `zsf_deposit` in the spec. + let zsf_deposit = (&mut limited_reader).zcash_deserialize_into()?; + + Ok(Transaction::ZFuture { + network_upgrade, + lock_time, + expiry_height, + inputs, + outputs, + sapling_shielded_data, + orchard_shielded_data, + zsf_deposit, + }) + } (_, _) => Err(SerializationError::Parse("bad tx header")), } } diff --git a/zebra-chain/src/transaction/tests/vectors.rs b/zebra-chain/src/transaction/tests/vectors.rs index 66d5009ed05..b4f66c21238 100644 --- a/zebra-chain/src/transaction/tests/vectors.rs +++ b/zebra-chain/src/transaction/tests/vectors.rs @@ -1001,6 +1001,27 @@ fn binding_signatures_for_network(network: Network) { ) .expect("a valid redjubjub::VerificationKey"); + bvk.verify( + shielded_sighash.as_ref(), + &sapling_shielded_data.binding_sig, + ) + .expect("must pass verification"); + } + } + #[cfg(feature = "zsf")] + Transaction::ZFuture { + sapling_shielded_data, + .. + } => { + if let Some(sapling_shielded_data) = sapling_shielded_data { + let shielded_sighash = + tx.sighash(upgrade.branch_id().unwrap(), HashType::ALL, &[], None); + + let bvk = redjubjub::VerificationKey::try_from( + sapling_shielded_data.binding_verification_key(), + ) + .expect("a valid redjubjub::VerificationKey"); + bvk.verify( shielded_sighash.as_ref(), &sapling_shielded_data.binding_sig, diff --git a/zebra-chain/src/transaction/txid.rs b/zebra-chain/src/transaction/txid.rs index f67f6dee58d..e4cc551d78e 100644 --- a/zebra-chain/src/transaction/txid.rs +++ b/zebra-chain/src/transaction/txid.rs @@ -29,6 +29,8 @@ impl<'a> TxIdBuilder<'a> { | Transaction::V3 { .. } | Transaction::V4 { .. } => self.txid_v1_to_v4(), Transaction::V5 { .. } => self.txid_v5(), + #[cfg(feature = "zsf")] + Transaction::ZFuture { .. } => self.txid_zfuture(), } } @@ -52,4 +54,12 @@ impl<'a> TxIdBuilder<'a> { let alt_tx: zcash_primitives::transaction::Transaction = self.trans.try_into()?; Ok(Hash(*alt_tx.txid().as_ref())) } + + /// Compute the Transaction ID for a ZFuture transaction in the given network upgrade. + fn txid_zfuture(self) -> Result { + // The v5 txid (from ZIP-244) is computed using librustzcash. Convert the zebra + // transaction to a librustzcash transaction. + let alt_tx: zcash_primitives::transaction::Transaction = self.trans.try_into()?; + Ok(Hash(*alt_tx.txid().as_ref())) + } } diff --git a/zebra-chain/src/transaction/unmined.rs b/zebra-chain/src/transaction/unmined.rs index da716573e8b..a063737abae 100644 --- a/zebra-chain/src/transaction/unmined.rs +++ b/zebra-chain/src/transaction/unmined.rs @@ -124,7 +124,7 @@ impl fmt::Display for UnminedTxId { .debug_tuple("transaction::Hash") .field(&"private") .finish(), - Witnessed(_id) => f.debug_tuple("WtxId").field(&"private").finish(), + Witnessed(id) => f.debug_tuple("WtxId").field(id).finish(), } } } @@ -141,6 +141,8 @@ impl From<&Transaction> for UnminedTxId { match transaction { V1 { .. } | V2 { .. } | V3 { .. } | V4 { .. } => Legacy(transaction.into()), V5 { .. } => Witnessed(transaction.into()), + #[cfg(feature = "zsf")] + ZFuture { .. } => Witnessed(transaction.into()), } } } diff --git a/zebra-chain/src/value_balance.rs b/zebra-chain/src/value_balance.rs index b2b33e878a7..ddd20ed9be9 100644 --- a/zebra-chain/src/value_balance.rs +++ b/zebra-chain/src/value_balance.rs @@ -7,7 +7,7 @@ use core::fmt; #[cfg(any(test, feature = "proptest-impl"))] use std::{borrow::Borrow, collections::HashMap}; -#[cfg(any(test, feature = "proptest-impl"))] +#[cfg(any(test, feature = "proptest-impl", feature = "zsf"))] use crate::{amount::MAX_MONEY, transaction::Transaction, transparent}; #[cfg(any(test, feature = "proptest-impl"))] @@ -395,6 +395,15 @@ impl ValueBalance { deferred, }) } + + #[cfg(feature = "zsf")] + pub fn zsf_balance(&self) -> Amount { + let max_money: Amount = MAX_MONEY + .try_into() + .expect("MAX_MONEY should be a valid amount"); + (max_money - self.transparent - self.sprout - self.sapling - self.orchard - self.deferred) + .expect("Expected non-negative value") + } } #[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)] diff --git a/zebra-consensus/Cargo.toml b/zebra-consensus/Cargo.toml index d1dba9dd535..bf89950d64b 100644 --- a/zebra-consensus/Cargo.toml +++ b/zebra-consensus/Cargo.toml @@ -17,6 +17,8 @@ categories = ["asynchronous", "cryptography::cryptocurrencies"] [features] default = [] +zsf = [] + # Production features that activate extra dependencies, or extra features in dependencies progress-bar = [ diff --git a/zebra-consensus/src/block.rs b/zebra-consensus/src/block.rs index 2b0013a5a3d..18e50e0f31d 100644 --- a/zebra-consensus/src/block.rs +++ b/zebra-consensus/src/block.rs @@ -216,7 +216,28 @@ where .map_err(VerifyBlockError::Time)?; let coinbase_tx = check::coinbase_is_first(&block)?; + #[cfg(not(feature = "zsf"))] let expected_block_subsidy = subsidy::general::block_subsidy(height, &network)?; + #[cfg(feature = "zsf")] + let expected_block_subsidy = { + let zsf_balance = match state_service + .ready() + .await + .map_err(|source| VerifyBlockError::Depth { source, hash })? + .call(zs::Request::TipPoolValues) + .await + .map_err(|source| VerifyBlockError::Depth { source, hash })? + { + zs::Response::TipPoolValues { + tip_hash: _, + tip_height: _, + value_balance, + } => value_balance.zsf_balance(), + _ => unreachable!("wrong response to Request::KnownBlock"), + }; + + subsidy::general::block_subsidy(height, &network, zsf_balance)? + }; check::subsidy_is_valid(&block, &network, expected_block_subsidy)?; @@ -301,7 +322,7 @@ where source: amount_error, })?; - check::miner_fees_are_valid( + check::transaction_miner_fees_are_valid( &coinbase_tx, height, block_miner_fees, diff --git a/zebra-consensus/src/block/check.rs b/zebra-consensus/src/block/check.rs index 189cfdc8493..b60433be213 100644 --- a/zebra-consensus/src/block/check.rs +++ b/zebra-consensus/src/block/check.rs @@ -233,7 +233,7 @@ pub fn subsidy_is_valid( /// Returns `Ok(())` if the miner fees consensus rule is valid. /// /// [7.1.2]: https://zips.z.cash/protocol/protocol.pdf#txnconsensus -pub fn miner_fees_are_valid( +pub fn transaction_miner_fees_are_valid( coinbase_tx: &Transaction, height: Height, block_miner_fees: Amount, @@ -241,6 +241,7 @@ pub fn miner_fees_are_valid( expected_deferred_amount: Amount, network: &Network, ) -> Result<(), BlockError> { + let network_upgrade = NetworkUpgrade::current(network, height); let transparent_value_balance = subsidy::general::output_amounts(coinbase_tx) .iter() .sum::, AmountError>>() @@ -250,6 +251,36 @@ pub fn miner_fees_are_valid( let sapling_value_balance = coinbase_tx.sapling_value_balance().sapling_amount(); let orchard_value_balance = coinbase_tx.orchard_value_balance().orchard_amount(); + // Coinbase transaction can still have a ZSF deposit + #[cfg(feature = "zsf")] + let zsf_deposit = coinbase_tx + .zsf_deposit() + .constrain() + .expect("positive value always fit in `NegativeAllowed`"); + + miner_fees_are_valid( + transparent_value_balance, + sapling_value_balance, + orchard_value_balance, + #[cfg(feature = "zsf")] + zsf_deposit, + expected_block_subsidy, + block_miner_fees, + expected_deferred_amount, + network_upgrade, + ) +} + +pub fn miner_fees_are_valid( + transparent_value_balance: Amount, + sapling_value_balance: Amount, + orchard_value_balance: Amount, + #[cfg(feature = "zsf")] zsf_deposit: Amount, + expected_block_subsidy: Amount, + block_miner_fees: Amount, + expected_deferred_amount: Amount, + network_upgrade: NetworkUpgrade, +) -> Result<(), BlockError> { // TODO: Update the quote below once its been updated for NU6. // // # Consensus @@ -262,6 +293,11 @@ pub fn miner_fees_are_valid( // // The expected lockbox funding stream output of the coinbase transaction is also subtracted // from the block subsidy value plus the transaction fees paid by transactions in this block. + #[cfg(feature = "zsf")] + let left = (transparent_value_balance - sapling_value_balance - orchard_value_balance + + zsf_deposit) + .map_err(|_| SubsidyError::SumOverflow)?; + #[cfg(not(feature = "zsf"))] let left = (transparent_value_balance - sapling_value_balance - orchard_value_balance) .map_err(|_| SubsidyError::SumOverflow)?; let right = (expected_block_subsidy + block_miner_fees - expected_deferred_amount) @@ -275,14 +311,27 @@ pub fn miner_fees_are_valid( // input. // // > [NU6 onward] The total output of a coinbase transaction MUST be equal to its total input. - if if NetworkUpgrade::current(network, height) < NetworkUpgrade::Nu6 { - left > right + let block_before_nu6 = network_upgrade < NetworkUpgrade::Nu6; + let miner_fees_valid = if block_before_nu6 { + left <= right } else { - left != right - } { + left == right + }; + + if !miner_fees_valid { Err(SubsidyError::InvalidMinerFees)? }; + // Verify that the ZSF deposit is at least the minimum required amount (ZIP-235). + #[cfg(feature = "zsf")] + if network_upgrade == NetworkUpgrade::ZFuture { + let minimum_zsf_deposit = ((block_miner_fees * 6).unwrap() / 10).unwrap(); + + if zsf_deposit < minimum_zsf_deposit { + Err(SubsidyError::InvalidZsfDepositAmount)? + } + } + Ok(()) } diff --git a/zebra-consensus/src/block/subsidy/funding_streams/tests.rs b/zebra-consensus/src/block/subsidy/funding_streams/tests.rs index 626b983fac6..13d1aa453bd 100644 --- a/zebra-consensus/src/block/subsidy/funding_streams/tests.rs +++ b/zebra-consensus/src/block/subsidy/funding_streams/tests.rs @@ -10,7 +10,7 @@ use zebra_chain::parameters::{ NetworkKind, }; -use crate::block::subsidy::general::block_subsidy; +use crate::block::subsidy::general::block_subsidy_pre_zsf; use super::*; @@ -26,7 +26,7 @@ fn test_funding_stream_values() -> Result<(), Report> { assert!(funding_stream_values( canopy_height_minus1, network, - block_subsidy(canopy_height_minus1, network)? + block_subsidy_pre_zsf(canopy_height_minus1, network)? )? .is_empty()); @@ -50,7 +50,7 @@ fn test_funding_stream_values() -> Result<(), Report> { funding_stream_values( canopy_height, network, - block_subsidy(canopy_height, network)? + block_subsidy_pre_zsf(canopy_height, network)? ) .unwrap(), hash_map @@ -60,7 +60,7 @@ fn test_funding_stream_values() -> Result<(), Report> { funding_stream_values( canopy_height_plus1, network, - block_subsidy(canopy_height_plus1, network)? + block_subsidy_pre_zsf(canopy_height_plus1, network)? ) .unwrap(), hash_map @@ -70,7 +70,7 @@ fn test_funding_stream_values() -> Result<(), Report> { funding_stream_values( canopy_height_plus2, network, - block_subsidy(canopy_height_plus2, network)? + block_subsidy_pre_zsf(canopy_height_plus2, network)? ) .unwrap(), hash_map @@ -82,11 +82,11 @@ fn test_funding_stream_values() -> Result<(), Report> { let last = (end - 1).unwrap(); assert_eq!( - funding_stream_values(last, network, block_subsidy(last, network)?).unwrap(), + funding_stream_values(last, network, block_subsidy_pre_zsf(last, network)?).unwrap(), hash_map ); - assert!(funding_stream_values(end, network, block_subsidy(end, network)?)?.is_empty()); + assert!(funding_stream_values(end, network, block_subsidy_pre_zsf(end, network)?)?.is_empty()); // TODO: Replace this with Mainnet once there's an NU6 activation height defined for Mainnet let network = testnet::Parameters::build() @@ -137,7 +137,8 @@ fn test_funding_stream_values() -> Result<(), Report> { Height(nu6_height.0 + 1), ] { assert_eq!( - funding_stream_values(height, &network, block_subsidy(height, &network)?).unwrap(), + funding_stream_values(height, &network, block_subsidy_pre_zsf(height, &network)?) + .unwrap(), hash_map ); } @@ -191,7 +192,8 @@ fn test_funding_stream_values() -> Result<(), Report> { Height(nu6_height.0 + 1), ] { assert_eq!( - funding_stream_values(height, &network, block_subsidy(height, &network)?).unwrap(), + funding_stream_values(height, &network, block_subsidy_pre_zsf(height, &network)?) + .unwrap(), hash_map ); } diff --git a/zebra-consensus/src/block/subsidy/general.rs b/zebra-consensus/src/block/subsidy/general.rs index 03ebac36d21..cd07b6580ae 100644 --- a/zebra-consensus/src/block/subsidy/general.rs +++ b/zebra-consensus/src/block/subsidy/general.rs @@ -59,10 +59,36 @@ pub fn halving_divisor(height: Height, network: &Network) -> Option { } } +#[cfg(feature = "zsf")] +pub fn block_subsidy( + height: Height, + network: &Network, + zsf_balance: Amount, +) -> Result, SubsidyError> { + let zsf_activation_height = ZFuture + .activation_height(network) + .expect("ZFuture activation height should be available"); + + if height < zsf_activation_height { + block_subsidy_pre_zsf(height, network) + } else { + let zsf_balance: i64 = zsf_balance.into(); + let zsf_balance: i128 = zsf_balance.into(); + const BLOCK_SUBSIDY_DENOMINATOR: i128 = 10_000_000_000; + const BLOCK_SUBSIDY_NUMERATOR: i128 = 4_126; + + // calculate the block subsidy (in zatoshi) using the ZSF balance, note the rounding up + let subsidy = (zsf_balance * BLOCK_SUBSIDY_NUMERATOR + (BLOCK_SUBSIDY_DENOMINATOR - 1)) + / BLOCK_SUBSIDY_DENOMINATOR; + + Ok(subsidy.try_into().expect("subsidy should fit in Amount")) + } +} + /// `BlockSubsidy(height)` as described in [protocol specification ยง7.8][7.8] /// /// [7.8]: https://zips.z.cash/protocol/protocol.pdf#subsidies -pub fn block_subsidy( +pub fn block_subsidy_pre_zsf( height: Height, network: &Network, ) -> Result, SubsidyError> { @@ -132,7 +158,7 @@ fn lockbox_input_value(network: &Network, height: Height) -> Amount return Amount::zero(); }; - let expected_block_subsidy = block_subsidy(nu6_activation_height, network) + let expected_block_subsidy = block_subsidy_pre_zsf(nu6_activation_height, network) .expect("block at NU6 activation height must have valid expected subsidy"); let &deferred_amount_per_block = funding_stream_values(nu6_activation_height, network, expected_block_subsidy) @@ -158,9 +184,12 @@ fn lockbox_input_value(network: &Network, height: Height) -> Amount mod test { use super::*; use color_eyre::Report; - use zebra_chain::parameters::testnet::{ - self, ConfiguredActivationHeights, ConfiguredFundingStreamRecipient, - ConfiguredFundingStreams, + use zebra_chain::{ + amount::MAX_MONEY, + parameters::testnet::{ + self, ConfiguredActivationHeights, ConfiguredFundingStreamRecipient, + ConfiguredFundingStreams, + }, }; #[test] @@ -308,25 +337,29 @@ mod test { // https://z.cash/support/faq/#what-is-slow-start-mining assert_eq!( Amount::::try_from(1_250_000_000)?, - block_subsidy((network.slow_start_interval() + 1).unwrap(), network)? + block_subsidy( + (network.slow_start_interval() + 1).unwrap(), + network, + Amount::zero() + )? ); assert_eq!( Amount::::try_from(1_250_000_000)?, - block_subsidy((blossom_height - 1).unwrap(), network)? + block_subsidy((blossom_height - 1).unwrap(), network, Amount::zero())? ); // After Blossom the block subsidy is reduced to 6.25 ZEC without halving // https://z.cash/upgrade/blossom/ assert_eq!( Amount::::try_from(625_000_000)?, - block_subsidy(blossom_height, network)? + block_subsidy(blossom_height, network, Amount::zero())? ); // After the 1st halving, the block subsidy is reduced to 3.125 ZEC // https://z.cash/upgrade/canopy/ assert_eq!( Amount::::try_from(312_500_000)?, - block_subsidy(first_halving_height, network)? + block_subsidy(first_halving_height, network, Amount::zero())? ); // After the 2nd halving, the block subsidy is reduced to 1.5625 ZEC @@ -335,105 +368,47 @@ mod test { Amount::::try_from(156_250_000)?, block_subsidy( (first_halving_height + POST_BLOSSOM_HALVING_INTERVAL).unwrap(), - network - )? - ); - - // After the 7th halving, the block subsidy is reduced to 0.04882812 ZEC - // Check that the block subsidy rounds down correctly, and there are no errors - assert_eq!( - Amount::::try_from(4_882_812)?, - block_subsidy( - (first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL * 6)).unwrap(), - network - )? - ); - - // After the 29th halving, the block subsidy is 1 zatoshi - // Check that the block subsidy is calculated correctly at the limit - assert_eq!( - Amount::::try_from(1)?, - block_subsidy( - (first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL * 28)).unwrap(), - network - )? - ); - - // After the 30th halving, there is no block subsidy - // Check that there are no errors - assert_eq!( - Amount::::try_from(0)?, - block_subsidy( - (first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL * 29)).unwrap(), - network - )? - ); - - assert_eq!( - Amount::::try_from(0)?, - block_subsidy( - (first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL * 39)).unwrap(), - network + network, + Amount::zero() )? ); - assert_eq!( - Amount::::try_from(0)?, - block_subsidy( - (first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL * 49)).unwrap(), - network - )? - ); + let zsf_activation_height = ZFuture + .activation_height(network) + .expect("ZFuture activation height should be available"); - assert_eq!( - Amount::::try_from(0)?, - block_subsidy( - (first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL * 59)).unwrap(), - network - )? - ); + // TODO: At ZSF activation the block subsidy is + // assert_eq!( + // Amount::::try_from()?, + // block_subsidy_pre_zsf( + // zsf_activation_height, + // network, + // zsf_balance_at_zsf_activation + // )? + // ); - // The largest possible integer divisor + // After ZSF activation the block subsidy is 0 if ZSF balance is 0 assert_eq!( Amount::::try_from(0)?, - block_subsidy( - (first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL * 62)).unwrap(), - network - )? + block_subsidy(zsf_activation_height, network, Amount::zero())? ); - // Other large divisors which should also result in zero + // After ZSF activation the block subsidy is 1 if ZSF balance is 1 assert_eq!( - Amount::::try_from(0)?, - block_subsidy( - (first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL * 63)).unwrap(), - network - )? + Amount::::try_from(1)?, + block_subsidy(zsf_activation_height, network, 1i64.try_into().unwrap())? ); + // After ZSF activation the block subsidy is 866460000 zatoshis when ZSF balance is MAX_MONEY assert_eq!( - Amount::::try_from(0)?, + Amount::::try_from(866460000)?, block_subsidy( - (first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL * 64)).unwrap(), - network + zsf_activation_height, + network, + MAX_MONEY.try_into().unwrap() )? ); - assert_eq!( - Amount::::try_from(0)?, - block_subsidy(Height(Height::MAX_AS_U32 / 4), network)? - ); - - assert_eq!( - Amount::::try_from(0)?, - block_subsidy(Height(Height::MAX_AS_U32 / 2), network)? - ); - - assert_eq!( - Amount::::try_from(0)?, - block_subsidy(Height::MAX, network)? - ); - Ok(()) } diff --git a/zebra-consensus/src/block/tests.rs b/zebra-consensus/src/block/tests.rs index d8f74bf7b2f..142c616f3d9 100644 --- a/zebra-consensus/src/block/tests.rs +++ b/zebra-consensus/src/block/tests.rs @@ -2,6 +2,7 @@ use color_eyre::eyre::{eyre, Report}; use once_cell::sync::Lazy; +use subsidy::general::block_subsidy_pre_zsf; use tower::{buffer::Buffer, util::BoxService}; use zebra_chain::{ @@ -20,7 +21,7 @@ use zebra_chain::{ use zebra_script::CachedFfiTransaction; use zebra_test::transcript::{ExpectedTranscriptError, Transcript}; -use crate::{block_subsidy, transaction}; +use crate::transaction; use super::*; @@ -303,10 +304,11 @@ fn subsidy_is_valid_for_network(network: Network) -> Result<(), Report> { // TODO: first halving, second halving, third halving, and very large halvings if height >= canopy_activation_height { - let expected_block_subsidy = - subsidy::general::block_subsidy(height, &network).expect("valid block subsidy"); + let expected_block_subsidy_pre_zsf = + subsidy::general::block_subsidy_pre_zsf(height, &network) + .expect("valid block subsidy"); - check::subsidy_is_valid(&block, &network, expected_block_subsidy) + check::subsidy_is_valid(&block, &network, expected_block_subsidy_pre_zsf) .expect("subsidies should pass for this block"); } } @@ -326,7 +328,7 @@ fn coinbase_validation_failure() -> Result<(), Report> { .expect("block should deserialize"); let mut block = Arc::try_unwrap(block).expect("block should unwrap"); - let expected_block_subsidy = subsidy::general::block_subsidy( + let expected_block_subsidy_pre_zsf = subsidy::general::block_subsidy_pre_zsf( block .coinbase_height() .expect("block should have coinbase height"), @@ -342,7 +344,8 @@ fn coinbase_validation_failure() -> Result<(), Report> { let expected = BlockError::NoTransactions; assert_eq!(expected, result); - let result = check::subsidy_is_valid(&block, &network, expected_block_subsidy).unwrap_err(); + let result = + check::subsidy_is_valid(&block, &network, expected_block_subsidy_pre_zsf).unwrap_err(); let expected = BlockError::Transaction(TransactionError::Subsidy(SubsidyError::NoCoinbase)); assert_eq!(expected, result); @@ -352,7 +355,7 @@ fn coinbase_validation_failure() -> Result<(), Report> { .expect("block should deserialize"); let mut block = Arc::try_unwrap(block).expect("block should unwrap"); - let expected_block_subsidy = subsidy::general::block_subsidy( + let expected_block_subsidy_pre_zsf = subsidy::general::block_subsidy_pre_zsf( block .coinbase_height() .expect("block should have coinbase height"), @@ -368,7 +371,8 @@ fn coinbase_validation_failure() -> Result<(), Report> { let expected = BlockError::Transaction(TransactionError::CoinbasePosition); assert_eq!(expected, result); - let result = check::subsidy_is_valid(&block, &network, expected_block_subsidy).unwrap_err(); + let result = + check::subsidy_is_valid(&block, &network, expected_block_subsidy_pre_zsf).unwrap_err(); let expected = BlockError::Transaction(TransactionError::Subsidy(SubsidyError::NoCoinbase)); assert_eq!(expected, result); @@ -392,7 +396,7 @@ fn coinbase_validation_failure() -> Result<(), Report> { let expected = BlockError::Transaction(TransactionError::CoinbaseAfterFirst); assert_eq!(expected, result); - let expected_block_subsidy = subsidy::general::block_subsidy( + let expected_block_subsidy_pre_zsf = subsidy::general::block_subsidy_pre_zsf( block .coinbase_height() .expect("block should have coinbase height"), @@ -400,7 +404,7 @@ fn coinbase_validation_failure() -> Result<(), Report> { ) .expect("valid block subsidy"); - check::subsidy_is_valid(&block, &network, expected_block_subsidy) + check::subsidy_is_valid(&block, &network, expected_block_subsidy_pre_zsf) .expect("subsidy does not check for extra coinbase transactions"); Ok(()) @@ -428,11 +432,12 @@ fn funding_stream_validation_for_network(network: Network) -> Result<(), Report> if height >= canopy_activation_height { let block = Block::zcash_deserialize(&block[..]).expect("block should deserialize"); - let expected_block_subsidy = - subsidy::general::block_subsidy(height, &network).expect("valid block subsidy"); + let expected_block_subsidy_pre_zsf = + subsidy::general::block_subsidy_pre_zsf(height, &network) + .expect("valid block subsidy"); // Validate - let result = check::subsidy_is_valid(&block, &network, expected_block_subsidy); + let result = check::subsidy_is_valid(&block, &network, expected_block_subsidy_pre_zsf); assert!(result.is_ok()); } } @@ -476,7 +481,7 @@ fn funding_stream_validation_failure() -> Result<(), Report> { }; // Validate it - let expected_block_subsidy = subsidy::general::block_subsidy( + let expected_block_subsidy_pre_zsf = subsidy::general::block_subsidy_pre_zsf( block .coinbase_height() .expect("block should have coinbase height"), @@ -484,7 +489,7 @@ fn funding_stream_validation_failure() -> Result<(), Report> { ) .expect("valid block subsidy"); - let result = check::subsidy_is_valid(&block, &network, expected_block_subsidy); + let result = check::subsidy_is_valid(&block, &network, expected_block_subsidy_pre_zsf); let expected = Err(BlockError::Transaction(TransactionError::Subsidy( SubsidyError::FundingStreamNotFound, ))); @@ -513,24 +518,24 @@ fn miner_fees_validation_for_network(network: Network) -> Result<(), Report> { &Block::zcash_deserialize(&block[..]).expect("block should deserialize"), )?; - let expected_block_subsidy = block_subsidy(height, &network)?; + let expected_block_subsidy_pre_zsf = block_subsidy_pre_zsf(height, &network)?; // TODO: Add link to lockbox stream ZIP let expected_deferred_amount = subsidy::funding_streams::funding_stream_values( height, &network, - expected_block_subsidy, + expected_block_subsidy_pre_zsf, ) .expect("we always expect a funding stream hashmap response even if empty") .remove(&FundingStreamReceiver::Deferred) .unwrap_or_default(); - assert!(check::miner_fees_are_valid( + assert!(check::transaction_miner_fees_are_valid( &coinbase_tx, height, // Set the miner fees to a high-enough amount. Amount::try_from(MAX_MONEY / 2).unwrap(), - expected_block_subsidy, + expected_block_subsidy_pre_zsf, expected_deferred_amount, &network, ) @@ -548,21 +553,24 @@ fn miner_fees_validation_failure() -> Result<(), Report> { let block = Block::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_347499_BYTES[..]) .expect("block should deserialize"); let height = block.coinbase_height().expect("valid coinbase height"); - let expected_block_subsidy = block_subsidy(height, &network)?; + let expected_block_subsidy_pre_zsf = block_subsidy_pre_zsf(height, &network)?; // TODO: Add link to lockbox stream ZIP - let expected_deferred_amount = - subsidy::funding_streams::funding_stream_values(height, &network, expected_block_subsidy) - .expect("we always expect a funding stream hashmap response even if empty") - .remove(&FundingStreamReceiver::Deferred) - .unwrap_or_default(); + let expected_deferred_amount = subsidy::funding_streams::funding_stream_values( + height, + &network, + expected_block_subsidy_pre_zsf, + ) + .expect("we always expect a funding stream hashmap response even if empty") + .remove(&FundingStreamReceiver::Deferred) + .unwrap_or_default(); assert_eq!( - check::miner_fees_are_valid( + check::transaction_miner_fees_are_valid( check::coinbase_is_first(&block)?.as_ref(), height, // Set the miner fee to an invalid amount. Amount::zero(), - expected_block_subsidy, + expected_block_subsidy_pre_zsf, expected_deferred_amount, &network ), @@ -574,6 +582,36 @@ fn miner_fees_validation_failure() -> Result<(), Report> { Ok(()) } +#[cfg(feature = "zsf")] +#[test] +fn miner_fees_validation_fails_when_zsf_deposit_is_zero() -> Result<(), Report> { + let transparent_value_balance = 100_001_000.try_into().unwrap(); + let sapling_value_balance = Amount::zero(); + let orchard_value_balance = Amount::zero(); + let zsf_deposit = Amount::zero(); + let expected_block_subsidy_pre_zsf = 100_000_000.try_into().unwrap(); + let block_miner_fees = 1000.try_into().unwrap(); + let expected_deferred_amount = Amount::zero(); + + assert_eq!( + check::miner_fees_are_valid( + transparent_value_balance, + sapling_value_balance, + orchard_value_balance, + zsf_deposit, + expected_block_subsidy_pre_zsf, + block_miner_fees, + expected_deferred_amount, + NetworkUpgrade::ZFuture + ), + Err(BlockError::Transaction(TransactionError::Subsidy( + SubsidyError::InvalidZsfDepositAmount, + ))) + ); + + Ok(()) +} + #[test] fn time_is_valid_for_historical_blocks() -> Result<(), Report> { let _init_guard = zebra_test::init(); diff --git a/zebra-consensus/src/checkpoint.rs b/zebra-consensus/src/checkpoint.rs index dfdf915b5be..9939eb3d48f 100644 --- a/zebra-consensus/src/checkpoint.rs +++ b/zebra-consensus/src/checkpoint.rs @@ -36,8 +36,7 @@ use zebra_chain::{ use zebra_state::{self as zs, CheckpointVerifiedBlock}; use crate::{ - block::VerifyBlockError, - block_subsidy, + block::{subsidy::general::block_subsidy_pre_zsf, VerifyBlockError}, checkpoint::types::{ Progress::{self, *}, TargetHeight::{self, *}, @@ -612,8 +611,12 @@ where // omit the calculation of the expected deferred amount. let expected_deferred_amount = if height > self.network.slow_start_interval() { // TODO: Add link to lockbox stream ZIP - funding_stream_values(height, &self.network, block_subsidy(height, &self.network)?)? - .remove(&FundingStreamReceiver::Deferred) + funding_stream_values( + height, + &self.network, + block_subsidy_pre_zsf(height, &self.network)?, + )? + .remove(&FundingStreamReceiver::Deferred) } else { None }; diff --git a/zebra-consensus/src/error.rs b/zebra-consensus/src/error.rs index 91dfbf0ce3a..3bd727143cb 100644 --- a/zebra-consensus/src/error.rs +++ b/zebra-consensus/src/error.rs @@ -43,6 +43,9 @@ pub enum SubsidyError { #[error("invalid amount")] InvalidAmount(amount::Error), + + #[error("ZSF deposit amount needs to be >= than 60% of transaction fees")] + InvalidZsfDepositAmount, } impl From for SubsidyError { diff --git a/zebra-consensus/src/script.rs b/zebra-consensus/src/script.rs index 5adbc18c105..03c8158b5e5 100644 --- a/zebra-consensus/src/script.rs +++ b/zebra-consensus/src/script.rs @@ -71,8 +71,14 @@ impl tower::Service for Verifier { let span = tracing::trace_span!("script", ?outpoint); async move { - cached_ffi_transaction.is_valid(branch_id, input_index)?; - tracing::trace!("script verification succeeded"); + tracing::trace!("script verification starting"); + let result = cached_ffi_transaction.is_valid(branch_id, input_index); + if let Err(e) = &result { + tracing::trace!(?e); + } + tracing::trace!("script verification done"); + + result?; Ok(()) } diff --git a/zebra-consensus/src/transaction.rs b/zebra-consensus/src/transaction.rs index 1c303003615..a524b7aeaf3 100644 --- a/zebra-consensus/src/transaction.rs +++ b/zebra-consensus/src/transaction.rs @@ -413,6 +413,18 @@ where sapling_shielded_data, orchard_shielded_data, )?, + #[cfg(feature = "zsf")] + Transaction::ZFuture { + sapling_shielded_data, + orchard_shielded_data, + .. } => Self::verify_zfuture_transaction( + &req, + &network, + script_verifier, + cached_ffi_transaction.clone(), + sapling_shielded_data, + orchard_shielded_data, + )?, }; if let Some(unmined_tx) = req.mempool_transaction() { @@ -476,6 +488,7 @@ where Response::Mempool { transaction } }, }; + tracing::trace!(?tx_id, "Verification finished"); Ok(rsp) } @@ -687,6 +700,9 @@ where transaction.version(), network_upgrade, )), + + #[cfg(feature = "zsf")] + NetworkUpgrade::ZFuture => Ok(()), } } @@ -767,6 +783,8 @@ where // Note: Here we verify the transaction version number of the above rule, the group // id is checked in zebra-chain crate, in the transaction serialize. NetworkUpgrade::Nu5 | NetworkUpgrade::Nu6 => Ok(()), + #[cfg(feature = "zsf")] + NetworkUpgrade::ZFuture => Ok(()), // Does not support V5 transactions NetworkUpgrade::Genesis @@ -782,6 +800,51 @@ where } } + /// Verify a ZFUTURE transaction. + #[cfg(feature = "zsf")] + fn verify_zfuture_transaction( + request: &Request, + network: &Network, + script_verifier: script::Verifier, + cached_ffi_transaction: Arc, + sapling_shielded_data: &Option>, + orchard_shielded_data: &Option, + ) -> Result { + let transaction = request.transaction(); + let upgrade = request.upgrade(network); + + if upgrade != NetworkUpgrade::ZFuture { + return Err(TransactionError::UnsupportedByNetworkUpgrade( + transaction.version(), + upgrade, + )); + } + + let shielded_sighash = transaction.sighash( + upgrade + .branch_id() + .expect("Overwinter-onwards must have branch ID, and we checkpoint on Canopy"), + HashType::ALL, + cached_ffi_transaction.all_previous_outputs(), + None, + ); + + Ok(Self::verify_transparent_inputs_and_outputs( + request, + network, + script_verifier, + cached_ffi_transaction, + )? + .and(Self::verify_sapling_shielded_data( + sapling_shielded_data, + &shielded_sighash, + )?) + .and(Self::verify_orchard_shielded_data( + orchard_shielded_data, + &shielded_sighash, + )?)) + } + /// Verifies if a transaction's transparent inputs are valid using the provided /// `script_verifier` and `cached_ffi_transaction`. /// diff --git a/zebra-consensus/src/transaction/check.rs b/zebra-consensus/src/transaction/check.rs index 66e3d0be595..ea4d96c4343 100644 --- a/zebra-consensus/src/transaction/check.rs +++ b/zebra-consensus/src/transaction/check.rs @@ -122,9 +122,14 @@ pub fn lock_time_has_passed( /// /// This check counts both `Coinbase` and `PrevOut` transparent inputs. pub fn has_inputs_and_outputs(tx: &Transaction) -> Result<(), TransactionError> { + #[cfg(feature = "zsf")] + let has_other_outputs = tx.has_zsf_deposit(); + #[cfg(not(feature = "zsf"))] + let has_other_outputs = false; + if !tx.has_transparent_or_shielded_inputs() { Err(TransactionError::NoInputs) - } else if !tx.has_transparent_or_shielded_outputs() { + } else if !tx.has_transparent_or_shielded_outputs() && !has_other_outputs { Err(TransactionError::NoOutputs) } else { Ok(()) diff --git a/zebra-consensus/src/transaction/tests/prop.rs b/zebra-consensus/src/transaction/tests/prop.rs index f45b4731de0..11023c29771 100644 --- a/zebra-consensus/src/transaction/tests/prop.rs +++ b/zebra-consensus/src/transaction/tests/prop.rs @@ -29,7 +29,7 @@ proptest! { (network, block_height) in sapling_onwards_strategy(), block_time in datetime_full(), relative_source_fund_heights in vec(0.0..1.0, 1..=MAX_TRANSPARENT_INPUTS), - transaction_version in 4_u8..=5, + transaction_version in 4_u8..=6, ) { let _init_guard = zebra_test::init(); @@ -300,6 +300,9 @@ fn mock_transparent_transaction( // Create the mock transaction let expiry_height = block_height; + #[cfg(feature = "tx_zfuture")] + let zsf_deposit = Amount::zero(); + let transaction = match transaction_version { 4 => Transaction::V4 { inputs, @@ -309,7 +312,17 @@ fn mock_transparent_transaction( joinsplit_data: None, sapling_shielded_data: None, }, - 5 => Transaction::V5 { + 5 | 6 => Transaction::V5 { + inputs, + outputs, + lock_time, + expiry_height, + sapling_shielded_data: None, + orchard_shielded_data: None, + network_upgrade, + }, + #[cfg(feature = "tx_zfuture")] + 255 => Transaction::ZFuture { inputs, outputs, lock_time, @@ -317,6 +330,7 @@ fn mock_transparent_transaction( sapling_shielded_data: None, orchard_shielded_data: None, network_upgrade, + zsf_deposit, }, invalid_version => unreachable!("invalid transaction version: {}", invalid_version), }; @@ -345,6 +359,7 @@ fn sanitize_transaction_version( Overwinter => 3, Sapling | Blossom | Heartwood | Canopy => 4, Nu5 | Nu6 => 5, + ZFuture => 0x00FF, } }; diff --git a/zebra-network/Cargo.toml b/zebra-network/Cargo.toml index 168105cb69d..dc0549e8158 100644 --- a/zebra-network/Cargo.toml +++ b/zebra-network/Cargo.toml @@ -27,6 +27,8 @@ categories = ["asynchronous", "cryptography::cryptocurrencies", "encoding", "net [features] default = [] +zsf = [] + # Production features that activate extra dependencies, or extra features in dependencies progress-bar = [ diff --git a/zebra-network/src/constants.rs b/zebra-network/src/constants.rs index d937e64729a..60b275c66cd 100644 --- a/zebra-network/src/constants.rs +++ b/zebra-network/src/constants.rs @@ -337,7 +337,10 @@ pub const TIMESTAMP_TRUNCATION_SECONDS: u32 = 30 * 60; /// /// The current protocol version typically changes before Mainnet and Testnet /// network upgrades. +#[cfg(not(feature = "zsf"))] pub const CURRENT_NETWORK_PROTOCOL_VERSION: Version = Version(170_100); +#[cfg(feature = "zsf")] +pub const CURRENT_NETWORK_PROTOCOL_VERSION: Version = Version(170_140); /// The default RTT estimate for peer responses. /// diff --git a/zebra-network/src/peer/minimum_peer_version/tests/prop.rs b/zebra-network/src/peer/minimum_peer_version/tests/prop.rs index c88fd7aa880..2fd68dbd0fa 100644 --- a/zebra-network/src/peer/minimum_peer_version/tests/prop.rs +++ b/zebra-network/src/peer/minimum_peer_version/tests/prop.rs @@ -17,7 +17,6 @@ proptest! { best_tip.send_best_tip_height(block_height); let expected_minimum_version = Version::min_remote_for_height(&network, block_height); - prop_assert_eq!(minimum_peer_version.current(), expected_minimum_version); } diff --git a/zebra-network/src/protocol/external/types.rs b/zebra-network/src/protocol/external/types.rs index 1048f33e308..fb52d4e4c1d 100644 --- a/zebra-network/src/protocol/external/types.rs +++ b/zebra-network/src/protocol/external/types.rs @@ -95,6 +95,10 @@ impl Version { (Mainnet, Nu5) => 170_100, (Testnet(params), Nu6) if params.is_default_testnet() => 170_110, (Mainnet, Nu6) => 170_120, + #[cfg(feature = "zsf")] + (Testnet(params), ZFuture) if params.is_default_testnet() => 170_130, + #[cfg(feature = "zsf")] + (Mainnet, ZFuture) => 170_140, // It should be fine to reject peers with earlier network protocol versions on custom testnets for now. (Testnet(_), _) => CURRENT_NETWORK_PROTOCOL_VERSION.0, @@ -194,6 +198,10 @@ mod test { let _init_guard = zebra_test::init(); let highest_network_upgrade = NetworkUpgrade::current(network, block::Height::MAX); + #[cfg(feature = "zsf")] + assert!(highest_network_upgrade == ZFuture, + "expected coverage of all network upgrades: add the new network upgrade to the list in this test"); + #[cfg(not(feature = "zsf"))] assert!(highest_network_upgrade == Nu6 || highest_network_upgrade == Nu5, "expected coverage of all network upgrades: add the new network upgrade to the list in this test"); @@ -206,6 +214,8 @@ mod test { Canopy, Nu5, Nu6, + #[cfg(feature = "zsf")] + ZFuture, ] { let height = network_upgrade.activation_height(network); if let Some(height) = height { diff --git a/zebra-rpc/Cargo.toml b/zebra-rpc/Cargo.toml index b2c1e98a8bc..2ae407b1da4 100644 --- a/zebra-rpc/Cargo.toml +++ b/zebra-rpc/Cargo.toml @@ -21,6 +21,8 @@ categories = [ [features] +zsf = [] + indexer-rpcs = [ "tonic-build", "tonic", diff --git a/zebra-rpc/src/methods/get_block_template_rpcs.rs b/zebra-rpc/src/methods/get_block_template_rpcs.rs index c8c83e9315a..38863e22c8d 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs.rs @@ -7,7 +7,8 @@ use jsonrpc_core::{self, BoxFuture, Error, ErrorCode, Result}; use jsonrpc_derive::rpc; use tower::{Service, ServiceExt}; -use zcash_address::{unified::Encoding, TryFromAddress}; +use zcash_address::unified::Encoding; +use zcash_address::TryFromAddress; use zebra_chain::{ amount::{self, Amount, NonNegative}, @@ -1154,6 +1155,7 @@ where fn get_block_subsidy(&self, height: Option) -> BoxFuture> { let latest_chain_tip = self.latest_chain_tip.clone(); let network = self.network.clone(); + let mut state_service = self.state.clone(); async move { let height = if let Some(height) = height { @@ -1175,7 +1177,33 @@ where // Always zero for post-halving blocks let founders = Amount::zero(); - let total_block_subsidy = block_subsidy(height, &network).map_server_error()?; + let service = state_service.ready().await.map_err(|_| Error { + code: ErrorCode::InternalError, + message: "".into(), + data: None, + })?; + + let tip_pool_values = + service + .call(ReadRequest::TipPoolValues) + .await + .map_err(|_| Error { + code: ErrorCode::InternalError, + message: "".into(), + data: None, + }); + + let zsf_balance = match tip_pool_values? { + ReadResponse::TipPoolValues { + tip_hash: _, + tip_height: _, + value_balance, + } => value_balance.zsf_balance(), + _ => unreachable!("wrong response to Request::KnownBlock"), + }; + + let total_block_subsidy = + block_subsidy(height, &network, zsf_balance).map_server_error()?; let miner_subsidy = miner_subsidy(height, &network, total_block_subsidy).map_server_error()?; 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..1d4f28e4fd4 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 @@ -6,7 +6,7 @@ use jsonrpc_core::{Error, ErrorCode, Result}; use tower::{Service, ServiceExt}; use zebra_chain::{ - amount::{self, Amount, NegativeOrZero, NonNegative}, + amount::{self, Amount, NegativeOrZero, NonNegative, MAX_MONEY}, block::{ self, merkle::{self, AuthDataRoot}, @@ -376,7 +376,9 @@ pub fn standard_coinbase_outputs( miner_fee: Amount, like_zcashd: bool, ) -> Vec<(Amount, transparent::Script)> { - let expected_block_subsidy = block_subsidy(height, network).expect("valid block subsidy"); + let max_money = MAX_MONEY.try_into().expect("MAX_MONEY is a valid amount"); + let expected_block_subsidy = + block_subsidy(height, network, max_money).expect("valid block subsidy"); let funding_streams = funding_stream_values(height, network, expected_block_subsidy) .expect("funding stream value calculations are valid for reasonable chain heights"); diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/proposal.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/proposal.rs index fc0805b533d..1b3b9c08e7e 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/proposal.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/proposal.rs @@ -220,6 +220,8 @@ pub fn proposal_block_from_template( NetworkUpgrade::Nu5 | NetworkUpgrade::Nu6 => { block_commitments_hash.bytes_in_serialized_order().into() } + #[cfg(feature = "zsf")] + NetworkUpgrade::ZFuture => block_commitments_hash.bytes_in_serialized_order().into(), }; Ok(Block { diff --git a/zebra-rpc/src/methods/tests/snapshot/snapshots/get_block_subsidy_excessive_height@mainnet_10.snap b/zebra-rpc/src/methods/tests/snapshot/snapshots/get_block_subsidy_excessive_height@mainnet_10.snap index 0183f2cd242..2cd744940de 100644 --- a/zebra-rpc/src/methods/tests/snapshot/snapshots/get_block_subsidy_excessive_height@mainnet_10.snap +++ b/zebra-rpc/src/methods/tests/snapshot/snapshots/get_block_subsidy_excessive_height@mainnet_10.snap @@ -3,9 +3,9 @@ source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs expression: get_block_subsidy --- { - "miner": 0.00610351, + "miner": 8.66459999, "founders": 0.0, "fundingstreamstotal": 0.0, "lockboxtotal": 0.0, - "totalblocksubsidy": 0.00610351 + "totalblocksubsidy": 8.66459999 } diff --git a/zebra-rpc/src/methods/tests/snapshot/snapshots/get_block_subsidy_excessive_height@testnet_10.snap b/zebra-rpc/src/methods/tests/snapshot/snapshots/get_block_subsidy_excessive_height@testnet_10.snap index 0183f2cd242..2cd744940de 100644 --- a/zebra-rpc/src/methods/tests/snapshot/snapshots/get_block_subsidy_excessive_height@testnet_10.snap +++ b/zebra-rpc/src/methods/tests/snapshot/snapshots/get_block_subsidy_excessive_height@testnet_10.snap @@ -3,9 +3,9 @@ source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs expression: get_block_subsidy --- { - "miner": 0.00610351, + "miner": 8.66459999, "founders": 0.0, "fundingstreamstotal": 0.0, "lockboxtotal": 0.0, - "totalblocksubsidy": 0.00610351 + "totalblocksubsidy": 8.66459999 } 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 64663b86102..28b7bb15d75 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 @@ -64,6 +64,16 @@ expression: info "name": "NU5", "activationheight": 1687104, "status": "pending" + }, + "c8e71055": { + "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 d5c7d4b5336..825405b4cf0 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 @@ -64,6 +64,16 @@ expression: info "name": "NU5", "activationheight": 1842420, "status": "pending" + }, + "c8e71055": { + "name": "Nu6", + "activationheight": 2900000, + "status": "pending" + }, + "ffffffff": { + "name": "ZFuture", + "activationheight": 3000000, + "status": "pending" } }, "consensus": { diff --git a/zebra-state/Cargo.toml b/zebra-state/Cargo.toml index 5c8aa288854..90d0fa957c4 100644 --- a/zebra-state/Cargo.toml +++ b/zebra-state/Cargo.toml @@ -16,6 +16,12 @@ categories = ["asynchronous", "caching", "cryptography::cryptocurrencies"] [features] +default = [] + +zsf = [ + "zebra-chain/zsf", +] + # Production features that activate extra dependencies, or extra features in dependencies progress-bar = [ diff --git a/zebra-state/src/request.rs b/zebra-state/src/request.rs index 28740a336bb..ba61c7cbce9 100644 --- a/zebra-state/src/request.rs +++ b/zebra-state/src/request.rs @@ -625,6 +625,9 @@ pub enum Request { /// with the current best chain tip. Tip, + #[cfg(feature = "zsf")] + TipPoolValues, + /// Computes a block locator object based on the current best chain. /// /// Returns [`Response::BlockLocator`] with hashes starting @@ -778,6 +781,8 @@ impl Request { Request::AwaitUtxo(_) => "await_utxo", Request::Depth(_) => "depth", Request::Tip => "tip", + #[cfg(feature = "zsf")] + Request::TipPoolValues => "tip_pool_values", Request::BlockLocator => "block_locator", Request::Transaction(_) => "transaction", Request::UnspentBestChainUtxo { .. } => "unspent_best_chain_utxo", @@ -1121,6 +1126,7 @@ impl TryFrom for ReadRequest { fn try_from(request: Request) -> Result { match request { Request::Tip => Ok(ReadRequest::Tip), + Request::TipPoolValues => Ok(ReadRequest::TipPoolValues), Request::Depth(hash) => Ok(ReadRequest::Depth(hash)), Request::BestChainNextMedianTimePast => Ok(ReadRequest::BestChainNextMedianTimePast), Request::BestChainBlockHash(hash) => Ok(ReadRequest::BestChainBlockHash(hash)), diff --git a/zebra-state/src/response.rs b/zebra-state/src/response.rs index 22e610838de..c25f17b0367 100644 --- a/zebra-state/src/response.rs +++ b/zebra-state/src/response.rs @@ -39,6 +39,15 @@ pub enum Response { // `LatestChainTip::best_tip_height_and_hash()` Tip(Option<(block::Height, block::Hash)>), + TipPoolValues { + /// The current best chain tip height. + tip_height: block::Height, + /// The current best chain tip hash. + tip_hash: block::Hash, + /// The value pool balance at the current best chain tip. + value_balance: ValueBalance, + }, + /// Response to [`Request::BlockLocator`] with a block locator object. BlockLocator(Vec), @@ -79,6 +88,9 @@ pub enum Response { /// Response to [`Request::KnownBlock`]. KnownBlock(Option), + /// Response to [`Request::ValuePools`] with the total value balance of the chain. + ValuePools(ValueBalance), + #[cfg(feature = "getblocktemplate-rpcs")] /// Response to [`Request::CheckBlockProposalValidity`] ValidBlockProposal, @@ -217,6 +229,8 @@ pub enum ReadResponse { /// Response to [`ReadRequest::BestChainBlockHash`] with the specified block hash. BlockHash(Option), + ValuePools(ValueBalance), + #[cfg(feature = "getblocktemplate-rpcs")] /// Response to [`ReadRequest::ChainInfo`] with the state /// information needed by the `getblocktemplate` RPC method. @@ -311,6 +325,8 @@ impl TryFrom for Response { Err("there is no corresponding Response for this ReadResponse") } + ReadResponse::ValuePools(value_pools) => Ok(Response::ValuePools(value_pools)), + #[cfg(feature = "getblocktemplate-rpcs")] ReadResponse::ValidBlockProposal => Ok(Response::ValidBlockProposal), diff --git a/zebra-state/src/service.rs b/zebra-state/src/service.rs index 2116ab10470..ea68f5c03d3 100644 --- a/zebra-state/src/service.rs +++ b/zebra-state/src/service.rs @@ -1112,6 +1112,24 @@ impl Service for StateService { .boxed() } + #[cfg(feature = "zsf")] + Request::TipPoolValues => { + // Redirect the request to the concurrent ReadStateService + let read_service = self.read_service.clone(); + + async move { + let req = req + .try_into() + .expect("ReadRequest conversion should not fail"); + + let rsp = read_service.oneshot(req).await?; + let rsp = rsp.try_into().expect("Response conversion should not fail"); + + Ok(rsp) + } + .boxed() + } + #[cfg(feature = "getblocktemplate-rpcs")] Request::CheckBlockProposalValidity(_) => { // Redirect the request to the concurrent ReadStateService diff --git a/zebra-state/src/service/non_finalized_state/chain.rs b/zebra-state/src/service/non_finalized_state/chain.rs index 12ee0528776..ba6a11a24c1 100644 --- a/zebra-state/src/service/non_finalized_state/chain.rs +++ b/zebra-state/src/service/non_finalized_state/chain.rs @@ -1524,6 +1524,8 @@ impl Chain { V1 { .. } | V2 { .. } | V3 { .. } => unreachable!( "older transaction versions only exist in finalized blocks, because of the mandatory canopy checkpoint", ), + #[cfg(feature = "zsf")] + ZFuture => todo!("") }; // add key `transaction.hash` and value `(height, tx_index)` to `tx_loc_by_hash` @@ -1685,6 +1687,21 @@ impl UpdateWith for Chain { V1 { .. } | V2 { .. } | V3 { .. } => unreachable!( "older transaction versions only exist in finalized blocks, because of the mandatory canopy checkpoint", ), + #[cfg(feature = "zsf")] + ZFuture { + inputs, + outputs, + sapling_shielded_data, + orchard_shielded_data, + .. + } => ( + inputs, + outputs, + &None, + &None, + sapling_shielded_data, + orchard_shielded_data, + ), }; // remove the utxos this produced diff --git a/zebra-state/src/tests.rs b/zebra-state/src/tests.rs index 488ab4227bd..a3016392b9c 100644 --- a/zebra-state/src/tests.rs +++ b/zebra-state/src/tests.rs @@ -34,6 +34,8 @@ impl FakeChainHelper for Arc { Transaction::V3 { inputs, .. } => &mut inputs[0], Transaction::V4 { inputs, .. } => &mut inputs[0], Transaction::V5 { inputs, .. } => &mut inputs[0], + #[cfg(feature = "zsf")] + Transaction::ZFuture { inputs, .. } => &mut inputs[0], }; match input { diff --git a/zebrad/Cargo.toml b/zebrad/Cargo.toml index 525c3adb609..440416ee999 100644 --- a/zebrad/Cargo.toml +++ b/zebrad/Cargo.toml @@ -156,6 +156,8 @@ test_sync_to_mandatory_checkpoint_testnet = [] test_sync_past_mandatory_checkpoint_mainnet = [] test_sync_past_mandatory_checkpoint_testnet = [] +zsf = ["zebra-chain/zsf", "zebra-network/zsf", "zebra-consensus/zsf", "zebra-rpc/zsf"] + [dependencies] zebra-chain = { path = "../zebra-chain", version = "1.0.0-beta.38" } zebra-consensus = { path = "../zebra-consensus", version = "1.0.0-beta.38" } diff --git a/zebrad/src/components/mempool/storage/tests/prop.rs b/zebrad/src/components/mempool/storage/tests/prop.rs index eca65935acb..a8cd5ef925e 100644 --- a/zebrad/src/components/mempool/storage/tests/prop.rs +++ b/zebrad/src/components/mempool/storage/tests/prop.rs @@ -568,6 +568,9 @@ impl SpendConflictTestInput { // No JoinSplits Transaction::V1 { .. } | Transaction::V5 { .. } => {} + + #[cfg(feature = "zsf")] + Transaction::ZFuture { .. } => {} } } } @@ -638,6 +641,14 @@ impl SpendConflictTestInput { Self::remove_sapling_transfers_with_conflicts(sapling_shielded_data, &conflicts) } + #[cfg(feature = "zsf")] + Transaction::ZFuture { + sapling_shielded_data, + .. + } => { + Self::remove_sapling_transfers_with_conflicts(sapling_shielded_data, &conflicts) + } + // No Spends Transaction::V1 { .. } | Transaction::V2 { .. } | Transaction::V3 { .. } => {} } @@ -709,6 +720,12 @@ impl SpendConflictTestInput { .. } => Self::remove_orchard_actions_with_conflicts(orchard_shielded_data, &conflicts), + #[cfg(feature = "zsf")] + Transaction::ZFuture { + orchard_shielded_data, + .. + } => Self::remove_orchard_actions_with_conflicts(orchard_shielded_data, &conflicts), + // No Spends Transaction::V1 { .. } | Transaction::V2 { .. }