diff --git a/src/gtest/test_checktransaction.cpp b/src/gtest/test_checktransaction.cpp index 1f7578915fe..052c1700a59 100644 --- a/src/gtest/test_checktransaction.cpp +++ b/src/gtest/test_checktransaction.cpp @@ -514,6 +514,7 @@ TEST(ContextualCheckShieldedInputsTest, BadTxnsInvalidJoinsplitSignature) { auto consensus = Params().GetConsensus(); std::optional> saplingAuth = std::nullopt; std::optional> orchardAuth = std::nullopt; + std::optional> issueAuth = std::nullopt; CMutableTransaction mtx = GetValidTransaction(); mtx.joinSplitSig.bytes[0] += 1; @@ -530,22 +531,25 @@ TEST(ContextualCheckShieldedInputsTest, BadTxnsInvalidJoinsplitSignature) { // during initial block download, for transactions being accepted into the // mempool (and thus not mined), DoS ban score should be zero, else 10 EXPECT_CALL(state, DoS(0, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1); - ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, consensus, 0, false, false, [](const Consensus::Params&) { return true; }); + ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, issueAuth, consensus, 0, false, false, [](const Consensus::Params&) { return true; }); EXPECT_CALL(state, DoS(10, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1); - ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, consensus, 0, false, false, [](const Consensus::Params&) { return false; }); + ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, issueAuth, consensus, 0, false, false, [](const Consensus::Params&) { return false; }); // for transactions that have been mined in a block, DoS ban score should // always be 100. EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1); - ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, consensus, 0, false, true, [](const Consensus::Params&) { return true; }); + ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, issueAuth, consensus, 0, false, true, [](const Consensus::Params&) { return true; }); EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1); - ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, consensus, 0, false, true, [](const Consensus::Params&) { return false; }); + ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, issueAuth, consensus, 0, false, true, [](const Consensus::Params&) { return false; }); } +// FIXME: add tests for issueAuth like orchardAuth + TEST(ContextualCheckShieldedInputsTest, JoinsplitSignatureDetectsOldBranchId) { SelectParams(CBaseChainParams::REGTEST); auto consensus = Params().GetConsensus(); std::optional> saplingAuth = std::nullopt; std::optional> orchardAuth = std::nullopt; + std::optional> issueAuth = std::nullopt; auto saplingBranchId = NetworkUpgradeInfo[Consensus::UPGRADE_SAPLING].nBranchId; auto blossomBranchId = NetworkUpgradeInfo[Consensus::UPGRADE_BLOSSOM].nBranchId; @@ -565,7 +569,7 @@ TEST(ContextualCheckShieldedInputsTest, JoinsplitSignatureDetectsOldBranchId) { CCoinsViewCache view(&baseView); // Ensure that the transaction validates against Sapling. EXPECT_TRUE(ContextualCheckShieldedInputs( - tx, txdata, state, view, saplingAuth, orchardAuth, consensus, saplingBranchId, false, false, + tx, txdata, state, view, saplingAuth, orchardAuth, issueAuth, consensus, saplingBranchId, false, false, [](const Consensus::Params&) { return false; })); // Attempt to validate the inputs against Blossom. We should be notified @@ -577,7 +581,7 @@ TEST(ContextualCheckShieldedInputsTest, JoinsplitSignatureDetectsOldBranchId) { HexInt(saplingBranchId)), false, "")).Times(1); EXPECT_FALSE(ContextualCheckShieldedInputs( - tx, txdata, state, view, saplingAuth, orchardAuth, consensus, blossomBranchId, false, false, + tx, txdata, state, view, saplingAuth, orchardAuth, issueAuth, consensus, blossomBranchId, false, false, [](const Consensus::Params&) { return false; })); // Attempt to validate the inputs against Heartwood. All we should learn is @@ -587,7 +591,7 @@ TEST(ContextualCheckShieldedInputsTest, JoinsplitSignatureDetectsOldBranchId) { 10, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1); EXPECT_FALSE(ContextualCheckShieldedInputs( - tx, txdata, state, view, saplingAuth, orchardAuth, consensus, heartwoodBranchId, false, false, + tx, txdata, state, view, saplingAuth, orchardAuth, issueAuth, consensus, heartwoodBranchId, false, false, [](const Consensus::Params&) { return false; })); } @@ -596,6 +600,7 @@ TEST(ContextualCheckShieldedInputsTest, NonCanonicalEd25519Signature) { auto consensus = Params().GetConsensus(); std::optional> saplingAuth = std::nullopt; std::optional> orchardAuth = std::nullopt; + std::optional> issueAuth = std::nullopt; AssumeShieldedInputsExistAndAreSpendable baseView; CCoinsViewCache view(&baseView); @@ -612,7 +617,7 @@ TEST(ContextualCheckShieldedInputsTest, NonCanonicalEd25519Signature) { CTransaction tx(mtx); const PrecomputedTransactionData txdata(tx, allPrevOutputs); MockCValidationState state; - EXPECT_TRUE(ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, consensus, saplingBranchId, false, true)); + EXPECT_TRUE(ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, issueAuth, consensus, saplingBranchId, false, true)); } // Copied from libsodium/crypto_sign/ed25519/ref10/open.c @@ -636,15 +641,15 @@ TEST(ContextualCheckShieldedInputsTest, NonCanonicalEd25519Signature) { // during initial block download, for transactions being accepted into the // mempool (and thus not mined), DoS ban score should be zero, else 10 EXPECT_CALL(state, DoS(0, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1); - ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, consensus, saplingBranchId, false, false, [](const Consensus::Params&) { return true; }); + ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, issueAuth, consensus, saplingBranchId, false, false, [](const Consensus::Params&) { return true; }); EXPECT_CALL(state, DoS(10, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1); - ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, consensus, saplingBranchId, false, false, [](const Consensus::Params&) { return false; }); + ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, issueAuth, consensus, saplingBranchId, false, false, [](const Consensus::Params&) { return false; }); // for transactions that have been mined in a block, DoS ban score should // always be 100. EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1); - ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, consensus, saplingBranchId, false, true, [](const Consensus::Params&) { return true; }); + ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, issueAuth, consensus, saplingBranchId, false, true, [](const Consensus::Params&) { return true; }); EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1); - ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, consensus, saplingBranchId, false, true, [](const Consensus::Params&) { return false; }); + ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, issueAuth, consensus, saplingBranchId, false, true, [](const Consensus::Params&) { return false; }); } TEST(ChecktransactionTests, OverwinterConstructors) { @@ -1290,6 +1295,7 @@ TEST(ChecktransactionTests, HeartwoodEnforcesSaplingRulesOnShieldedCoinbase) { std::optional> saplingAuth = sapling::init_batch_validator(false); std::optional> orchardAuth = std::nullopt; + std::optional> issueAuth = std::nullopt; auto heartwoodBranchId = NetworkUpgradeInfo[Consensus::UPGRADE_HEARTWOOD].nBranchId; // Coinbase transaction does not pass shielded input checks, as bindingSig @@ -1303,7 +1309,7 @@ TEST(ChecktransactionTests, HeartwoodEnforcesSaplingRulesOnShieldedCoinbase) { AssumeShieldedInputsExistAndAreSpendable baseView; CCoinsViewCache view(&baseView); EXPECT_TRUE(ContextualCheckShieldedInputs( - tx, txdata, state, view, saplingAuth, orchardAuth, chainparams.GetConsensus(), heartwoodBranchId, false, true)); + tx, txdata, state, view, saplingAuth, orchardAuth, issueAuth, chainparams.GetConsensus(), heartwoodBranchId, false, true)); EXPECT_FALSE(saplingAuth.value()->validate()); RegtestDeactivateHeartwood(); diff --git a/src/main.cpp b/src/main.cpp index 374f6b12f08..ed003b06e1f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1293,6 +1293,7 @@ bool ContextualCheckShieldedInputs( const CCoinsViewCache &view, std::optional>& saplingAuth, std::optional>& orchardAuth, + std::optional>& issueAuth, const Consensus::Params& consensus, uint32_t consensusBranchId, bool nu5Active, @@ -1375,6 +1376,11 @@ bool ContextualCheckShieldedInputs( tx.GetOrchardBundle().QueueAuthValidation(*orchardAuth.value(), dataToBeSigned); } + // Queue Issue bundle to be batch-validated. + if (issueAuth.has_value()) { + tx.GetIssueBundle().QueueAuthValidation(*issueAuth.value(), dataToBeSigned); + } + return true; } @@ -1980,6 +1986,9 @@ bool AcceptToMemoryPool( // Orchard bundle contains at least two signatures. std::optional> orchardAuth = orchard::init_batch_validator(true); + // This will be a single-transaction batch for IssueAuth + std::optional> issueAuth = issue_bundle::init_batch_validator(true); + // Check shielded input signatures. if (!ContextualCheckShieldedInputs( tx, @@ -1988,6 +1997,7 @@ bool AcceptToMemoryPool( view, saplingAuth, orchardAuth, + issueAuth, chainparams.GetConsensus(), consensusBranchId, chainparams.GetConsensus().NetworkUpgradeActive(nextBlockHeight, Consensus::UPGRADE_NU5), @@ -1996,7 +2006,7 @@ bool AcceptToMemoryPool( return false; } - // Check Sapling and Orchard bundle authorizations. + // Check Sapling, Orchard and Issue bundle authorizations. // `saplingAuth` and `orchardAuth` are known here to be non-null. if (!saplingAuth.value()->validate()) { return state.DoS(100, false, REJECT_INVALID, "bad-sapling-bundle-authorization"); @@ -2004,6 +2014,10 @@ bool AcceptToMemoryPool( if (!orchardAuth.value()->validate()) { return state.DoS(100, false, REJECT_INVALID, "bad-orchard-bundle-authorization"); } + // FIXME: should the error text be "bad-issue-bundle" as it's not only about authorization? + if (!issueAuth.value()->validate()) { + return state.DoS(100, false, REJECT_INVALID, "bad-issue-bundle-authorization"); + } { // Store transaction in memory @@ -3103,11 +3117,13 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin // proof verification is expensive, disable if possible auto verifier = fExpensiveChecks ? ProofVerifier::Strict() : ProofVerifier::Disabled(); - // Disable Sapling and Orchard batch validation if possible. + // Disable Sapling, Orchard and Issue batch validation if possible. std::optional> saplingAuth = fExpensiveChecks ? std::optional(sapling::init_batch_validator(fCacheResults)) : std::nullopt; std::optional> orchardAuth = fExpensiveChecks ? std::optional(orchard::init_batch_validator(fCacheResults)) : std::nullopt; + std::optional> issueAuth = fExpensiveChecks ? + std::optional(issue_bundle::init_batch_validator(fCacheResults)) : std::nullopt; // If in initial block download, and this block is an ancestor of a checkpoint, // and -ibdskiptxverification is set, disable all transaction checks. @@ -3372,6 +3388,7 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin view, saplingAuth, orchardAuth, + issueAuth, chainparams.GetConsensus(), consensusBranchId, chainparams.GetConsensus().NetworkUpgradeActive(pindex->nHeight, Consensus::UPGRADE_NU5), @@ -3683,6 +3700,15 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin REJECT_INVALID, "bad-orchard-bundle-authorization"); } + // Ensure Issue bundle is valid (if we are checking it) + if (issueAuth.has_value() && !issueAuth.value()->validate()) { + // FIXME: should the error text be "bad-issue-bundle" as it's not only about authorization? + // FIXME: add validation errotr message .... + return state.DoS(100, + error("ConnectBlock(): an Issue bundle within the block is invalid"), + REJECT_INVALID, "bad-orchard-bundle-authorization"); + } + if (!control.Wait()) return state.DoS(100, false); int64_t nTime2 = GetTimeMicros(); nTimeVerify += nTime2 - nTimeStart; diff --git a/src/main.h b/src/main.h index fa238e4588a..9f28f1aa73d 100644 --- a/src/main.h +++ b/src/main.h @@ -415,6 +415,7 @@ bool ContextualCheckShieldedInputs( const CCoinsViewCache &view, std::optional>& saplingAuth, std::optional>& orchardAuth, + std::optional>& issueAuth, const Consensus::Params& consensus, uint32_t consensusBranchId, bool nu5Active, diff --git a/src/primitives/issue.h b/src/primitives/issue.h index 0bb13e0d805..905b7972ced 100644 --- a/src/primitives/issue.h +++ b/src/primitives/issue.h @@ -181,6 +181,15 @@ const size_t GetNumActions() const { return inner->num_actions(); } + + /// Queues this bundle's authorization for validation. + /// + /// `sighash` must be for the transaction this bundle is within. + void QueueAuthValidation( + issue_bundle::BatchValidator& batch, const uint256& sighash) const + { + batch.add_bundle(inner->box_clone(), sighash.GetRawBytes()); + } }; #endif // ZCASH_PRIMITIVES_ISSUE_H diff --git a/src/rust/bin/inspect/transaction.rs b/src/rust/bin/inspect/transaction.rs index aae19f5ce2a..05b620566a0 100644 --- a/src/rust/bin/inspect/transaction.rs +++ b/src/rust/bin/inspect/transaction.rs @@ -149,6 +149,7 @@ impl Authorization for PrecomputedAuth { type TransparentAuth = TransparentAuth; type SaplingAuth = sapling::Authorized; type OrchardAuth = orchard::bundle::Authorized; + // FIXME: add IssueAuth? } pub(crate) fn inspect(tx: Transaction, context: Option) { diff --git a/src/rust/src/bridge.rs b/src/rust/src/bridge.rs index 7eae94284cc..1e5b9a9f94c 100644 --- a/src/rust/src/bridge.rs +++ b/src/rust/src/bridge.rs @@ -14,6 +14,7 @@ use crate::{ create_issue_bundle, issue_bundle_from_raw_box, none_issue_bundle, parse_issue_bundle, IssueAction, IssueBundle, IssueNote, }, + issue_ffi::{issue_batch_validation_init, BatchValidator as IssueBatchValidator}, merkle_frontier::{new_orchard, orchard_empty_root, parse_orchard, Orchard, OrchardWallet}, note_encryption::{ try_sapling_note_decryption, try_sapling_output_recovery, DecryptedSaplingOutput, @@ -355,6 +356,16 @@ pub(crate) mod ffi { fn authorization(self: &IssueBundle) -> [u8; 64]; // Assumption is that we only need auth for IssueBundle here, ignoring empty for Unauthorized and sighash for Prepared } + #[namespace = "issue_bundle"] + extern "Rust" { + #[cxx_name = "BatchValidator"] + type IssueBatchValidator; + #[cxx_name = "init_batch_validator"] + fn issue_batch_validation_init(cache_store: bool) -> Box; + fn add_bundle(self: &mut IssueBatchValidator, bundle: Box, sighash: [u8; 32]); + fn validate(self: &mut IssueBatchValidator) -> bool; + } + #[namespace = "orchard"] extern "Rust" { #[cxx_name = "BatchValidator"] diff --git a/src/rust/src/builder_ffi.rs b/src/rust/src/builder_ffi.rs index 8c00495ac99..e437f3f5141 100644 --- a/src/rust/src/builder_ffi.rs +++ b/src/rust/src/builder_ffi.rs @@ -240,6 +240,7 @@ pub(crate) fn shielded_signature_digest( type TransparentAuth = TransparentAuth; type SaplingAuth = sapling::builder::Unauthorized; type OrchardAuth = InProgress; + // FIXME: add IssueAuth? } let txdata: TransactionData = tx.into_data().map_bundles( diff --git a/src/rust/src/bundlecache.rs b/src/rust/src/bundlecache.rs index f9cfd4acce2..ca23af8531f 100644 --- a/src/rust/src/bundlecache.rs +++ b/src/rust/src/bundlecache.rs @@ -97,6 +97,7 @@ impl BundleValidityCache { static BUNDLE_CACHES_LOADED: Once = Once::new(); static mut SAPLING_BUNDLE_VALIDITY_CACHE: Option> = None; static mut ORCHARD_BUNDLE_VALIDITY_CACHE: Option> = None; +static mut ISSUE_BUNDLE_VALIDITY_CACHE: Option> = None; pub(crate) fn init(cache_bytes: usize) { BUNDLE_CACHES_LOADED.call_once(|| unsafe { @@ -110,6 +111,11 @@ pub(crate) fn init(cache_bytes: usize) { b"OrchardVeriCache", cache_bytes, ))); + ISSUE_BUNDLE_VALIDITY_CACHE = Some(RwLock::new(BundleValidityCache::new( + "Issue", + b"IssueVeriCache", + cache_bytes, + ))); }); } @@ -142,3 +148,17 @@ pub(crate) fn orchard_bundle_validity_cache_mut() -> RwLockWriteGuard<'static, B .write() .unwrap() } + +pub(crate) fn issue_bundle_validity_cache() -> RwLockReadGuard<'static, BundleValidityCache> { + unsafe { ISSUE_BUNDLE_VALIDITY_CACHE.as_ref() } + .expect("bundlecache::init() should have been called") + .read() + .unwrap() +} + +pub(crate) fn issue_bundle_validity_cache_mut() -> RwLockWriteGuard<'static, BundleValidityCache> { + unsafe { ISSUE_BUNDLE_VALIDITY_CACHE.as_mut() } + .expect("bundlecache::init() should have been called") + .write() + .unwrap() +} diff --git a/src/rust/src/issue_ffi.rs b/src/rust/src/issue_ffi.rs index 8bfd3a70112..6340cf6bdc3 100644 --- a/src/rust/src/issue_ffi.rs +++ b/src/rust/src/issue_ffi.rs @@ -10,6 +10,10 @@ use std::{ptr, slice}; use tracing::error; use zcash_primitives::transaction::components::issuance as issuance_serialization; +mod batch_validator; + +pub use batch_validator::{issue_batch_validation_init, BatchValidator}; + #[no_mangle] pub extern "C" fn issuance_key_to_issuance_authorizing_key( key: *const IssuanceKey, diff --git a/src/rust/src/issue_ffi/batch_validator.rs b/src/rust/src/issue_ffi/batch_validator.rs new file mode 100644 index 00000000000..180ebdc4c58 --- /dev/null +++ b/src/rust/src/issue_ffi/batch_validator.rs @@ -0,0 +1,88 @@ +use std::convert::TryInto; + +use rand_core::OsRng; +use tracing::{debug, error}; + +use crate::{ + bundlecache::{issue_bundle_validity_cache, issue_bundle_validity_cache_mut, CacheEntries}, + issue_bundle::IssueBundle, +}; + +struct BatchValidatorInner { + validator: orchard::issuance::BatchValidator, + queued_entries: CacheEntries, +} + +pub(crate) struct BatchValidator(Option); + +/// Creates an Issue bundle batch validation context. +pub(crate) fn issue_batch_validation_init(cache_store: bool) -> Box { + Box::new(BatchValidator(Some(BatchValidatorInner { + validator: orchard::issuance::BatchValidator::new(), + queued_entries: CacheEntries::new(cache_store), + }))) +} + +impl BatchValidator { + /// Adds an Issue bundle to this batch. + pub(crate) fn add_bundle(&mut self, bundle: Box, sighash: [u8; 32]) { + let batch = self.0.as_mut(); + let bundle = bundle.inner(); + + match (batch, bundle) { + (Some(batch), Some(bundle)) => { + let cache = issue_bundle_validity_cache(); + + // Compute the cache entry for this bundle. + let cache_entry = { + let bundle_commitment = bundle.commitment(); + let bundle_authorizing_commitment = bundle.authorizing_commitment(); + cache.compute_entry( + bundle_commitment.0.as_bytes().try_into().unwrap(), + bundle_authorizing_commitment + .0 + .as_bytes() + .try_into() + .unwrap(), + &sighash, + ) + }; + + // Check if this bundle's validation result exists in the cache. + if !cache.contains(cache_entry, &mut batch.queued_entries) { + // The bundle has been added to `inner.queued_entries` because it was not + // in the cache. We now add its authorization to the validation batch. + batch.validator.add_bundle(bundle, sighash); + } + } + (Some(_), None) => debug!("Tx has no Issue component"), + (None, _) => error!("orchard::issuance::BatchValidator has already been used"), + } + } + + /// Validates this batch. + /// + /// - Returns `true` if `batch` is null. + /// - Returns `false` if any item in the batch is invalid. + /// + /// The batch validation context is freed by this function. + pub(crate) fn validate(&mut self) -> bool { + if let Some(inner) = self.0.take() { + let vk = unsafe { crate::ISSUE_VK.as_ref() } + .expect("Parameters not loaded: ISSUE_VK should have been initialized"); + if inner.validator.validate() { + // `BatchValidator::validate()` is only called if every + // `BatchValidator::check_bundle()` returned `true`, so at this point + // every bundle that was added to `inner.queued_entries` has valid + // authorization. + issue_bundle_validity_cache_mut().insert(inner.queued_entries); + true + } else { + false + } + } else { + error!("orchard::issuance::BatchValidator has already been used"); + false + } + } +} diff --git a/src/rust/src/transaction_ffi.rs b/src/rust/src/transaction_ffi.rs index e10a5e34e8b..cdbfc8af4a7 100644 --- a/src/rust/src/transaction_ffi.rs +++ b/src/rust/src/transaction_ffi.rs @@ -150,6 +150,7 @@ impl Authorization for PrecomputedAuth { type TransparentAuth = TransparentAuth; type SaplingAuth = sapling::Authorized; type OrchardAuth = orchard::bundle::Authorized; + // FIXME: add IssueAuth? } pub struct PrecomputedTxParts { diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index 31ddbab99d3..999a17d1028 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -357,6 +357,7 @@ void test_simple_joinsplit_invalidity(uint32_t consensusBranchId, CMutableTransa auto verifier = ProofVerifier::Strict(); std::optional> saplingAuth = std::nullopt; std::optional> orchardAuth = std::nullopt; + std::optional> issueAuth = std::nullopt; { // Ensure that empty vin/vout remain invalid without // joinsplits. @@ -393,7 +394,7 @@ void test_simple_joinsplit_invalidity(uint32_t consensusBranchId, CMutableTransa BOOST_CHECK(!ContextualCheckShieldedInputs( newTx, txdata, state, view, - saplingAuth, orchardAuth, + saplingAuth, orchardAuth, issueAuth, Params().GetConsensus(), consensusBranchId, false, true)); @@ -415,7 +416,7 @@ void test_simple_joinsplit_invalidity(uint32_t consensusBranchId, CMutableTransa BOOST_CHECK(ContextualCheckShieldedInputs( newTx, txdata, state, view, - saplingAuth, orchardAuth, + saplingAuth, orchardAuth, issueAuth, Params().GetConsensus(), consensusBranchId, false, true));