diff --git a/src/coins.cpp b/src/coins.cpp index f2999df13..0145c225f 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -914,7 +914,7 @@ CAmount CCoinsViewCache::GetValueIn(const CTransaction& tx) const return nResult; } -bool CCoinsViewCache::HaveShieldedRequirements(const CTransaction& tx) const +std::optional CCoinsViewCache::HaveShieldedRequirements(const CTransaction& tx) const { boost::unordered_map intermediates; @@ -925,7 +925,7 @@ bool CCoinsViewCache::HaveShieldedRequirements(const CTransaction& tx) const if (GetNullifier(nullifier, SPROUT)) { // If the nullifier is set, this transaction // double-spends! - return false; + return UnsatisfiedShieldedReq::SproutDuplicateNullifier; } } @@ -934,7 +934,7 @@ bool CCoinsViewCache::HaveShieldedRequirements(const CTransaction& tx) const if (it != intermediates.end()) { tree = it->second; } else if (!GetSproutAnchorAt(joinsplit.anchor, tree)) { - return false; + return UnsatisfiedShieldedReq::SproutUnknownAnchor; } BOOST_FOREACH(const uint256& commitment, joinsplit.commitments) @@ -946,16 +946,17 @@ bool CCoinsViewCache::HaveShieldedRequirements(const CTransaction& tx) const } for (const SpendDescription &spendDescription : tx.vShieldedSpend) { - if (GetNullifier(spendDescription.nullifier, SAPLING)) // Prevent double spends - return false; + if (GetNullifier(spendDescription.nullifier, SAPLING)) { // Prevent double spends + return UnsatisfiedShieldedReq::SaplingDuplicateNullifier; + } SaplingMerkleTree tree; if (!GetSaplingAnchorAt(spendDescription.anchor, tree)) { - return false; + return UnsatisfiedShieldedReq::SaplingUnknownAnchor; } } - return true; + return std::nullopt; } bool CCoinsViewCache::HaveInputs(const CTransaction& tx) const diff --git a/src/coins.h b/src/coins.h index 10999e8d9..67cba90e8 100644 --- a/src/coins.h +++ b/src/coins.h @@ -447,6 +447,14 @@ public: friend class CCoinsViewCache; }; +/** The set of shielded requirements that might be unsatisfied. */ +enum class UnsatisfiedShieldedReq { + SproutDuplicateNullifier, + SproutUnknownAnchor, + SaplingDuplicateNullifier, + SaplingUnknownAnchor, +}; + /** CCoinsView that adds a memory cache for transactions to another CCoinsView */ class CCoinsViewCache : public CCoinsViewBacked { @@ -566,7 +574,7 @@ public: bool HaveInputs(const CTransaction& tx) const; //! Check whether all joinsplit and sapling spend requirements (anchors/nullifiers) are satisfied - bool HaveShieldedRequirements(const CTransaction& tx) const; + std::optional HaveShieldedRequirements(const CTransaction& tx) const; //! Return priority of tx at height nHeight double GetPriority(const CTransaction &tx, int nHeight) const; diff --git a/src/main.cpp b/src/main.cpp index 2d43a1263..4fe2df808 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -117,6 +117,28 @@ const string strMessageMagic = "Zcash Signed Message:\n"; // Internal stuff namespace { + unsigned char ShieldedReqRejectCode(UnsatisfiedShieldedReq shieldedReq) + { + switch (shieldedReq) { + case UnsatisfiedShieldedReq::SproutDuplicateNullifier: + case UnsatisfiedShieldedReq::SaplingDuplicateNullifier: + return REJECT_DUPLICATE; + case UnsatisfiedShieldedReq::SproutUnknownAnchor: + case UnsatisfiedShieldedReq::SaplingUnknownAnchor: + return REJECT_INVALID; + } + } + + std::string ShieldedReqRejectReason(UnsatisfiedShieldedReq shieldedReq) + { + switch (shieldedReq) { + case UnsatisfiedShieldedReq::SproutDuplicateNullifier: return "bad-txns-sprout-duplicate-nullifier"; + case UnsatisfiedShieldedReq::SproutUnknownAnchor: return "bad-txns-sprout-unknown-anchor"; + case UnsatisfiedShieldedReq::SaplingDuplicateNullifier: return "bad-txns-sapling-duplicate-nullifier"; + case UnsatisfiedShieldedReq::SaplingUnknownAnchor: return "bad-txns-sapling-unknown-anchor"; + } + } + /** Abort with a message */ bool AbortNode(const std::string& strMessage, const std::string& userMessage="") { @@ -1542,10 +1564,14 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa return state.Invalid(error("AcceptToMemoryPool: inputs already spent"), REJECT_DUPLICATE, "bad-txns-inputs-spent"); - // are the joinsplits' and sapling spends' requirements met in tx(valid anchors/nullifiers)? - if (!view.HaveShieldedRequirements(tx)) - return state.Invalid(error("AcceptToMemoryPool: shielded requirements not met"), - REJECT_DUPLICATE, "bad-txns-shielded-requirements-not-met"); + // Are the shielded spends' requirements met? + auto unmetShieldedReq = view.HaveShieldedRequirements(tx); + if (unmetShieldedReq) { + auto rejectCode = ShieldedReqRejectCode(*unmetShieldedReq); + auto rejectReason = ShieldedReqRejectReason(*unmetShieldedReq); + return state.Invalid(error("AcceptToMemoryPool: shielded requirements not met (%s)", rejectReason), + rejectCode, rejectReason); + } // Bring the best block into scope view.GetBestBlock(); @@ -2176,9 +2202,14 @@ bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoins if (!inputs.HaveInputs(tx)) return state.Invalid(error("CheckInputs(): %s inputs unavailable", tx.GetHash().ToString())); - // are the JoinSplit's requirements met? - if (!inputs.HaveShieldedRequirements(tx)) - return state.Invalid(error("CheckInputs(): %s JoinSplit requirements not met", tx.GetHash().ToString())); + // Are the shielded spends' requirements met? + auto unmetShieldedReq = inputs.HaveShieldedRequirements(tx); + if (unmetShieldedReq) { + auto rejectCode = ShieldedReqRejectCode(*unmetShieldedReq); + auto rejectReason = ShieldedReqRejectReason(*unmetShieldedReq); + return state.Invalid(error("CheckInputs(): %s shielded requirements not met (%s)", tx.GetHash().ToString(), rejectReason), + rejectCode, rejectReason); + } CAmount nValueIn = 0; CAmount nFees = 0; @@ -2861,10 +2892,14 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin return state.DoS(100, error("ConnectBlock(): inputs missing/spent"), REJECT_INVALID, "bad-txns-inputs-missingorspent"); - // are the JoinSplit's requirements met? - if (!view.HaveShieldedRequirements(tx)) - return state.DoS(100, error("ConnectBlock(): JoinSplit requirements not met"), - REJECT_INVALID, "bad-txns-joinsplit-requirements-not-met"); + // Are the shielded spends' requirements met? + auto unmetShieldedReq = view.HaveShieldedRequirements(tx); + if (unmetShieldedReq) { + auto rejectCode = ShieldedReqRejectCode(*unmetShieldedReq); + auto rejectReason = ShieldedReqRejectReason(*unmetShieldedReq); + return state.DoS(100, error("ConnectBlock(): shielded requirements not met (%s)", rejectReason), + rejectCode, rejectReason); + } // insightexplorer // https://github.com/bitpay/bitcoin/commit/017f548ea6d89423ef568117447e61dd5707ec42#diff-7ec3c68a81efff79b6ca22ac1f1eabbaR2597