Improve reject reasons for unmet shielded requirements
These reject messages end up bubbling up to users via the RPC interface. Distinguishing between the various failure cases will help users figure out why their transaction is being rejected. Uses operator* instead of std::optional::value because the latter was introduced in macOS 10.14, and our current minimum is 10.12. Closes zcash/zcash#3114.
This commit is contained in:
parent
693def7bac
commit
47c0c65326
|
@ -914,7 +914,7 @@ CAmount CCoinsViewCache::GetValueIn(const CTransaction& tx) const
|
|||
return nResult;
|
||||
}
|
||||
|
||||
bool CCoinsViewCache::HaveShieldedRequirements(const CTransaction& tx) const
|
||||
std::optional<UnsatisfiedShieldedReq> CCoinsViewCache::HaveShieldedRequirements(const CTransaction& tx) const
|
||||
{
|
||||
boost::unordered_map<uint256, SproutMerkleTree, CCoinsKeyHasher> 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
|
||||
|
|
10
src/coins.h
10
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<UnsatisfiedShieldedReq> HaveShieldedRequirements(const CTransaction& tx) const;
|
||||
|
||||
//! Return priority of tx at height nHeight
|
||||
double GetPriority(const CTransaction &tx, int nHeight) const;
|
||||
|
|
57
src/main.cpp
57
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
|
||||
|
|
Loading…
Reference in New Issue