diff --git a/Cargo.lock b/Cargo.lock index c9e9dcbcb5e..b42e8d04d14 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1343,6 +1343,15 @@ dependencies = [ "byteorder", ] +[[package]] +name = "equihash" +version = "0.2.0" +source = "git+https://github.com/ShieldedLabs/librustzcash/?branch=nsm-zebra#5fcd3034e47d425df4846192abd4d84fb3752207" +dependencies = [ + "blake2b_simd", + "byteorder", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -1372,8 +1381,7 @@ dependencies = [ [[package]] name = "f4jumble" version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a83e8d7fd0c526af4aad893b7c9fe41e2699ed8a776a6c74aecdeafe05afc75" +source = "git+https://github.com/ShieldedLabs/librustzcash/?branch=nsm-zebra#5fcd3034e47d425df4846192abd4d84fb3752207" dependencies = [ "blake2b_simd", ] @@ -5549,14 +5557,14 @@ dependencies = [ [[package]] name = "which" -version = "4.4.2" +version = "6.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +checksum = "b4ee928febd44d98f2f459a4a79bd4d928591333a494a10a868418ac1b39cf1f" dependencies = [ "either", "home", - "once_cell", "rustix", + "winsafe", ] [[package]] @@ -5806,6 +5814,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" + [[package]] name = "wyz" version = "0.5.1" @@ -5836,8 +5850,7 @@ checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546" [[package]] name = "zcash_address" version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ff95eac82f71286a79c750e674550d64fb2b7aadaef7b89286b2917f645457d" +source = "git+https://github.com/ShieldedLabs/librustzcash/?branch=nsm-zebra#5fcd3034e47d425df4846192abd4d84fb3752207" dependencies = [ "bech32", "bs58", @@ -5849,8 +5862,7 @@ dependencies = [ [[package]] name = "zcash_client_backend" version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbeeede366fdb642710d3c59fc2090489affd075f66db53ed11bb7138d2d0258" +source = "git+https://github.com/ShieldedLabs/librustzcash/?branch=nsm-zebra#5fcd3034e47d425df4846192abd4d84fb3752207" dependencies = [ "base64 0.22.1", "bech32", @@ -5889,8 +5901,7 @@ dependencies = [ [[package]] name = "zcash_encoding" version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052d8230202f0a018cd9b5d1b56b94cd25e18eccc2d8665073bcea8261ab87fc" +source = "git+https://github.com/ShieldedLabs/librustzcash/?branch=nsm-zebra#5fcd3034e47d425df4846192abd4d84fb3752207" dependencies = [ "byteorder", "nonempty", @@ -5899,8 +5910,7 @@ dependencies = [ [[package]] name = "zcash_history" version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fde17bf53792f9c756b313730da14880257d7661b5bfc69d0571c3a7c11a76d" +source = "git+https://github.com/ShieldedLabs/librustzcash/?branch=nsm-zebra#5fcd3034e47d425df4846192abd4d84fb3752207" dependencies = [ "blake2b_simd", "byteorder", @@ -5910,8 +5920,7 @@ dependencies = [ [[package]] name = "zcash_keys" version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8162c94957f1e379b8e2fb30f97b95cfa93ac9c6bc02895946ca6392d1abb81" +source = "git+https://github.com/ShieldedLabs/librustzcash/?branch=nsm-zebra#5fcd3034e47d425df4846192abd4d84fb3752207" dependencies = [ "bech32", "blake2b_simd", @@ -5949,8 +5958,7 @@ dependencies = [ [[package]] name = "zcash_primitives" version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ab47d526d7fd6f88b3a2854ad81b54757a80c2aeadd1d8b06f690556af9743c" +source = "git+https://github.com/ShieldedLabs/librustzcash/?branch=nsm-zebra#5fcd3034e47d425df4846192abd4d84fb3752207" dependencies = [ "aes", "bip32", @@ -5958,7 +5966,7 @@ dependencies = [ "bs58", "byteorder", "document-features", - "equihash", + "equihash 0.2.0 (git+https://github.com/ShieldedLabs/librustzcash/?branch=nsm-zebra)", "ff", "fpe", "group", @@ -5988,8 +5996,7 @@ dependencies = [ [[package]] name = "zcash_proofs" version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daba607872e60d91a09248d8e1ea3d6801c819fb80d67016d9de02d81323c10d" +source = "git+https://github.com/ShieldedLabs/librustzcash/?branch=nsm-zebra#5fcd3034e47d425df4846192abd4d84fb3752207" dependencies = [ "bellman", "blake2b_simd", @@ -6011,8 +6018,7 @@ dependencies = [ [[package]] name = "zcash_protocol" version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bc22b9155b2c7eb20105cd06de170d188c1bc86489b92aa3fda7b8da8d96acf" +source = "git+https://github.com/ShieldedLabs/librustzcash/?branch=nsm-zebra#5fcd3034e47d425df4846192abd4d84fb3752207" dependencies = [ "document-features", "memuse", @@ -6054,7 +6060,7 @@ dependencies = [ "criterion", "dirs", "ed25519-zebra", - "equihash", + "equihash 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "futures", "group", "halo2_proofs", @@ -6553,8 +6559,7 @@ dependencies = [ [[package]] name = "zip321" version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3e613defb0940acef1f54774b51c7f48f2fa705613dd800870dc69f35cd2ea" +source = "git+https://github.com/ShieldedLabs/librustzcash/?branch=nsm-zebra#5fcd3034e47d425df4846192abd4d84fb3752207" dependencies = [ "base64 0.22.1", "nom", diff --git a/Cargo.toml b/Cargo.toml index c50c93ad414..406d1c50442 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,14 +25,15 @@ resolver = "2" incrementalmerkletree = { version = "0.7.0", features = ["legacy-api"] } orchard = "0.10.0" sapling-crypto = "0.3.0" -zcash_address = "0.6.0" -zcash_client_backend = "0.14.0" -zcash_encoding = "0.2.1" -zcash_history = "0.4.0" -zcash_keys = "0.4.0" -zcash_primitives = "0.19.0" -zcash_proofs = "0.19.0" -zcash_protocol = "0.4.0" +# TODO: Revert to a release once librustzcash is released (#8749). +zcash_address = { git = "https://github.com/ShieldedLabs/librustzcash/", branch = "nsm-zebra" } +zcash_client_backend = { git = "https://github.com/ShieldedLabs/librustzcash/", branch = "nsm-zebra" } +zcash_encoding = { git = "https://github.com/ShieldedLabs/librustzcash/", branch = "nsm-zebra" } +zcash_history = { git = "https://github.com/ShieldedLabs/librustzcash/", branch = "nsm-zebra" } +zcash_keys = { git = "https://github.com/ShieldedLabs/librustzcash/", branch = "nsm-zebra" } +zcash_primitives = { git = "https://github.com/ShieldedLabs/librustzcash/", branch = "nsm-zebra" } +zcash_proofs = { git = "https://github.com/ShieldedLabs/librustzcash/", branch = "nsm-zebra" } +zcash_protocol = { git = "https://github.com/ShieldedLabs/librustzcash/", branch = "nsm-zebra" } [workspace.metadata.release] diff --git a/zebra-chain/Cargo.toml b/zebra-chain/Cargo.toml index b43e77f149a..b35dafb5b5b 100644 --- a/zebra-chain/Cargo.toml +++ b/zebra-chain/Cargo.toml @@ -17,6 +17,8 @@ categories = ["asynchronous", "cryptography::cryptocurrencies", "encoding"] [features] default = [] +nsm = [] + # Production features that activate extra functionality # Consensus-critical conversion from JSON to Zcash types @@ -178,3 +180,6 @@ required-features = ["bench"] [[bench]] name = "redpallas" harness = false + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(zcash_unstable, values("nsm"))'] } 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..4b0e50cf620 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(zcash_unstable = "nsm")] + (ZFuture, _) => Ok(ChainHistoryBlockTxAuthCommitment( + ChainHistoryBlockTxAuthCommitmentHash(bytes), + )), } } diff --git a/zebra-chain/src/history_tree.rs b/zebra-chain/src/history_tree.rs index 91fa3a17628..32ac12c039e 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(zcash_unstable = "nsm")] + 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(zcash_unstable = "nsm")] + 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 78f7a69a302..cf42515420f 100644 --- a/zebra-chain/src/parameters/network/testnet.rs +++ b/zebra-chain/src/parameters/network/testnet.rs @@ -206,6 +206,9 @@ pub struct ConfiguredActivationHeights { /// Activation height for `NU6` network upgrade. #[serde(rename = "NU6")] pub nu6: Option, + #[cfg(zcash_unstable = "nsm")] + #[serde(rename = "ZFuture")] + pub zfuture: Option, } /// Builder for the [`Parameters`] struct. @@ -336,6 +339,8 @@ impl ParametersBuilder { canopy, nu5, nu6, + #[cfg(zcash_unstable = "nsm")] + zfuture, }: ConfiguredActivationHeights, ) -> Self { use NetworkUpgrade::*; @@ -348,7 +353,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))) @@ -357,7 +362,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(zcash_unstable = "nsm")] + 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(); @@ -604,6 +615,8 @@ impl Parameters { canopy: Some(1), nu5: nu5_activation_height, nu6: nu6_activation_height, + #[cfg(zcash_unstable = "nsm")] + zfuture: nu5_activation_height.map(|height| height + 1), ..Default::default() }) .with_halving_interval(PRE_BLOSSOM_REGTEST_HALVING_INTERVAL); diff --git a/zebra-chain/src/parameters/network/tests/vectors.rs b/zebra-chain/src/parameters/network/tests/vectors.rs index 4282c86844f..0874cf6900f 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(zcash_unstable = "nsm")] + 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(zcash_unstable = "nsm"))] nu6: Some(expected_activation_height), + #[cfg(zcash_unstable = "nsm")] + 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 (#8920). (Height(100), NetworkUpgrade::Nu5), + #[cfg(zcash_unstable = "nsm")] + (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 57165d0c760..15c6b8b4ca6 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(zcash_unstable = "nsm"))] pub const NETWORK_UPGRADES_IN_ORDER: [NetworkUpgrade; 9] = [ Genesis, BeforeOverwinter, @@ -27,6 +28,21 @@ pub const NETWORK_UPGRADES_IN_ORDER: [NetworkUpgrade; 9] = [ Nu6, ]; +#[cfg(zcash_unstable = "nsm")] +pub const NETWORK_UPGRADES_IN_ORDER: [NetworkUpgrade; 10] = [ + Genesis, + BeforeOverwinter, + Overwinter, + Sapling, + Blossom, + Heartwood, + Canopy, + Nu5, + Nu6, + #[cfg(zcash_unstable = "nsm")] + ZFuture, +]; + /// A Zcash network upgrade. /// /// Network upgrades change the Zcash network protocol or consensus rules. Note that they have no @@ -61,6 +77,10 @@ pub enum NetworkUpgrade { /// The Zcash protocol after the NU6 upgrade. #[serde(rename = "NU6")] Nu6, + #[cfg(zcash_unstable = "nsm")] + #[serde(rename = "ZFuture")] + #[allow(non_snake_case)] + ZFuture, } impl fmt::Display for NetworkUpgrade { @@ -89,7 +109,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: Update NU6 activation height once it's been specified. (block::Height(2_726_400), Nu6), + #[cfg(zcash_unstable = "nsm")] + (block::Height(3_000_000), ZFuture), ]; /// Fake mainnet network upgrade activation heights, used in tests. @@ -104,6 +127,8 @@ const FAKE_MAINNET_ACTIVATION_HEIGHTS: &[(block::Height, NetworkUpgrade)] = &[ (block::Height(30), Canopy), (block::Height(35), Nu5), (block::Height(40), Nu6), + #[cfg(zcash_unstable = "nsm")] + (block::Height(45), ZFuture), ]; /// Testnet network upgrade activation heights. @@ -126,6 +151,8 @@ pub(super) const TESTNET_ACTIVATION_HEIGHTS: &[(block::Height, NetworkUpgrade)] (block::Height(1_028_500), Canopy), (block::Height(1_842_420), Nu5), (block::Height(2_976_000), Nu6), + #[cfg(zcash_unstable = "nsm")] + (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(zcash_unstable = "nsm")] + (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(zcash_unstable = "nsm")] + (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(zcash_unstable = "nsm"))] Nu6 => None, + #[cfg(zcash_unstable = "nsm")] + Nu6 => Some(ZFuture), + #[cfg(zcash_unstable = "nsm")] + 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(zcash_unstable = "nsm")] + 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(zcash_unstable = "nsm")] + 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..121d38933bd 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(zcash_unstable = "nsm")] +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..c8c9bac8bc3 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(zcash_unstable = "nsm")] + 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..7a8ed9afcb6 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(zcash_unstable = "nsm")] + 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..27822189a28 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(zcash_unstable = "nsm")] +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(zcash_unstable = "nsm")] + #[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 burn amount for this transaction, if any. + burn_amount: 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(zcash_unstable = "nsm")] + fmter.field("burn_amount", &self.burn_amount()); 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(zcash_unstable = "nsm")] + Transaction::ZFuture { .. } => Some(AuthDigest::from(self)), } } @@ -276,6 +306,11 @@ impl Transaction { !self.outputs().is_empty() || self.has_shielded_outputs() } + #[cfg(zcash_unstable = "nsm")] + pub fn has_burn_amount(&self) -> bool { + self.burn_amount() > 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(zcash_unstable = "nsm")] + Transaction::ZFuture { .. } => true, } } @@ -332,6 +369,8 @@ impl Transaction { Transaction::V3 { .. } => 3, Transaction::V4 { .. } => 4, Transaction::V5 { .. } => 5, + #[cfg(zcash_unstable = "nsm")] + 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(zcash_unstable = "nsm")] + 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(zcash_unstable = "nsm")] + 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(zcash_unstable = "nsm")] + 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(zcash_unstable = "nsm")] + Transaction::ZFuture { + ref mut expiry_height, + .. + } => expiry_height, } } @@ -468,6 +524,10 @@ impl Transaction { Transaction::V5 { network_upgrade, .. } => Some(*network_upgrade), + #[cfg(zcash_unstable = "nsm")] + 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(zcash_unstable = "nsm")] + 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(zcash_unstable = "nsm")] + 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(zcash_unstable = "nsm")] + Transaction::ZFuture { ref outputs, .. } => outputs, } } @@ -533,6 +599,10 @@ impl Transaction { Transaction::V5 { ref mut outputs, .. } => outputs, + #[cfg(zcash_unstable = "nsm")] + Transaction::ZFuture { + ref mut outputs, .. + } => outputs, } } @@ -581,6 +651,8 @@ impl Transaction { .. } | Transaction::V5 { .. } => Box::new(std::iter::empty()), + #[cfg(zcash_unstable = "nsm")] + Transaction::ZFuture { .. } => Box::new(std::iter::empty()), } } @@ -616,6 +688,8 @@ impl Transaction { .. } | Transaction::V5 { .. } => 0, + #[cfg(zcash_unstable = "nsm")] + Transaction::ZFuture { .. } => 0, } } @@ -655,6 +729,8 @@ impl Transaction { .. } | Transaction::V5 { .. } => Box::new(std::iter::empty()), + #[cfg(zcash_unstable = "nsm")] + Transaction::ZFuture { .. } => Box::new(std::iter::empty()), } } @@ -691,6 +767,8 @@ impl Transaction { .. } | Transaction::V5 { .. } => None, + #[cfg(zcash_unstable = "nsm")] + Transaction::ZFuture { .. } => None, } } @@ -699,6 +777,8 @@ impl Transaction { match self { // No JoinSplits Transaction::V1 { .. } | Transaction::V5 { .. } => false, + #[cfg(zcash_unstable = "nsm")] + 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(zcash_unstable = "nsm")] + 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(zcash_unstable = "nsm")] + 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(zcash_unstable = "nsm")] + 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(zcash_unstable = "nsm")] + 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(zcash_unstable = "nsm")] + 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(zcash_unstable = "nsm")] + 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(zcash_unstable = "nsm")] + 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(zcash_unstable = "nsm")] + 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(zcash_unstable = "nsm")] + 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(zcash_unstable = "nsm")] + 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(zcash_unstable = "nsm")] + 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(zcash_unstable = "nsm")] + 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(zcash_unstable = "nsm")] + 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(zcash_unstable = "nsm")] + 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(zcash_unstable = "nsm")] + Transaction::ZFuture { + orchard_shielded_data: None, + .. + } => None, } } @@ -1087,6 +1239,8 @@ impl Transaction { .. } | Transaction::V5 { .. } => Box::new(std::iter::empty()), + #[cfg(zcash_unstable = "nsm")] + Transaction::ZFuture { .. } => Box::new(std::iter::empty()), } } @@ -1136,6 +1290,8 @@ impl Transaction { .. } | Transaction::V5 { .. } => Box::new(std::iter::empty()), + #[cfg(zcash_unstable = "nsm")] + Transaction::ZFuture { .. } => Box::new(std::iter::empty()), } } @@ -1183,6 +1339,8 @@ impl Transaction { .. } | Transaction::V5 { .. } => Box::new(std::iter::empty()), + #[cfg(zcash_unstable = "nsm")] + Transaction::ZFuture { .. } => Box::new(std::iter::empty()), } } @@ -1232,6 +1390,8 @@ impl Transaction { .. } | Transaction::V5 { .. } => Box::new(std::iter::empty()), + #[cfg(zcash_unstable = "nsm")] + Transaction::ZFuture { .. } => Box::new(std::iter::empty()), } } @@ -1273,6 +1433,8 @@ impl Transaction { .. } | Transaction::V5 { .. } => Box::new(iter::empty()), + #[cfg(zcash_unstable = "nsm")] + 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(zcash_unstable = "nsm")] + 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(zcash_unstable = "nsm")] + 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(zcash_unstable = "nsm")] + 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(zcash_unstable = "nsm")] + Transaction::ZFuture { + sapling_shielded_data: None, + .. + } => None, } } @@ -1391,6 +1573,10 @@ impl Transaction { } /// 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 +1613,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(zcash_unstable = "nsm")] + pub fn burn_amount(&self) -> Amount { + match self { + Transaction::V1 { .. } + | Transaction::V2 { .. } + | Transaction::V3 { .. } + | Transaction::V4 { .. } + | Transaction::V5 { .. } => Amount::zero(), + Transaction::ZFuture { burn_amount, .. } => *burn_amount, + } + } } diff --git a/zebra-chain/src/transaction/arbitrary.rs b/zebra-chain/src/transaction/arbitrary.rs index cf4aa7a9552..b75e6286201 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(zcash_unstable = "nsm")] + 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(zcash_unstable = "nsm")] + ZFuture { .. } => todo!(), } } diff --git a/zebra-chain/src/transaction/builder.rs b/zebra-chain/src/transaction/builder.rs index 37397353aab..227658ad479 100644 --- a/zebra-chain/src/transaction/builder.rs +++ b/zebra-chain/src/transaction/builder.rs @@ -9,6 +9,119 @@ use crate::{ }; impl Transaction { + /// Returns a new version zfuture coinbase transaction for `network` and `height`, + /// which contains the specified `outputs`. + #[cfg(zcash_unstable = "nsm")] + pub fn new_zfuture_coinbase( + network: &Network, + height: Height, + outputs: impl IntoIterator, transparent::Script)>, + extra_coinbase_data: Vec, + like_zcashd: bool, + burn_amount: Option>, + ) -> Transaction { + let mut extra_data = None; + let mut sequence = None; + + // `zcashd` includes an extra byte after the coinbase height in the coinbase data, + // and a sequence number of u32::MAX. + if like_zcashd { + extra_data = Some(vec![0x00]); + sequence = Some(u32::MAX); + } + + // Override like_zcashd if extra_coinbase_data was supplied + if !extra_coinbase_data.is_empty() { + extra_data = Some(extra_coinbase_data); + } + + // # Consensus + // + // These consensus rules apply to v5 coinbase transactions after NU5 activation: + // + // > If effectiveVersion ≥ 5 then this condition MUST hold: + // > tx_in_count > 0 or nSpendsSapling > 0 or + // > (nActionsOrchard > 0 and enableSpendsOrchard = 1). + // + // > A coinbase transaction for a block at block height greater than 0 MUST have + // > a script that, as its first item, encodes the block height height as follows. ... + // > let heightBytes be the signed little-endian representation of height, + // > using the minimum nonzero number of bytes such that the most significant byte + // > is < 0x80. The length of heightBytes MUST be in the range {1 .. 5}. + // > Then the encoding is the length of heightBytes encoded as one byte, + // > followed by heightBytes itself. This matches the encoding used by Bitcoin + // > in the implementation of [BIP-34] + // > (but the description here is to be considered normative). + // + // > A coinbase transaction script MUST have length in {2 .. 100} bytes. + // + // Zebra adds extra coinbase data if configured to do so. + // + // Since we're not using a lock time, any sequence number is valid here. + // See `Transaction::lock_time()` for the relevant consensus rules. + // + // + let inputs = vec![transparent::Input::new_coinbase( + height, extra_data, sequence, + )]; + + // > The block subsidy is composed of a miner subsidy and a series of funding streams. + // + // + // + // > The total value in zatoshi of transparent outputs from a coinbase transaction, + // > minus vbalanceSapling, minus vbalanceOrchard, MUST NOT be greater than + // > the value in zatoshi of block subsidy plus the transaction fees + // > paid by transactions in this block. + // + // > If effectiveVersion ≥ 5 then this condition MUST hold: + // > tx_out_count > 0 or nOutputsSapling > 0 or + // > (nActionsOrchard > 0 and enableOutputsOrchard = 1). + // + // + let outputs: Vec<_> = outputs + .into_iter() + .map(|(amount, lock_script)| transparent::Output::new_coinbase(amount, lock_script)) + .collect(); + assert!( + !outputs.is_empty(), + "invalid coinbase transaction: must have at least one output" + ); + + Transaction::ZFuture { + // > The transaction version number MUST be 4 or 5. ... + // > If the transaction version number is 5 then the version group ID + // > MUST be 0x26A7270A. + // > If effectiveVersion ≥ 5, the nConsensusBranchId field MUST match the consensus + // > branch ID used for SIGHASH transaction hashes, as specified in [ZIP-244]. + network_upgrade: NetworkUpgrade::current(network, height), + + // There is no documented consensus rule for the lock time field in coinbase + // transactions, so we just leave it unlocked. (We could also set it to `height`.) + lock_time: LockTime::unlocked(), + + // > The nExpiryHeight field of a coinbase transaction MUST be equal to its + // > block height. + expiry_height: height, + + inputs, + outputs, + + // Zebra does not support shielded coinbase yet. + // + // > In a version 5 coinbase transaction, the enableSpendsOrchard flag MUST be 0. + // > In a version 5 transaction, the reserved bits 2 .. 7 of the flagsOrchard field + // > MUST be zero. + // + // See the Zcash spec for additional shielded coinbase consensus rules. + sapling_shielded_data: None, + orchard_shielded_data: None, + + // > The NSM burn_amount field [ZIP-233] must be set. It can be set to 0. + burn_amount: burn_amount.unwrap_or(Amount::zero()), + } + } + /// Returns a new version 5 coinbase transaction for `network` and `height`, /// which contains the specified `outputs`. pub fn new_v5_coinbase( diff --git a/zebra-chain/src/transaction/serialize.rs b/zebra-chain/src/transaction/serialize.rs index 0e583efc5bf..29e7fac5faa 100644 --- a/zebra-chain/src/transaction/serialize.rs +++ b/zebra-chain/src/transaction/serialize.rs @@ -8,6 +8,9 @@ use halo2::pasta::group::ff::PrimeField; use hex::FromHex; use reddsa::{orchard::Binding, orchard::SpendAuth, Signature}; +#[cfg(zcash_unstable = "nsm")] +use crate::parameters::TX_ZFUTURE_VERSION_GROUP_ID; + use crate::{ amount, block::MAX_BLOCK_BYTES, @@ -672,6 +675,54 @@ impl ZcashSerialize for Transaction { // `proofsOrchard`, `vSpendAuthSigsOrchard`, and `bindingSigOrchard`. orchard_shielded_data.zcash_serialize(&mut writer)?; } + + #[cfg(zcash_unstable = "nsm")] + Transaction::ZFuture { + network_upgrade, + lock_time, + expiry_height, + inputs, + outputs, + sapling_shielded_data, + orchard_shielded_data, + burn_amount, + } => { + // 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 `burn_amount` in the spec. + burn_amount.zcash_serialize(&mut writer)?; + } } Ok(()) } @@ -928,6 +979,62 @@ impl ZcashDeserialize for Transaction { orchard_shielded_data, }) } + #[cfg(zcash_unstable = "nsm")] + (ZFUTURE_TX_VERSION, true) => { + // Denoted as `nVersionGroupId` in the spec. + let id = limited_reader.read_u32::()?; + if id != TX_ZFUTURE_VERSION_GROUP_ID { + return Err(SerializationError::Parse( + "expected TX_ZFUTURE_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 `burn_amount` in the spec. + let burn_amount = (&mut limited_reader).zcash_deserialize_into()?; + + Ok(Transaction::ZFuture { + network_upgrade, + lock_time, + expiry_height, + inputs, + outputs, + sapling_shielded_data, + orchard_shielded_data, + burn_amount, + }) + } (_, _) => 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..7cc4987d3b4 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(zcash_unstable = "nsm")] + 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..051b54e6dc7 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(zcash_unstable = "nsm")] + Transaction::ZFuture { .. } => self.txid_zfuture(), } } @@ -52,4 +54,13 @@ impl<'a> TxIdBuilder<'a> { let alt_tx: zcash_primitives::transaction::Transaction = self.trans.try_into()?; Ok(Hash(*alt_tx.txid().as_ref())) } + + #[cfg(zcash_unstable = "nsm")] + /// 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..db676dd8f7f 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(zcash_unstable = "nsm")] + ZFuture { .. } => Witnessed(transaction.into()), } } } diff --git a/zebra-consensus/Cargo.toml b/zebra-consensus/Cargo.toml index cf8424d1606..f929920f041 100644 --- a/zebra-consensus/Cargo.toml +++ b/zebra-consensus/Cargo.toml @@ -17,6 +17,8 @@ categories = ["asynchronous", "cryptography::cryptocurrencies"] [features] default = [] +nsm = [] + # Production features that activate extra dependencies, or extra features in dependencies progress-bar = [ @@ -97,3 +99,6 @@ tracing-subscriber = "0.3.18" zebra-state = { path = "../zebra-state", version = "1.0.0-beta.42", features = ["proptest-impl"] } zebra-chain = { path = "../zebra-chain", version = "1.0.0-beta.42", features = ["proptest-impl"] } zebra-test = { path = "../zebra-test/", version = "1.0.0-beta.42" } + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(zcash_unstable, values("nsm"))'] } diff --git a/zebra-consensus/src/block/tests.rs b/zebra-consensus/src/block/tests.rs index e6eb6f2c4b9..a0e2683e39e 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; 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::*; diff --git a/zebra-consensus/src/transaction.rs b/zebra-consensus/src/transaction.rs index 1c303003615..ed04ead0c4c 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(zcash_unstable = "nsm")] + 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() { @@ -444,17 +456,21 @@ where // Get the `value_balance` to calculate the transaction fee. let value_balance = tx.value_balance(&spent_utxos); + let burn_amount = match *tx { + #[cfg(zcash_unstable = "nsm")] + Transaction::ZFuture{ .. } => tx.burn_amount(), + _ => Amount::zero() + }; + // Calculate the fee only for non-coinbase transactions. let mut miner_fee = None; if !tx.is_coinbase() { // TODO: deduplicate this code with remaining_transaction_value()? - miner_fee = match value_balance { - Ok(vb) => match vb.remaining_transaction_value() { - Ok(tx_rtv) => Some(tx_rtv), - Err(_) => return Err(TransactionError::IncorrectFee), - }, - Err(_) => return Err(TransactionError::IncorrectFee), - }; + miner_fee = value_balance + .map(|vb| vb.remaining_transaction_value()) + .map(|tx_rtv| tx_rtv - burn_amount) + .or(Err(TransactionError::IncorrectFee))? + .ok(); } let legacy_sigop_count = cached_ffi_transaction.legacy_sigop_count()?; @@ -687,6 +703,9 @@ where transaction.version(), network_upgrade, )), + + #[cfg(zcash_unstable = "nsm")] + NetworkUpgrade::ZFuture => Ok(()), } } @@ -767,6 +786,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(zcash_unstable = "nsm")] + NetworkUpgrade::ZFuture => Ok(()), // Does not support V5 transactions NetworkUpgrade::Genesis @@ -782,6 +803,51 @@ where } } + /// Verify a ZFUTURE transaction. + #[cfg(zcash_unstable = "nsm")] + 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..0891cc2b210 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(zcash_unstable = "nsm")] + let has_other_outputs = tx.has_burn_amount(); + #[cfg(not(zcash_unstable = "nsm"))] + 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..7ed4fb9cbbe 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(zcash_unstable = "nsm")] + let burn_amount = 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(zcash_unstable = "nsm")] + 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, + burn_amount, }, 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 e4967cc66f2..88bdf504725 100644 --- a/zebra-network/Cargo.toml +++ b/zebra-network/Cargo.toml @@ -27,6 +27,8 @@ categories = ["asynchronous", "cryptography::cryptocurrencies", "encoding", "net [features] default = [] +nsm = [] + # Production features that activate extra dependencies, or extra features in dependencies progress-bar = [ @@ -95,3 +97,6 @@ toml = "0.8.19" zebra-chain = { path = "../zebra-chain", features = ["proptest-impl"] } zebra-test = { path = "../zebra-test/" } + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(zcash_unstable, values("nsm"))'] } diff --git a/zebra-network/src/constants.rs b/zebra-network/src/constants.rs index a116fd63018..d540c32df20 100644 --- a/zebra-network/src/constants.rs +++ b/zebra-network/src/constants.rs @@ -337,10 +337,10 @@ pub const TIMESTAMP_TRUNCATION_SECONDS: u32 = 30 * 60; /// /// The current protocol version typically changes before Mainnet and Testnet /// network upgrades. -/// -/// This version of Zebra draws the current network protocol version from -/// [ZIP-253](https://zips.z.cash/zip-0253). +#[cfg(not(zcash_unstable = "nsm"))] pub const CURRENT_NETWORK_PROTOCOL_VERSION: Version = Version(170_120); +#[cfg(zcash_unstable = "nsm")] +pub const CURRENT_NETWORK_PROTOCOL_VERSION: Version = Version(170_140); /// The default RTT estimate for peer responses. /// diff --git a/zebra-network/src/protocol/external/types.rs b/zebra-network/src/protocol/external/types.rs index c6241ba4d78..22e1b98a5a6 100644 --- a/zebra-network/src/protocol/external/types.rs +++ b/zebra-network/src/protocol/external/types.rs @@ -106,6 +106,10 @@ impl Version { (Mainnet, Nu5) => 170_100, (Testnet(params), Nu6) if params.is_default_testnet() => 170_110, (Mainnet, Nu6) => 170_120, + #[cfg(zcash_unstable = "nsm")] + (Testnet(params), ZFuture) if params.is_default_testnet() => 170_130, + #[cfg(zcash_unstable = "nsm")] + (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, @@ -205,6 +209,10 @@ mod test { let _init_guard = zebra_test::init(); let highest_network_upgrade = NetworkUpgrade::current(network, block::Height::MAX); + #[cfg(zcash_unstable = "nsm")] + assert!(highest_network_upgrade == ZFuture, + "expected coverage of all network upgrades: add the new network upgrade to the list in this test"); + #[cfg(not(zcash_unstable = "nsm"))] 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"); @@ -217,6 +225,8 @@ mod test { Canopy, Nu5, Nu6, + #[cfg(zcash_unstable = "nsm")] + 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 85be248bc76..da6fa80d8cc 100644 --- a/zebra-rpc/Cargo.toml +++ b/zebra-rpc/Cargo.toml @@ -21,6 +21,8 @@ categories = [ [features] +nsm = [] + indexer-rpcs = [ "tonic-build", "tonic", @@ -140,3 +142,6 @@ zebra-state = { path = "../zebra-state", version = "1.0.0-beta.42", features = [ ] } zebra-test = { path = "../zebra-test", version = "1.0.0-beta.42" } + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(zcash_unstable, values("nsm"))'] } diff --git a/zebra-rpc/src/methods/get_block_template_rpcs.rs b/zebra-rpc/src/methods/get_block_template_rpcs.rs index 2d50552cfec..28d3c45d4e3 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs.rs @@ -286,13 +286,13 @@ pub trait GetBlockTemplateRpc { address: String, ) -> BoxFuture>; - #[rpc(name = "generate")] /// Mine blocks immediately. Returns the block hashes of the generated blocks. /// /// # Parameters /// /// - `num_blocks`: (numeric, required, example=1) Number of blocks to be generated. /// + /// - `burn_amount`: (numeric, optional) The amount of money to be burned in a transaction [ZIP-233] /// # Notes /// /// Only works if the network of the running zebrad process is `Regtest`. @@ -300,7 +300,14 @@ pub trait GetBlockTemplateRpc { /// zcashd reference: [`generate`](https://zcash.github.io/rpc/generate.html) /// method: post /// tags: generating - fn generate(&self, num_blocks: u32) -> BoxFuture>>; + #[rpc(name = "generate")] + fn generate( + &self, + num_blocks: u32, + // the burn_amount field should be currently hidden behind zcash_unstable flag, but it doesn't compile due to the rpc macro + //#[cfg(zcash_unstable = "nsm")] + burn_amount: Option>, + ) -> BoxFuture>>; } /// RPC method implementations. @@ -882,6 +889,13 @@ where "selecting transactions for the template from the mempool" ); + #[cfg(zcash_unstable = "nsm")] + let burn_amount = if let Some(params) = parameters { + params.burn_amount + } else { + None + }; + // Randomly select some mempool transactions. let mempool_txs = zip317::select_mempool_transactions( &network, @@ -890,6 +904,8 @@ where mempool_txs, debug_like_zcashd, extra_coinbase_data.clone(), + #[cfg(zcash_unstable = "nsm")] + burn_amount, ) .await; @@ -902,7 +918,6 @@ where ); // - After this point, the template only depends on the previously fetched data. - let response = GetBlockTemplate::new( &network, &miner_address, @@ -912,6 +927,8 @@ where submit_old, debug_like_zcashd, extra_coinbase_data, + #[cfg(zcash_unstable = "nsm")] + burn_amount, ); Ok(response.into()) @@ -1406,7 +1423,12 @@ where .boxed() } - fn generate(&self, num_blocks: u32) -> BoxFuture>> { + fn generate( + &self, + num_blocks: u32, + //#[cfg(zcash_unstable = "nsm")] + burn_amount: Option>, + ) -> BoxFuture>> { let rpc: GetBlockTemplateRpcImpl< Mempool, State, @@ -1427,8 +1449,18 @@ where } let mut block_hashes = Vec::new(); + #[cfg(not(zcash_unstable = "nsm"))] + let params = None; + #[cfg(zcash_unstable = "nsm")] + let params = Some(get_block_template::JsonParameters { + burn_amount, + ..Default::default() + }); for _ in 0..num_blocks { - let block_template = rpc.get_block_template(None).await.map_server_error()?; + let block_template = rpc + .get_block_template(params.clone()) + .await + .map_server_error()?; let get_block_template::Response::TemplateMode(block_template) = block_template else { 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..71a5dd63012 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 @@ -289,6 +289,7 @@ where /// /// If `like_zcashd` is true, try to match the coinbase transactions generated by `zcashd` /// in the `getblocktemplate` RPC. +#[allow(clippy::too_many_arguments)] pub fn generate_coinbase_and_roots( network: &Network, block_template_height: Height, @@ -297,6 +298,7 @@ pub fn generate_coinbase_and_roots( history_tree: Arc, like_zcashd: bool, extra_coinbase_data: Vec, + #[cfg(zcash_unstable = "nsm")] burn_amount: Option>, ) -> (TransactionTemplate, DefaultRoots) { // Generate the coinbase transaction let miner_fee = calculate_miner_fee(mempool_txs); @@ -307,6 +309,8 @@ pub fn generate_coinbase_and_roots( miner_fee, like_zcashd, extra_coinbase_data, + #[cfg(zcash_unstable = "nsm")] + burn_amount, ); // Calculate block default roots @@ -340,13 +344,57 @@ pub fn generate_coinbase_transaction( miner_fee: Amount, like_zcashd: bool, extra_coinbase_data: Vec, + #[cfg(zcash_unstable = "nsm")] burn_amount: Option>, ) -> UnminedTx { let outputs = standard_coinbase_outputs(network, height, miner_address, miner_fee, like_zcashd); if like_zcashd { + #[cfg(zcash_unstable = "nsm")] + { + let network_upgrade = NetworkUpgrade::current(network, height); + if network_upgrade < NetworkUpgrade::ZFuture { + Transaction::new_v4_coinbase( + network, + height, + outputs, + like_zcashd, + extra_coinbase_data, + ) + .into() + } else { + Transaction::new_zfuture_coinbase( + network, + height, + outputs, + extra_coinbase_data, + like_zcashd, + burn_amount, + ) + .into() + } + } + #[cfg(not(zcash_unstable = "nsm"))] Transaction::new_v4_coinbase(network, height, outputs, like_zcashd, extra_coinbase_data) .into() } else { + #[cfg(zcash_unstable = "nsm")] + { + let network_upgrade = NetworkUpgrade::current(network, height); + if network_upgrade < NetworkUpgrade::ZFuture { + Transaction::new_v5_coinbase(network, height, outputs, extra_coinbase_data).into() + } else { + Transaction::new_zfuture_coinbase( + network, + height, + outputs, + extra_coinbase_data, + like_zcashd, + burn_amount, + ) + .into() + } + } + #[cfg(not(zcash_unstable = "nsm"))] Transaction::new_v5_coinbase(network, height, outputs, extra_coinbase_data).into() } } diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template.rs index d7c31e11a81..adae2fbefa8 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template.rs @@ -4,7 +4,7 @@ use std::fmt; use zebra_chain::{ - amount, + amount::{self, Amount, NonNegative}, block::{ChainHistoryBlockTxAuthCommitmentHash, MAX_BLOCK_BYTES, ZCASH_BLOCK_VERSION}, parameters::Network, serialization::DateTime32, @@ -231,6 +231,7 @@ impl GetBlockTemplate { submit_old: Option, like_zcashd: bool, extra_coinbase_data: Vec, + #[cfg(zcash_unstable = "nsm")] burn_amount: Option>, ) -> Self { // Calculate the next block height. let next_block_height = @@ -271,6 +272,8 @@ impl GetBlockTemplate { chain_tip_and_local_time.history_tree.clone(), like_zcashd, extra_coinbase_data, + #[cfg(zcash_unstable = "nsm")] + burn_amount, ); // Convert difficulty diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/parameters.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/parameters.rs index 73e1ed820ba..0230a50ce1d 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/parameters.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/parameters.rs @@ -1,5 +1,7 @@ //! Parameter types for the `getblocktemplate` RPC. +use zebra_chain::amount::{Amount, NonNegative}; + use crate::methods::{get_block_template_rpcs::types::long_poll::LongPollId, hex_data::HexData}; /// Defines whether the RPC method should generate a block template or attempt to validate a block proposal. @@ -89,6 +91,10 @@ pub struct JsonParameters { /// currently unused. #[serde(rename = "workid")] pub _work_id: Option, + + /// The amount of money to be burned in a transaction [ZIP-233] + #[cfg(zcash_unstable = "nsm")] + pub burn_amount: Option>, } impl JsonParameters { 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..e5039fb1e0f 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(zcash_unstable = "nsm")] + NetworkUpgrade::ZFuture => block_commitments_hash.bytes_in_serialized_order().into(), }; Ok(Block { diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/zip317.rs b/zebra-rpc/src/methods/get_block_template_rpcs/zip317.rs index 3f0979dc266..3563485d284 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/zip317.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/zip317.rs @@ -12,7 +12,7 @@ use rand::{ }; use zebra_chain::{ - amount::NegativeOrZero, + amount::{Amount, NegativeOrZero, NonNegative}, block::{Height, MAX_BLOCK_BYTES}, parameters::Network, transaction::{zip317::BLOCK_UNPAID_ACTION_LIMIT, VerifiedUnminedTx}, @@ -43,6 +43,7 @@ pub async fn select_mempool_transactions( mempool_txs: Vec, like_zcashd: bool, extra_coinbase_data: Vec, + #[cfg(zcash_unstable = "nsm")] burn_amount: Option>, ) -> Vec { // Use a fake coinbase transaction to break the dependency between transaction // selection, the miner fee, and the fee payment in the coinbase transaction. @@ -52,6 +53,8 @@ pub async fn select_mempool_transactions( miner_address, like_zcashd, extra_coinbase_data, + #[cfg(zcash_unstable = "nsm")] + burn_amount, ); // Setup the transaction lists. @@ -120,6 +123,7 @@ pub fn fake_coinbase_transaction( miner_address: &transparent::Address, like_zcashd: bool, extra_coinbase_data: Vec, + #[cfg(zcash_unstable = "nsm")] burn_amount: Option>, ) -> TransactionTemplate { // Block heights are encoded as variable-length (script) and `u32` (lock time, expiry height). // They can also change the `u32` consensus branch id. @@ -139,6 +143,8 @@ pub fn fake_coinbase_transaction( miner_fee, like_zcashd, extra_coinbase_data, + #[cfg(zcash_unstable = "nsm")] + burn_amount, ); TransactionTemplate::from_coinbase(&coinbase_tx, miner_fee) diff --git a/zebra-state/Cargo.toml b/zebra-state/Cargo.toml index 55f4f2e1556..1d5c357fa8f 100644 --- a/zebra-state/Cargo.toml +++ b/zebra-state/Cargo.toml @@ -16,6 +16,12 @@ categories = ["asynchronous", "caching", "cryptography::cryptocurrencies"] [features] +default = [] + +nsm = [ + "zebra-chain/nsm", +] + # Production features that activate extra dependencies, or extra features in dependencies progress-bar = [ @@ -110,3 +116,6 @@ tokio = { version = "1.41.0", features = ["full", "tracing", "test-util"] } zebra-chain = { path = "../zebra-chain", version = "1.0.0-beta.42", features = ["proptest-impl"] } zebra-test = { path = "../zebra-test/", version = "1.0.0-beta.42" } + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(zcash_unstable, values("nsm"))'] } diff --git a/zebra-state/src/service/non_finalized_state/chain.rs b/zebra-state/src/service/non_finalized_state/chain.rs index 12ee0528776..e46e704459e 100644 --- a/zebra-state/src/service/non_finalized_state/chain.rs +++ b/zebra-state/src/service/non_finalized_state/chain.rs @@ -1524,6 +1524,21 @@ impl Chain { V1 { .. } | V2 { .. } | V3 { .. } => unreachable!( "older transaction versions only exist in finalized blocks, because of the mandatory canopy checkpoint", ), + #[cfg(zcash_unstable = "nsm")] + ZFuture { + inputs, + outputs, + sapling_shielded_data, + orchard_shielded_data, + .. + } => ( + inputs, + outputs, + &None, + &None, + sapling_shielded_data, + orchard_shielded_data, + ) }; // add key `transaction.hash` and value `(height, tx_index)` to `tx_loc_by_hash` @@ -1685,6 +1700,21 @@ impl UpdateWith for Chain { V1 { .. } | V2 { .. } | V3 { .. } => unreachable!( "older transaction versions only exist in finalized blocks, because of the mandatory canopy checkpoint", ), + #[cfg(zcash_unstable = "nsm")] + 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..bd7a6b661cf 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(zcash_unstable = "nsm")] + Transaction::ZFuture { inputs, .. } => &mut inputs[0], }; match input { diff --git a/zebrad/Cargo.toml b/zebrad/Cargo.toml index 768f68dfcb3..393d668d28d 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 = [] +nsm = ["zebra-chain/nsm", "zebra-network/nsm", "zebra-consensus/nsm", "zebra-rpc/nsm"] + [dependencies] zebra-chain = { path = "../zebra-chain", version = "1.0.0-beta.42" } zebra-consensus = { path = "../zebra-consensus", version = "1.0.0-beta.42" } @@ -299,4 +301,4 @@ zebra-grpc = { path = "../zebra-grpc", version = "0.1.0-alpha.9" } zebra-utils = { path = "../zebra-utils", version = "1.0.0-beta.42" } [lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tokio_unstable)'] } +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tokio_unstable)', 'cfg(zcash_unstable, values("nsm"))'] } diff --git a/zebrad/src/components/mempool/storage/tests/prop.rs b/zebrad/src/components/mempool/storage/tests/prop.rs index eca65935acb..8b56baee93e 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(zcash_unstable = "nsm")] + Transaction::ZFuture { .. } => {} } } } @@ -638,6 +641,14 @@ impl SpendConflictTestInput { Self::remove_sapling_transfers_with_conflicts(sapling_shielded_data, &conflicts) } + #[cfg(zcash_unstable = "nsm")] + 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(zcash_unstable = "nsm")] + Transaction::ZFuture { + orchard_shielded_data, + .. + } => Self::remove_orchard_actions_with_conflicts(orchard_shielded_data, &conflicts), + // No Spends Transaction::V1 { .. } | Transaction::V2 { .. } diff --git a/zebrad/src/components/miner.rs b/zebrad/src/components/miner.rs index cb32cc91981..d9240abe71f 100644 --- a/zebrad/src/components/miner.rs +++ b/zebrad/src/components/miner.rs @@ -258,6 +258,8 @@ where capabilities: vec![LongPoll, CoinbaseTxn], long_poll_id: None, _work_id: None, + #[cfg(zcash_unstable = "nsm")] + burn_amount: None, }; // Shut down the task when all the template receivers are dropped, or Zebra shuts down. diff --git a/zebrad/tests/acceptance.rs b/zebrad/tests/acceptance.rs index cd3572ce3f2..5777437b98e 100644 --- a/zebrad/tests/acceptance.rs +++ b/zebrad/tests/acceptance.rs @@ -3448,6 +3448,8 @@ async fn nu6_funding_streams_and_coinbase_balance() -> Result<()> { history_tree.clone(), true, vec![], + #[cfg(zcash_unstable = "nsm")] + None, ); let block_template = GetBlockTemplate { @@ -3491,6 +3493,8 @@ async fn nu6_funding_streams_and_coinbase_balance() -> Result<()> { history_tree.clone(), true, vec![], + #[cfg(zcash_unstable = "nsm")] + None, ); let block_template = GetBlockTemplate {