// Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2014 The Bitcoin Core developers // Copyright (c) 2016-2024 The Hush developers // Distributed under the GPLv3 software license, see the accompanying // file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html // // Mempool acceptance and orphan transaction management — extracted from main.cpp // Functions: AddOrphanTx, EraseOrphanTx, EraseOrphansFor, LimitOrphanTxSize, // GetMinRelayFee, AcceptToMemoryPool, CCTxFixAcceptToMemPoolUnchecked, myAddtomempool #include "main.h" #include "sodium.h" #include "arith_uint256.h" #include "chainparams.h" #include "consensus/upgrades.h" #include "consensus/validation.h" #include "core_io.h" #include "init.h" #include "key_io.h" #include "metrics.h" #include "net.h" #include "script/interpreter.h" #include "timedata.h" #include "txdb.h" #include "txmempool.h" #include "undo.h" #include "util.h" #include "utilmoneystr.h" #include "validationinterface.h" #include "hush_defs.h" #include "hush.h" #include "librustzcash.h" #include #include #include #include #include #include #include using namespace std; extern int32_t HUSH_LOADINGBLOCKS,HUSH_LONGESTCHAIN,HUSH_INSYNC,HUSH_CONNECTING,HUSH_EXTRASATOSHI; extern unsigned int expiryDelta; extern CFeeRate minRelayTxFee; extern bool fAddressIndex; extern bool fSpentIndex; extern const bool ishush3; #define ASYNC_RPC_OPERATION_DEFAULT_MINERS_FEE 10000 // Orphan transaction data — defined in main.cpp struct COrphanTx { CTransaction tx; NodeId fromPeer; }; extern map mapOrphanTransactions; extern map > mapOrphanTransactionsByPrev; bool AddOrphanTx(const CTransaction& tx, NodeId peer) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { uint256 hash = tx.GetHash(); if (mapOrphanTransactions.count(hash)) return false; // Ignore big transactions, to avoid a // send-big-orphans memory exhaustion attack. If a peer has a legitimate // large transaction with a missing parent then we assume // it will rebroadcast it later, after the parent transaction(s) // have been mined or received. // 10,000 orphans, each of which is at most 5,000 bytes big is // at most 500 megabytes of orphans: unsigned int sz = GetSerializeSize(tx, SER_NETWORK, tx.nVersion); if (sz > 5000) { LogPrint("mempool", "ignoring large orphan tx (size: %u, hash: %s)\n", sz, hash.ToString()); return false; } mapOrphanTransactions[hash].tx = tx; mapOrphanTransactions[hash].fromPeer = peer; BOOST_FOREACH(const CTxIn& txin, tx.vin) mapOrphanTransactionsByPrev[txin.prevout.hash].insert(hash); LogPrint("mempool", "stored orphan tx %s (mapsz %u prevsz %u)\n", hash.ToString(), mapOrphanTransactions.size(), mapOrphanTransactionsByPrev.size()); return true; } void EraseOrphanTx(uint256 hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { map::iterator it = mapOrphanTransactions.find(hash); if (it == mapOrphanTransactions.end()) return; BOOST_FOREACH(const CTxIn& txin, it->second.tx.vin) { map >::iterator itPrev = mapOrphanTransactionsByPrev.find(txin.prevout.hash); if (itPrev == mapOrphanTransactionsByPrev.end()) continue; itPrev->second.erase(hash); if (itPrev->second.empty()) mapOrphanTransactionsByPrev.erase(itPrev); } mapOrphanTransactions.erase(it); } void EraseOrphansFor(NodeId peer) { int nErased = 0; map::iterator iter = mapOrphanTransactions.begin(); while (iter != mapOrphanTransactions.end()) { map::iterator maybeErase = iter++; // increment to avoid iterator becoming invalid if (maybeErase->second.fromPeer == peer) { EraseOrphanTx(maybeErase->second.tx.GetHash()); ++nErased; } } if (nErased > 0) LogPrint("mempool", "Erased %d orphan tx from peer %d\n", nErased, peer); } unsigned int LimitOrphanTxSize(unsigned int nMaxOrphans) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { unsigned int nEvicted = 0; while (mapOrphanTransactions.size() > nMaxOrphans) { // Evict a random orphan: uint256 randomhash = GetRandHash(); map::iterator it = mapOrphanTransactions.lower_bound(randomhash); if (it == mapOrphanTransactions.end()) it = mapOrphanTransactions.begin(); EraseOrphanTx(it->first); ++nEvicted; } return nEvicted; } CAmount GetMinRelayFee(const CTransaction& tx, unsigned int nBytes, bool fAllowFree) { { LOCK(mempool.cs); uint256 hash = tx.GetHash(); double dPriorityDelta = 0; CAmount nFeeDelta = 0; mempool.ApplyDeltas(hash, dPriorityDelta, nFeeDelta); if (dPriorityDelta > 0 || nFeeDelta > 0) return 0; } CAmount nMinFee = ::minRelayTxFee.GetFee(nBytes); if (fAllowFree) { // There is a free transaction area in blocks created by most miners, // * If we are relaying we allow transactions up to DEFAULT_BLOCK_PRIORITY_SIZE - 1000 // to be considered to fall into this category. We don't want to encourage sending // multiple transactions instead of one big transaction to avoid fees. if (nBytes < (DEFAULT_BLOCK_PRIORITY_SIZE - 1000)) nMinFee = 0; } if (!MoneyRange(nMinFee)) nMinFee = MAX_MONEY; return nMinFee; } bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransaction &tx, bool fLimitFree,bool* pfMissingInputs, bool fRejectAbsurdFee, int dosLevel) { AssertLockHeld(cs_main); const uint32_t z2zTransitionWindow = 10; const uint32_t z2zTransitionStart = 340000 - z2zTransitionWindow; const uint32_t nHeight = chainActive.Height(); // This only applies to HUSH3, other chains can start off z2z via ac_private=1 if(ishush3) { if((nHeight >= z2zTransitionStart) || (nHeight <= 340000)) { // During the z2z transition window, only coinbase tx's as part of blocks are allowed // Theory: We want an empty mempool at our fork block height, and the only way to assure that // is to have an empty mempool for a few previous blocks, to take care of potential re-orgs // and edge cases. This empty mempool assures there will be no transactions involving taddrs // stuck in the mempool, when the z2z rule takes effect. // Thanks to jl777 for helping design this fprintf(stderr,"%s: rejecting all tx's during z2z transition window. Please retry after Block %d !!!\n", __func__,nHeight); return false; } } if (pfMissingInputs) *pfMissingInputs = false; uint32_t tiptime; int flag=0,nextBlockHeight = chainActive.Height() + 1; auto consensusBranchId = CurrentEpochBranchId(nextBlockHeight, Params().GetConsensus()); if ( nextBlockHeight <= 1 || chainActive.LastTip() == 0 ) tiptime = (uint32_t)time(NULL); else tiptime = (uint32_t)chainActive.LastTip()->nTime; auto verifier = libzcash::ProofVerifier::Strict(); if (!CheckTransaction(tiptime,tx, state, verifier, 0, 0)) { return error("AcceptToMemoryPool: CheckTransaction failed"); } // Reject duplicate output proofs in a single ztx in mempool // Migrate this to CheckTransaction() to make it a consensus requirement { set vSaplingOutputProof; BOOST_FOREACH(const OutputDescription& output, tx.vShieldedOutput) { if (vSaplingOutputProof.count(output.zkproof)) return state.Invalid(error("AcceptToMemoryPool: duplicate output proof"),REJECT_DUPLICATE_OUTPUT_PROOF, "bad-txns-duplicate-output-proof"); vSaplingOutputProof.insert(output.zkproof); } } // Reject duplicate spend proofs in a single ztx in mempool // Migrate this to CheckTransaction() to make it a consensus requirement { set vSaplingSpendProof; BOOST_FOREACH(const SpendDescription& spend, tx.vShieldedSpend) { if (vSaplingSpendProof.count(spend.zkproof)) return state.Invalid(error("AcceptToMemoryPool: duplicate spend proof"),REJECT_DUPLICATE_SPEND_PROOF, "bad-txns-duplicate-spend-proof"); vSaplingSpendProof.insert(spend.zkproof); } } // DoS level set to 10 to be more forgiving. // Check transaction contextually against the set of consensus rules which apply in the next block to be mined. if (!ContextualCheckTransaction(0,0,0,tx, state, nextBlockHeight, (dosLevel == -1) ? 10 : dosLevel)) { return error("AcceptToMemoryPool: ContextualCheckTransaction failed"); } //fprintf(stderr,"addmempool 2\n"); // Coinbase is only valid in a block, not as a loose transaction if (tx.IsCoinBase()) { fprintf(stderr,"AcceptToMemoryPool coinbase as individual tx\n"); return state.DoS(100, error("AcceptToMemoryPool: coinbase as individual tx"),REJECT_INVALID, "coinbase"); } // Rather not work on nonstandard transactions (unless -testnet/-regtest) string reason; if (Params().RequireStandard() && !IsStandardTx(tx, reason, nextBlockHeight)) { // //fprintf(stderr,"AcceptToMemoryPool reject nonstandard transaction: %s\nscriptPubKey: %s\n",reason.c_str(),tx.vout[0].scriptPubKey.ToString().c_str()); return state.DoS(0,error("AcceptToMemoryPool: nonstandard transaction: %s", reason),REJECT_NONSTANDARD, reason); } // Only accept nLockTime-using transactions that can be mined in the next // block; we don't want our mempool filled up with transactions that can't // be mined yet. if (!CheckFinalTx(tx, STANDARD_LOCKTIME_VERIFY_FLAGS)) { //fprintf(stderr,"AcceptToMemoryPool reject non-final\n"); return state.DoS(0, false, REJECT_NONSTANDARD, "non-final"); } // is it already in the memory pool? uint256 hash = tx.GetHash(); if (pool.exists(hash)) { //fprintf(stderr,"already in mempool\n"); return state.Invalid(false, REJECT_DUPLICATE, "already in mempool"); } // Check for conflicts with in-memory transactions { LOCK(pool.cs); // protect pool.mapNextTx for (unsigned int i = 0; i < tx.vin.size(); i++) { COutPoint outpoint = tx.vin[i].prevout; if (pool.mapNextTx.count(outpoint)) { // Disable replacement feature for now return false; } } for (const SpendDescription &spendDescription : tx.vShieldedSpend) { if (pool.nullifierExists(spendDescription.nullifier, SAPLING)) { return false; } } } { CCoinsView dummy; CCoinsViewCache view(&dummy); int64_t interest; CAmount nValueIn = 0; { LOCK(pool.cs); CCoinsViewMemPool viewMemPool(pcoinsTip, pool); view.SetBackend(viewMemPool); // do we already have it? if (view.HaveCoins(hash)) { //fprintf(stderr,"view.HaveCoins(hash) error\n"); return state.Invalid(false, REJECT_DUPLICATE, "already have coins"); } { // do all inputs exist? // Note that this does not check for the presence of actual outputs (see the next check for that), // and only helps with filling in pfMissingInputs (to determine missing vs spent). BOOST_FOREACH(const CTxIn txin, tx.vin) { if (!view.HaveCoins(txin.prevout.hash)) { if (pfMissingInputs) *pfMissingInputs = true; //fprintf(stderr,"missing inputs\n"); return false; // https://github.com/zcash/zcash/blob/master/src/main.cpp#L1490 // state.DoS(0, error("AcceptToMemoryPool: tx inputs not found"),REJECT_INVALID, "bad-txns-inputs-missing"); } } // are the actual inputs available? if (!view.HaveInputs(tx)) { //fprintf(stderr,"accept failure. inputs-spent\n"); return state.Invalid(error("AcceptToMemoryPool: inputs already spent"),REJECT_DUPLICATE, "bad-txns-inputs-spent"); } } // are the zaddr requirements met? if (!view.HaveShieldedRequirements(tx)) { //fprintf(stderr,"accept failure. ztx reqs not met\n"); return state.Invalid(error("AcceptToMemoryPool: shielded requirements not met"),REJECT_DUPLICATE, "bad-txns-shielded-requirements-not-met"); } // Bring the best block into scope view.GetBestBlock(); nValueIn = view.GetValueIn(chainActive.LastTip()->GetHeight(),&interest,tx,chainActive.LastTip()->nTime); // we have all inputs cached now, so switch back to dummy, so we don't need to keep lock on mempool view.SetBackend(dummy); } // Check for non-standard pay-to-script-hash in inputs if (Params().RequireStandard() && !AreInputsStandard(tx, view, consensusBranchId)) return error("AcceptToMemoryPool: reject nonstandard transaction input"); // Check that the transaction doesn't have an excessive number of // sigops, making it impossible to mine. Since the coinbase transaction // itself can contain sigops MAX_STANDARD_TX_SIGOPS is less than // MAX_BLOCK_SIGOPS; we still consider this an invalid rather than // merely non-standard transaction. unsigned int nSigOps = GetLegacySigOpCount(tx); nSigOps += GetP2SHSigOpCount(tx, view); if (nSigOps > MAX_STANDARD_TX_SIGOPS) { fprintf(stderr,"accept failure.4\n"); return state.DoS(1, error("AcceptToMemoryPool: too many sigops %s, %d > %d", hash.ToString(), nSigOps, MAX_STANDARD_TX_SIGOPS),REJECT_NONSTANDARD, "bad-txns-too-many-sigops"); } CAmount nValueOut = tx.GetValueOut(); CAmount nFees = nValueIn-nValueOut; double dPriority = view.GetPriority(tx, chainActive.Height()); if ( nValueOut > 777777*COIN && HUSH_VALUETOOBIG(nValueOut - 777777*COIN) != 0 ) // some room for blockreward and txfees return state.DoS(100, error("AcceptToMemoryPool: GetValueOut too big"),REJECT_INVALID,"tx valueout is too big"); // Keep track of transactions that spend a coinbase, which we re-scan // during reorgs to ensure COINBASE_MATURITY is still met. bool fSpendsCoinbase = false; BOOST_FOREACH(const CTxIn &txin, tx.vin) { const CCoins *coins = view.AccessCoins(txin.prevout.hash); if (coins->IsCoinBase()) { fSpendsCoinbase = true; break; } } // Grab the branch ID we expect this transaction to commit to. We don't // yet know if it does, but if the entry gets added to the mempool, then // it has passed ContextualCheckInputs and therefore this is correct. auto consensusBranchId = CurrentEpochBranchId(chainActive.Height() + 1, Params().GetConsensus()); CTxMemPoolEntry entry(tx, nFees, GetTime(), dPriority, chainActive.Height(), mempool.HasNoInputsOf(tx), fSpendsCoinbase, consensusBranchId); unsigned int nSize = entry.GetTxSize(); // Accept a tx if it contains zspends and has at least the default fee specified by z_sendmany. if (tx.vShieldedSpend.size() > 0 && nFees >= ASYNC_RPC_OPERATION_DEFAULT_MINERS_FEE) { // In future we will we have more accurate and dynamic computation of fees, derpz } else { // Don't accept it if it can't get into a block, yallz CAmount txMinFee = GetMinRelayFee(tx, nSize, true); if (fLimitFree && nFees < txMinFee) { //fprintf(stderr,"accept failure.5\n"); return state.DoS(0, error("AcceptToMemoryPool: not enough fees %s, %d < %d",hash.ToString(), nFees, txMinFee),REJECT_INSUFFICIENTFEE, "insufficient fee"); } } // Require that free transactions have sufficient priority to be mined in the next block. if (GetBoolArg("-relaypriority", false) && nFees < ::minRelayTxFee.GetFee(nSize) && !AllowFree(view.GetPriority(tx, chainActive.Height() + 1))) { fprintf(stderr,"accept failure.6\n"); return state.DoS(0, false, REJECT_INSUFFICIENTFEE, "insufficient priority"); } // Continuously rate-limit free (really, very-low-fee) transactions // This mitigates 'penny-flooding' -- sending thousands of free transactions just to // be annoying or make others' transactions take longer to confirm. if (fLimitFree && nFees < ::minRelayTxFee.GetFee(nSize) ) { static CCriticalSection csFreeLimiter; static double dFreeCount; static int64_t nLastTime; int64_t nNow = GetTime(); LOCK(csFreeLimiter); // Use an exponentially decaying ~10-minute window: dFreeCount *= pow(1.0 - 1.0/600.0, (double)(nNow - nLastTime)); nLastTime = nNow; // -limitfreerelay unit is thousand-bytes-per-minute // At default rate it would take over a month to fill 1GB if (dFreeCount >= GetArg("-limitfreerelay", 15)*10*1000) { fprintf(stderr,"accept failure.7\n"); return state.DoS(0, error("AcceptToMemoryPool: free transaction rejected by rate limiter"), REJECT_INSUFFICIENTFEE, "rate limited free transaction"); } LogPrint("mempool", "Rate limit dFreeCount: %g => %g\n", dFreeCount, dFreeCount+nSize); dFreeCount += nSize; } fRejectAbsurdFee = false; if ( fRejectAbsurdFee && nFees > ::minRelayTxFee.GetFee(nSize) * 10000 && nFees > nValueOut/19) // Disable checks for absurd fees when adding to the mempool. Instead, this check is done // when a user attempts to make a transaction with an absurd fee and only rejects absurd // fees when OP_RETURN data is NOT being used. This means users making normal financial // transactions (z2z) are protected from absurd fees, it is only users who are storing // arbitrary data via a z2t transaction are allowed to (or potentially required) to pay high fees // It would be nice to detect the use of OP_RETURN right here but it seems to only be known // inside of IsStandard() inside of IsStandardTx() and we want to avoid doing expensive checks // multiple times. { string errmsg = strprintf("absurdly high fees %s, %d > %d", hash.ToString(), nFees, ::minRelayTxFee.GetFee(nSize) * 10000); LogPrint("mempool", errmsg.c_str()); return state.Error("AcceptToMemoryPool: " + errmsg); } //fprintf(stderr,"addmempool 6\n"); // Check against previous transactions // This is done last to help prevent CPU exhaustion denial-of-service attacks. PrecomputedTransactionData txdata(tx); if (!ContextualCheckInputs(tx, state, view, true, STANDARD_SCRIPT_VERIFY_FLAGS, true, txdata, Params().GetConsensus(), consensusBranchId)) { //fprintf(stderr,"accept failure.9\n"); return error("AcceptToMemoryPool: ConnectInputs failed %s", hash.ToString()); } // Check again against just the consensus-critical mandatory script // verification flags, in case of bugs in the standard flags that cause // transactions to pass as valid when they're actually invalid. For // instance the STRICTENC flag was incorrectly allowing certain // CHECKSIG NOT scripts to pass, even though they were invalid. // // There is a similar check in CreateNewBlock() to prevent creating // invalid blocks, however allowing such transactions into the mempool // can be exploited as a DoS attack. // XXX: is this neccesary for CryptoConditions? if ( HUSH_CONNECTING <= 0 && chainActive.LastTip() != 0 ) { flag = 1; HUSH_CONNECTING = (1<<30) + (int32_t)chainActive.LastTip()->GetHeight() + 1; } if (!ContextualCheckInputs(tx, state, view, true, MANDATORY_SCRIPT_VERIFY_FLAGS, true, txdata, Params().GetConsensus(), consensusBranchId)) { if ( flag != 0 ) HUSH_CONNECTING = -1; return error("AcceptToMemoryPool: BUG! PLEASE REPORT THIS! ConnectInputs failed against MANDATORY but not STANDARD flags %s", hash.ToString()); } if ( flag != 0 ) HUSH_CONNECTING = -1; { LOCK(pool.cs); // Store transaction in memory pool.addUnchecked(hash, entry, !IsInitialBlockDownload()); // Add memory address index if (fAddressIndex) { pool.addAddressIndex(entry, view); } // Add memory spent index if (fSpentIndex) { pool.addSpentIndex(entry, view); } } } return true; } bool CCTxFixAcceptToMemPoolUnchecked(CTxMemPool& pool, const CTransaction &tx) { // called from CheckBlock which is in cs_main and mempool.cs locks already. auto consensusBranchId = CurrentEpochBranchId(chainActive.Height() + 1, Params().GetConsensus()); CTxMemPoolEntry entry(tx, 0, GetTime(), 0, chainActive.Height(), mempool.HasNoInputsOf(tx), false, consensusBranchId); //fprintf(stderr, "adding %s to mempool from block %d\n",tx.GetHash().ToString().c_str(),chainActive.GetHeight()); pool.addUnchecked(tx.GetHash(), entry, false); return true; } bool myAddtomempool(CTransaction &tx, CValidationState *pstate, bool fSkipExpiry) { CValidationState state; if (!pstate) pstate = &state; CTransaction Ltx; bool fMissingInputs,fOverrideFees = false; if ( mempool.lookup(tx.GetHash(),Ltx) == 0 ) { if ( !fSkipExpiry ) return(AcceptToMemoryPool(mempool, *pstate, tx, false, &fMissingInputs, !fOverrideFees, -1)); else return(CCTxFixAcceptToMemPoolUnchecked(mempool,tx)); } else return(true); }