Files
hush3/src/mempool_accept.cpp
dan_s 0083cd26bb Split main.cpp (8,217 lines) into four focused translation units
- tx_validation.cpp (1,012 lines): transaction validation functions
  (IsStandardTx, CheckTransaction, ContextualCheckInputs, etc.)
- mempool_accept.cpp (524 lines): mempool acceptance and orphan management
  (AcceptToMemoryPool, AddOrphanTx, GetMinRelayFee, etc.)
- block_processing.cpp (4,064 lines): block processing, chain management,
  and disk I/O (ConnectBlock, DisconnectBlock, ActivateBestChain, CheckBlock,
  LoadBlockIndex, FlushStateToDisk, etc.)
- main_internal.h (83 lines): shared internal state declarations formerly in
  anonymous namespace (CBlockIndexWorkComparator, setBlockIndexCandidates,
  vinfoBlockFile, setDirtyBlockIndex, etc.)

main.cpp retains NET message handling (ProcessMessage, SendMessages),
peer state management, and utility functions (2,821 lines).
2026-02-27 01:51:24 -06:00

525 lines
22 KiB
C++

// 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 <cstring>
#include <algorithm>
#include <atomic>
#include <sstream>
#include <map>
#include <unordered_map>
#include <vector>
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<uint256, COrphanTx> mapOrphanTransactions;
extern map<uint256, set<uint256> > 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<uint256, COrphanTx>::iterator it = mapOrphanTransactions.find(hash);
if (it == mapOrphanTransactions.end())
return;
BOOST_FOREACH(const CTxIn& txin, it->second.tx.vin)
{
map<uint256, set<uint256> >::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<uint256, COrphanTx>::iterator iter = mapOrphanTransactions.begin();
while (iter != mapOrphanTransactions.end())
{
map<uint256, COrphanTx>::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<uint256, COrphanTx>::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<libzcash::GrothProof> 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<libzcash::GrothProof> 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);
}