mirror of
https://github.com/dashpay/dash.git
synced 2024-12-25 03:52:49 +01:00
merge #16400: refactor: Rewrite AcceptToMemoryPoolWorker() using smaller parts
This commit is contained in:
parent
ce3e8072a7
commit
bd00a6a2e8
@ -506,22 +506,124 @@ static bool CheckInputsFromMempoolAndCache(const CTransaction& tx, CValidationSt
|
||||
return CheckInputs(tx, state, view, flags, /* cacheSigStore = */ true, /* cacheFullSciptStore = */ true, txdata);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param[out] coins_to_uncache Return any outpoints which were not previously present in the
|
||||
* coins cache, but were added as a result of validating the tx
|
||||
* for mempool acceptance. This allows the caller to optionally
|
||||
* remove the cache additions if the associated transaction ends
|
||||
* up being rejected by the mempool.
|
||||
*/
|
||||
static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool& pool, CValidationState& state, const CTransactionRef& ptx,
|
||||
bool* pfMissingInputs, int64_t nAcceptTime, bool bypass_limits,
|
||||
const CAmount& nAbsurdFee, std::vector<COutPoint>& coins_to_uncache, bool test_accept) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
|
||||
namespace {
|
||||
|
||||
class MemPoolAccept
|
||||
{
|
||||
boost::posix_time::ptime start = boost::posix_time::microsec_clock::local_time();
|
||||
const CTransaction& tx = *ptx;
|
||||
const uint256 hash = tx.GetHash();
|
||||
AssertLockHeld(cs_main);
|
||||
LOCK(pool.cs); // mempool "read lock" (held through GetMainSignals().TransactionAddedToMempool())
|
||||
public:
|
||||
MemPoolAccept(CTxMemPool& mempool) : m_pool(mempool), m_view(&m_dummy), m_viewmempool(&::ChainstateActive().CoinsTip(), m_pool),
|
||||
m_limit_ancestors(gArgs.GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT)),
|
||||
m_limit_ancestor_size(gArgs.GetArg("-limitancestorsize", DEFAULT_ANCESTOR_SIZE_LIMIT)*1000),
|
||||
m_limit_descendants(gArgs.GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT)),
|
||||
m_limit_descendant_size(gArgs.GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT)*1000) {}
|
||||
|
||||
// We put the arguments we're handed into a struct, so we can pass them
|
||||
// around easier.
|
||||
struct ATMPArgs {
|
||||
const CChainParams& m_chainparams;
|
||||
CValidationState &m_state;
|
||||
bool* m_missing_inputs;
|
||||
const int64_t m_accept_time;
|
||||
const bool m_bypass_limits;
|
||||
const CAmount& m_absurd_fee;
|
||||
/*
|
||||
* Return any outpoints which were not previously present in the coins
|
||||
* cache, but were added as a result of validating the tx for mempool
|
||||
* acceptance. This allows the caller to optionally remove the cache
|
||||
* additions if the associated transaction ends up being rejected by
|
||||
* the mempool.
|
||||
*/
|
||||
std::vector<COutPoint>& m_coins_to_uncache;
|
||||
const bool m_test_accept;
|
||||
};
|
||||
|
||||
// Single transaction acceptance
|
||||
bool AcceptSingleTransaction(const CTransactionRef& ptx, ATMPArgs& args) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
|
||||
|
||||
private:
|
||||
// All the intermediate state that gets passed between the various levels
|
||||
// of checking a given transaction.
|
||||
struct Workspace {
|
||||
Workspace(const CTransactionRef& ptx) : m_ptx(ptx), m_hash(ptx->GetHash()) {}
|
||||
CTxMemPool::setEntries m_ancestors;
|
||||
std::unique_ptr<CTxMemPoolEntry> m_entry;
|
||||
|
||||
CAmount m_modified_fees;
|
||||
|
||||
const CTransactionRef& m_ptx;
|
||||
const uint256& m_hash;
|
||||
};
|
||||
|
||||
// Run the policy checks on a given transaction, excluding any script checks.
|
||||
// Looks up inputs, calculates feerate, considers replacement, evaluates
|
||||
// package limits, etc. As this function can be invoked for "free" by a peer,
|
||||
// only tests that are fast should be done here (to avoid CPU DoS).
|
||||
bool PreChecks(ATMPArgs& args, Workspace& ws) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
|
||||
|
||||
// Run the script checks using our policy flags. As this can be slow, we should
|
||||
// only invoke this on transactions that have otherwise passed policy checks.
|
||||
bool PolicyScriptChecks(ATMPArgs& args, Workspace& ws, PrecomputedTransactionData& txdata) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
|
||||
|
||||
// Re-run the script checks, using consensus flags, and try to cache the
|
||||
// result in the scriptcache. This should be done after
|
||||
// PolicyScriptChecks(). This requires that all inputs either be in our
|
||||
// utxo set or in the mempool.
|
||||
bool ConsensusScriptChecks(ATMPArgs& args, Workspace& ws, PrecomputedTransactionData &txdata) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
|
||||
|
||||
// Try to add the transaction to the mempool, removing any conflicts first.
|
||||
// Returns true if the transaction is in the mempool after any size
|
||||
// limiting is performed, false otherwise.
|
||||
bool Finalize(ATMPArgs& args, Workspace& ws) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
|
||||
|
||||
// Compare a package's feerate against minimum allowed.
|
||||
bool CheckFeeRate(size_t package_size, CAmount package_fee, CValidationState& state)
|
||||
{
|
||||
CAmount mempoolRejectFee = m_pool.GetMinFee(gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000).GetFee(package_size);
|
||||
if (mempoolRejectFee > 0 && package_fee < mempoolRejectFee) {
|
||||
return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_INSUFFICIENTFEE, "mempool min fee not met", strprintf("%d < %d", package_fee, mempoolRejectFee));
|
||||
}
|
||||
|
||||
if (package_fee < ::minRelayTxFee.GetFee(package_size)) {
|
||||
return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_INSUFFICIENTFEE, "min relay fee not met", strprintf("%d < %d", package_fee, ::minRelayTxFee.GetFee(package_size)));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
CTxMemPool& m_pool;
|
||||
CCoinsViewCache m_view;
|
||||
CCoinsViewMemPool m_viewmempool;
|
||||
CCoinsView m_dummy;
|
||||
|
||||
// The package limits in effect at the time of invocation.
|
||||
const size_t m_limit_ancestors;
|
||||
const size_t m_limit_ancestor_size;
|
||||
// These may be modified while evaluating a transaction (eg to account for
|
||||
// in-mempool conflicts; see below).
|
||||
size_t m_limit_descendants;
|
||||
size_t m_limit_descendant_size;
|
||||
};
|
||||
|
||||
bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
||||
{
|
||||
const CTransactionRef& ptx = ws.m_ptx;
|
||||
const CTransaction& tx = *ws.m_ptx;
|
||||
const uint256& hash = ws.m_hash;
|
||||
|
||||
// Copy/alias what we need out of args
|
||||
const CChainParams& chainparams = args.m_chainparams;
|
||||
CValidationState &state = args.m_state;
|
||||
bool* pfMissingInputs = args.m_missing_inputs;
|
||||
const int64_t nAcceptTime = args.m_accept_time;
|
||||
const bool bypass_limits = args.m_bypass_limits;
|
||||
const CAmount& nAbsurdFee = args.m_absurd_fee;
|
||||
std::vector<COutPoint>& coins_to_uncache = args.m_coins_to_uncache;
|
||||
|
||||
// Alias what we need out of ws
|
||||
CTxMemPool::setEntries& setAncestors = ws.m_ancestors;
|
||||
std::unique_ptr<CTxMemPoolEntry>& entry = ws.m_entry;
|
||||
CAmount& nModifiedFees = ws.m_modified_fees;
|
||||
|
||||
if (pfMissingInputs) {
|
||||
*pfMissingInputs = false;
|
||||
}
|
||||
@ -560,7 +662,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
|
||||
return state.Invalid(ValidationInvalidReason::TX_PREMATURE_SPEND, false, REJECT_NONSTANDARD, "non-final");
|
||||
|
||||
// is it already in the memory pool?
|
||||
if (pool.exists(hash)) {
|
||||
if (m_pool.exists(hash)) {
|
||||
statsClient.inc("transactions.duplicate", 1.0f);
|
||||
return state.Invalid(ValidationInvalidReason::TX_CONFLICT, false, REJECT_DUPLICATE, "txn-already-in-mempool");
|
||||
}
|
||||
@ -568,7 +670,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
|
||||
llmq::CInstantSendLockPtr conflictLock = llmq::quorumInstantSendManager->GetConflictingLock(tx);
|
||||
if (conflictLock) {
|
||||
uint256 hashBlock;
|
||||
CTransactionRef txConflict = GetTransaction(/* block_index */ nullptr, &pool, conflictLock->txid, chainparams.GetConsensus(), hashBlock);
|
||||
CTransactionRef txConflict = GetTransaction(/* block_index */ nullptr, &m_pool, conflictLock->txid, chainparams.GetConsensus(), hashBlock);
|
||||
if (txConflict) {
|
||||
GetMainSignals().NotifyInstantSendDoubleSpendAttempt(ptx, txConflict);
|
||||
}
|
||||
@ -576,13 +678,13 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
|
||||
}
|
||||
|
||||
if (llmq::quorumInstantSendManager->IsWaitingForTx(hash)) {
|
||||
pool.removeConflicts(tx);
|
||||
pool.removeProTxConflicts(tx);
|
||||
m_pool.removeConflicts(tx);
|
||||
m_pool.removeProTxConflicts(tx);
|
||||
} else {
|
||||
// Check for conflicts with in-memory transactions
|
||||
for (const CTxIn &txin : tx.vin)
|
||||
{
|
||||
const CTransaction* ptxConflicting = pool.GetConflictTx(txin.prevout);
|
||||
const CTransaction* ptxConflicting = m_pool.GetConflictTx(txin.prevout);
|
||||
if (ptxConflicting)
|
||||
{
|
||||
// Transaction conflicts with mempool and RBF doesn't exist in Dash
|
||||
@ -591,212 +693,255 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
CCoinsView dummy;
|
||||
CCoinsViewCache view(&dummy);
|
||||
LockPoints lp;
|
||||
m_view.SetBackend(m_viewmempool);
|
||||
|
||||
LockPoints lp;
|
||||
CCoinsViewCache& coins_cache = ::ChainstateActive().CoinsTip();
|
||||
CCoinsViewMemPool viewMemPool(&coins_cache, pool);
|
||||
view.SetBackend(viewMemPool);
|
||||
CCoinsViewCache& coins_cache = ::ChainstateActive().CoinsTip();
|
||||
// do all inputs exist?
|
||||
for (const CTxIn& txin : tx.vin) {
|
||||
if (!coins_cache.HaveCoinInCache(txin.prevout)) {
|
||||
coins_to_uncache.push_back(txin.prevout);
|
||||
}
|
||||
|
||||
// do all inputs exist?
|
||||
for (const CTxIn& txin : tx.vin) {
|
||||
if (!coins_cache.HaveCoinInCache(txin.prevout)) {
|
||||
coins_to_uncache.push_back(txin.prevout);
|
||||
}
|
||||
|
||||
// Note: this call may add txin.prevout to the coins cache
|
||||
// (pcoinsTip.cacheCoins) by way of FetchCoin(). It should be removed
|
||||
// later (via coins_to_uncache) if this tx turns out to be invalid.
|
||||
if (!view.HaveCoin(txin.prevout)) {
|
||||
// Are inputs missing because we already have the tx?
|
||||
for (size_t out = 0; out < tx.vout.size(); out++) {
|
||||
// Optimistically just do efficient check of cache for outputs
|
||||
if (coins_cache.HaveCoinInCache(COutPoint(hash, out))) {
|
||||
return state.Invalid(ValidationInvalidReason::TX_CONFLICT, false, REJECT_DUPLICATE, "txn-already-known");
|
||||
}
|
||||
// Note: this call may add txin.prevout to the coins cache
|
||||
// (coins_cache.cacheCoins) by way of FetchCoin(). It should be removed
|
||||
// later (via coins_to_uncache) if this tx turns out to be invalid.
|
||||
if (!m_view.HaveCoin(txin.prevout)) {
|
||||
// Are inputs missing because we already have the tx?
|
||||
for (size_t out = 0; out < tx.vout.size(); out++) {
|
||||
// Optimistically just do efficient check of cache for outputs
|
||||
if (coins_cache.HaveCoinInCache(COutPoint(hash, out))) {
|
||||
return state.Invalid(ValidationInvalidReason::TX_CONFLICT, false, REJECT_DUPLICATE, "txn-already-known");
|
||||
}
|
||||
// Otherwise assume this might be an orphan tx for which we just haven't seen parents yet
|
||||
if (pfMissingInputs) {
|
||||
*pfMissingInputs = true;
|
||||
}
|
||||
return false; // fMissingInputs and !state.IsInvalid() is used to detect this condition, don't set state.Invalid()
|
||||
}
|
||||
}
|
||||
|
||||
// Bring the best block into scope
|
||||
view.GetBestBlock();
|
||||
|
||||
// we have all inputs cached now, so switch back to dummy, so we don't need to keep lock on mempool
|
||||
view.SetBackend(dummy);
|
||||
|
||||
// Only accept BIP68 sequence locked 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.
|
||||
// Must keep pool.cs for this unless we change CheckSequenceLocks to take a
|
||||
// CoinsViewCache instead of create its own
|
||||
if (!CheckSequenceLocks(pool, tx, STANDARD_LOCKTIME_VERIFY_FLAGS, &lp))
|
||||
return state.Invalid(ValidationInvalidReason::TX_PREMATURE_SPEND, false, REJECT_NONSTANDARD, "non-BIP68-final");
|
||||
|
||||
CAmount nFees = 0;
|
||||
if (!Consensus::CheckTxInputs(tx, state, view, GetSpendHeight(view), nFees)) {
|
||||
return error("%s: Consensus::CheckTxInputs: %s, %s", __func__, tx.GetHash().ToString(), FormatStateMessage(state));
|
||||
}
|
||||
|
||||
// Check for non-standard pay-to-script-hash in inputs
|
||||
if (fRequireStandard && !AreInputsStandard(tx, view))
|
||||
return state.Invalid(ValidationInvalidReason::TX_NOT_STANDARD, false, REJECT_NONSTANDARD, "bad-txns-nonstandard-inputs");
|
||||
|
||||
unsigned int nSigOps = GetTransactionSigOpCount(tx, view, STANDARD_SCRIPT_VERIFY_FLAGS);
|
||||
|
||||
// nModifiedFees includes any fee deltas from PrioritiseTransaction
|
||||
CAmount nModifiedFees = nFees;
|
||||
pool.ApplyDelta(hash, nModifiedFees);
|
||||
|
||||
// Keep track of transactions that spend a coinbase, which we re-scan
|
||||
// during reorgs to ensure COINBASE_MATURITY is still met.
|
||||
bool fSpendsCoinbase = false;
|
||||
for (const CTxIn &txin : tx.vin) {
|
||||
const Coin &coin = view.AccessCoin(txin.prevout);
|
||||
if (coin.IsCoinBase()) {
|
||||
fSpendsCoinbase = true;
|
||||
break;
|
||||
// Otherwise assume this might be an orphan tx for which we just haven't seen parents yet
|
||||
if (pfMissingInputs) {
|
||||
*pfMissingInputs = true;
|
||||
}
|
||||
}
|
||||
|
||||
CTxMemPoolEntry entry(ptx, nFees, nAcceptTime, ::ChainActive().Height(),
|
||||
fSpendsCoinbase, nSigOps, lp);
|
||||
unsigned int nSize = entry.GetTxSize();
|
||||
|
||||
if (nSigOps > MAX_STANDARD_TX_SIGOPS)
|
||||
return state.Invalid(ValidationInvalidReason::TX_NOT_STANDARD, false, REJECT_NONSTANDARD, "bad-txns-too-many-sigops",
|
||||
strprintf("%d", nSigOps));
|
||||
|
||||
CAmount mempoolRejectFee = pool.GetMinFee(gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000).GetFee(nSize);
|
||||
if (!bypass_limits && mempoolRejectFee > 0 && nModifiedFees < mempoolRejectFee) {
|
||||
return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_INSUFFICIENTFEE, "mempool min fee not met", strprintf("%d < %d", nModifiedFees, mempoolRejectFee));
|
||||
}
|
||||
|
||||
// No transactions are allowed below minRelayTxFee except from disconnected blocks
|
||||
if (!bypass_limits && nModifiedFees < ::minRelayTxFee.GetFee(nSize)) {
|
||||
return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_INSUFFICIENTFEE, "min relay fee not met", strprintf("%d < %d", nModifiedFees, ::minRelayTxFee.GetFee(nSize)));
|
||||
}
|
||||
|
||||
if (nAbsurdFee && nFees > nAbsurdFee)
|
||||
return state.Invalid(ValidationInvalidReason::TX_NOT_STANDARD, false,
|
||||
REJECT_HIGHFEE, "absurdly-high-fee",
|
||||
strprintf("%d > %d", nFees, nAbsurdFee));
|
||||
|
||||
// Calculate in-mempool ancestors, up to a limit.
|
||||
CTxMemPool::setEntries setAncestors;
|
||||
size_t nLimitAncestors = gArgs.GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT);
|
||||
size_t nLimitAncestorSize = gArgs.GetArg("-limitancestorsize", DEFAULT_ANCESTOR_SIZE_LIMIT)*1000;
|
||||
size_t nLimitDescendants = gArgs.GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT);
|
||||
size_t nLimitDescendantSize = gArgs.GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT)*1000;
|
||||
std::string errString;
|
||||
if (!pool.CalculateMemPoolAncestors(entry, setAncestors, nLimitAncestors, nLimitAncestorSize, nLimitDescendants, nLimitDescendantSize, errString)) {
|
||||
setAncestors.clear();
|
||||
// If CalculateMemPoolAncestors fails second time, we want the original error string.
|
||||
std::string dummy_err_string;
|
||||
// If the new transaction is relatively small (up to 40k weight)
|
||||
// and has at most one ancestor (ie ancestor limit of 2, including
|
||||
// the new transaction), allow it if its parent has exactly the
|
||||
// descendant limit descendants.
|
||||
//
|
||||
// This allows protocols which rely on distrusting counterparties
|
||||
// being able to broadcast descendants of an unconfirmed transaction
|
||||
// to be secure by simply only having two immediately-spendable
|
||||
// outputs - one for each counterparty. For more info on the uses for
|
||||
// this, see https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2018-November/016518.html
|
||||
if (nSize > EXTRA_DESCENDANT_TX_SIZE_LIMIT ||
|
||||
!pool.CalculateMemPoolAncestors(entry, setAncestors, 2, nLimitAncestorSize, nLimitDescendants + 1, nLimitDescendantSize + EXTRA_DESCENDANT_TX_SIZE_LIMIT, dummy_err_string)) {
|
||||
return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_NONSTANDARD, "too-long-mempool-chain", errString);
|
||||
}
|
||||
}
|
||||
|
||||
// check special TXs after all the other checks. If we'd do this before the other checks, we might end up
|
||||
// DoS scoring a node for non-critical errors, e.g. duplicate keys because a TX is received that was already
|
||||
// mined
|
||||
// NOTE: we use UTXO here and do NOT allow mempool txes as masternode collaterals
|
||||
if (!CheckSpecialTx(tx, ::ChainActive().Tip(), state, ::ChainstateActive().CoinsTip(), true))
|
||||
return false;
|
||||
|
||||
if (pool.existsProviderTxConflict(tx)) {
|
||||
return state.Invalid(ValidationInvalidReason::TX_CONFLICT, false, REJECT_DUPLICATE, "protx-dup");
|
||||
}
|
||||
|
||||
constexpr unsigned int scriptVerifyFlags = STANDARD_SCRIPT_VERIFY_FLAGS;
|
||||
|
||||
// Check against previous transactions
|
||||
// This is done last to help prevent CPU exhaustion denial-of-service attacks.
|
||||
PrecomputedTransactionData txdata;
|
||||
if (!CheckInputs(tx, state, view, scriptVerifyFlags, true, false, txdata)) {
|
||||
assert(IsTransactionReason(state.GetReason()));
|
||||
return false; // state filled in by CheckInputs
|
||||
}
|
||||
|
||||
// Check again against the current block tip's script verification
|
||||
// flags to cache our script execution flags. This is, of course,
|
||||
// useless if the next block has different script flags from the
|
||||
// previous one, but because the cache tracks script flags for us it
|
||||
// will auto-invalidate and we'll just have a few blocks of extra
|
||||
// misses on soft-fork activation.
|
||||
//
|
||||
// This is also useful 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 (using TestBlockValidity), however allowing such
|
||||
// transactions into the mempool can be exploited as a DoS attack.
|
||||
unsigned int currentBlockScriptVerifyFlags = GetBlockScriptFlags(::ChainActive().Tip(), chainparams.GetConsensus());
|
||||
if (!CheckInputsFromMempoolAndCache(tx, state, view, pool, currentBlockScriptVerifyFlags, txdata)) {
|
||||
return error("%s: BUG! PLEASE REPORT THIS! CheckInputs failed against latest-block but not STANDARD flags %s, %s",
|
||||
__func__, hash.ToString(), FormatStateMessage(state));
|
||||
}
|
||||
|
||||
if (test_accept) {
|
||||
// Tx was accepted, but not added
|
||||
return true;
|
||||
}
|
||||
|
||||
// This transaction should only count for fee estimation if:
|
||||
// - it's not being re-added during a reorg which bypasses typical mempool fee limits
|
||||
// - the node is not behind
|
||||
// - the transaction is not dependent on any other transactions in the mempool
|
||||
// - the transaction is not a zero fee transaction
|
||||
bool validForFeeEstimation = (nFees !=0) && !bypass_limits && IsCurrentForFeeEstimation() && pool.HasNoInputsOf(tx);
|
||||
|
||||
// Store transaction in memory
|
||||
pool.addUnchecked(entry, setAncestors, validForFeeEstimation);
|
||||
CAmount nValueOut = tx.GetValueOut();
|
||||
statsClient.count("transactions.sizeBytes", nSize, 1.0f);
|
||||
statsClient.count("transactions.fees", nFees, 1.0f);
|
||||
statsClient.count("transactions.inputValue", nValueOut - nFees, 1.0f);
|
||||
statsClient.count("transactions.outputValue", nValueOut, 1.0f);
|
||||
statsClient.count("transactions.sigOps", nSigOps, 1.0f);
|
||||
|
||||
// Add memory address index
|
||||
if (fAddressIndex) {
|
||||
pool.addAddressIndex(entry, view);
|
||||
}
|
||||
|
||||
// Add memory spent index
|
||||
if (fSpentIndex) {
|
||||
pool.addSpentIndex(entry, view);
|
||||
}
|
||||
|
||||
if (!bypass_limits) {
|
||||
LimitMempoolSize(pool, gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000, std::chrono::hours{gArgs.GetArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY)});
|
||||
if (!pool.exists(hash))
|
||||
return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_INSUFFICIENTFEE, "mempool full");
|
||||
return false; // fMissingInputs and !state.IsInvalid() is used to detect this condition, don't set state.Invalid()
|
||||
}
|
||||
}
|
||||
|
||||
// Bring the best block into scope
|
||||
m_view.GetBestBlock();
|
||||
|
||||
// we have all inputs cached now, so switch back to dummy (to protect
|
||||
// against bugs where we pull more inputs from disk that miss being added
|
||||
// to coins_to_uncache)
|
||||
m_view.SetBackend(m_dummy);
|
||||
|
||||
// Only accept BIP68 sequence locked 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.
|
||||
// Must keep pool.cs for this unless we change CheckSequenceLocks to take a
|
||||
// CoinsViewCache instead of create its own
|
||||
if (!CheckSequenceLocks(m_pool, tx, STANDARD_LOCKTIME_VERIFY_FLAGS, &lp))
|
||||
return state.Invalid(ValidationInvalidReason::TX_PREMATURE_SPEND, false, REJECT_NONSTANDARD, "non-BIP68-final");
|
||||
|
||||
CAmount nFees = 0;
|
||||
if (!Consensus::CheckTxInputs(tx, state, m_view, GetSpendHeight(m_view), nFees)) {
|
||||
return error("%s: Consensus::CheckTxInputs: %s, %s", __func__, tx.GetHash().ToString(), FormatStateMessage(state));
|
||||
}
|
||||
|
||||
// Check for non-standard pay-to-script-hash in inputs
|
||||
if (fRequireStandard && !AreInputsStandard(tx, m_view))
|
||||
return state.Invalid(ValidationInvalidReason::TX_NOT_STANDARD, false, REJECT_NONSTANDARD, "bad-txns-nonstandard-inputs");
|
||||
|
||||
unsigned int nSigOps = GetTransactionSigOpCount(tx, m_view, STANDARD_SCRIPT_VERIFY_FLAGS);
|
||||
|
||||
// nModifiedFees includes any fee deltas from PrioritiseTransaction
|
||||
nModifiedFees = nFees;
|
||||
m_pool.ApplyDelta(hash, nModifiedFees);
|
||||
|
||||
// Keep track of transactions that spend a coinbase, which we re-scan
|
||||
// during reorgs to ensure COINBASE_MATURITY is still met.
|
||||
bool fSpendsCoinbase = false;
|
||||
for (const CTxIn &txin : tx.vin) {
|
||||
const Coin &coin = m_view.AccessCoin(txin.prevout);
|
||||
if (coin.IsCoinBase()) {
|
||||
fSpendsCoinbase = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
entry.reset(new CTxMemPoolEntry(ptx, nFees, nAcceptTime, ::ChainActive().Height(),
|
||||
fSpendsCoinbase, nSigOps, lp));
|
||||
unsigned int nSize = entry->GetTxSize();
|
||||
|
||||
if (nSigOps > MAX_STANDARD_TX_SIGOPS)
|
||||
return state.Invalid(ValidationInvalidReason::TX_NOT_STANDARD, false, REJECT_NONSTANDARD, "bad-txns-too-many-sigops",
|
||||
strprintf("%d", nSigOps));
|
||||
|
||||
// No transactions are allowed below minRelayTxFee except from disconnected
|
||||
// blocks
|
||||
if (!bypass_limits && !CheckFeeRate(nSize, nModifiedFees, state)) return false;
|
||||
|
||||
if (nAbsurdFee && nFees > nAbsurdFee)
|
||||
return state.Invalid(ValidationInvalidReason::TX_NOT_STANDARD, false,
|
||||
REJECT_HIGHFEE, "absurdly-high-fee",
|
||||
strprintf("%d > %d", nFees, nAbsurdFee));
|
||||
|
||||
// Calculate in-mempool ancestors, up to a limit.
|
||||
std::string errString;
|
||||
if (!m_pool.CalculateMemPoolAncestors(*entry, setAncestors, m_limit_ancestors, m_limit_ancestor_size, m_limit_descendants, m_limit_descendant_size, errString)) {
|
||||
setAncestors.clear();
|
||||
// If CalculateMemPoolAncestors fails second time, we want the original error string.
|
||||
std::string dummy_err_string;
|
||||
// If the new transaction is relatively small (up to 40k weight)
|
||||
// and has at most one ancestor (ie ancestor limit of 2, including
|
||||
// the new transaction), allow it if its parent has exactly the
|
||||
// descendant limit descendants.
|
||||
//
|
||||
// This allows protocols which rely on distrusting counterparties
|
||||
// being able to broadcast descendants of an unconfirmed transaction
|
||||
// to be secure by simply only having two immediately-spendable
|
||||
// outputs - one for each counterparty. For more info on the uses for
|
||||
// this, see https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2018-November/016518.html
|
||||
if (nSize > EXTRA_DESCENDANT_TX_SIZE_LIMIT ||
|
||||
!m_pool.CalculateMemPoolAncestors(*entry, setAncestors, 2, m_limit_ancestor_size, m_limit_descendants + 1, m_limit_descendant_size + EXTRA_DESCENDANT_TX_SIZE_LIMIT, dummy_err_string)) {
|
||||
return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_NONSTANDARD, "too-long-mempool-chain", errString);
|
||||
}
|
||||
}
|
||||
|
||||
// check special TXs after all the other checks. If we'd do this before the other checks, we might end up
|
||||
// DoS scoring a node for non-critical errors, e.g. duplicate keys because a TX is received that was already
|
||||
// mined
|
||||
// NOTE: we use UTXO here and do NOT allow mempool txes as masternode collaterals
|
||||
if (!CheckSpecialTx(tx, ::ChainActive().Tip(), state, ::ChainstateActive().CoinsTip(), true))
|
||||
return false;
|
||||
|
||||
if (m_pool.existsProviderTxConflict(tx)) {
|
||||
return state.Invalid(ValidationInvalidReason::TX_CONFLICT, false, REJECT_DUPLICATE, "protx-dup");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MemPoolAccept::PolicyScriptChecks(ATMPArgs& args, Workspace& ws, PrecomputedTransactionData& txdata)
|
||||
{
|
||||
const CTransaction& tx = *ws.m_ptx;
|
||||
|
||||
CValidationState &state = args.m_state;
|
||||
|
||||
constexpr unsigned int scriptVerifyFlags = STANDARD_SCRIPT_VERIFY_FLAGS;
|
||||
|
||||
// Check against previous transactions
|
||||
// This is done last to help prevent CPU exhaustion denial-of-service attacks.
|
||||
if (!CheckInputs(tx, state, m_view, scriptVerifyFlags, true, false, txdata)) {
|
||||
assert(IsTransactionReason(state.GetReason()));
|
||||
return false; // state filled in by CheckInputs
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MemPoolAccept::ConsensusScriptChecks(ATMPArgs& args, Workspace& ws, PrecomputedTransactionData& txdata)
|
||||
{
|
||||
const CTransaction& tx = *ws.m_ptx;
|
||||
const uint256& hash = ws.m_hash;
|
||||
|
||||
CValidationState &state = args.m_state;
|
||||
const CChainParams& chainparams = args.m_chainparams;
|
||||
|
||||
// Check again against the current block tip's script verification
|
||||
// flags to cache our script execution flags. This is, of course,
|
||||
// useless if the next block has different script flags from the
|
||||
// previous one, but because the cache tracks script flags for us it
|
||||
// will auto-invalidate and we'll just have a few blocks of extra
|
||||
// misses on soft-fork activation.
|
||||
//
|
||||
// This is also useful 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 (using TestBlockValidity), however allowing such
|
||||
// transactions into the mempool can be exploited as a DoS attack.
|
||||
unsigned int currentBlockScriptVerifyFlags = GetBlockScriptFlags(::ChainActive().Tip(), chainparams.GetConsensus());
|
||||
if (!CheckInputsFromMempoolAndCache(tx, state, m_view, m_pool, currentBlockScriptVerifyFlags, txdata)) {
|
||||
return error("%s: BUG! PLEASE REPORT THIS! CheckInputs failed against latest-block but not STANDARD flags %s, %s",
|
||||
__func__, hash.ToString(), FormatStateMessage(state));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
bool MemPoolAccept::Finalize(ATMPArgs& args, Workspace& ws)
|
||||
{
|
||||
const CTransaction& tx = *ws.m_ptx;
|
||||
const uint256& hash = ws.m_hash;
|
||||
CValidationState &state = args.m_state;
|
||||
const bool bypass_limits = args.m_bypass_limits;
|
||||
|
||||
CTxMemPool::setEntries& setAncestors = ws.m_ancestors;
|
||||
const CAmount& nModifiedFees = ws.m_modified_fees;
|
||||
std::unique_ptr<CTxMemPoolEntry>& entry = ws.m_entry;
|
||||
|
||||
// This transaction should only count for fee estimation if:
|
||||
// - it's not being re-added during a reorg which bypasses typical mempool fee limits
|
||||
// - the node is not behind
|
||||
// - the transaction is not dependent on any other transactions in the mempool
|
||||
// - the transaction is not a zero fee transaction
|
||||
bool validForFeeEstimation = (nModifiedFees != 0) && !bypass_limits && IsCurrentForFeeEstimation() && m_pool.HasNoInputsOf(tx);
|
||||
|
||||
// Store transaction in memory
|
||||
m_pool.addUnchecked(*entry, setAncestors, validForFeeEstimation);
|
||||
CAmount nValueOut = tx.GetValueOut();
|
||||
unsigned int nSigOps = GetTransactionSigOpCount(tx, m_view, STANDARD_SCRIPT_VERIFY_FLAGS);
|
||||
|
||||
statsClient.count("transactions.sizeBytes", entry->GetTxSize(), 1.0f);
|
||||
statsClient.count("transactions.fees", nModifiedFees, 1.0f);
|
||||
statsClient.count("transactions.inputValue", nValueOut - nModifiedFees, 1.0f);
|
||||
statsClient.count("transactions.outputValue", nValueOut, 1.0f);
|
||||
statsClient.count("transactions.sigOps", nSigOps, 1.0f);
|
||||
|
||||
// Add memory address index
|
||||
if (fAddressIndex) {
|
||||
m_pool.addAddressIndex(*entry, m_view);
|
||||
}
|
||||
|
||||
// Add memory spent index
|
||||
if (fSpentIndex) {
|
||||
m_pool.addSpentIndex(*entry, m_view);
|
||||
}
|
||||
|
||||
if (!bypass_limits) {
|
||||
LimitMempoolSize(m_pool, gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000, std::chrono::hours{gArgs.GetArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY)});
|
||||
if (!m_pool.exists(hash))
|
||||
return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_INSUFFICIENTFEE, "mempool full");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MemPoolAccept::AcceptSingleTransaction(const CTransactionRef& ptx, ATMPArgs& args)
|
||||
{
|
||||
boost::posix_time::ptime start = boost::posix_time::microsec_clock::local_time();
|
||||
AssertLockHeld(cs_main);
|
||||
LOCK(m_pool.cs); // mempool "read lock" (held through GetMainSignals().TransactionAddedToMempool())
|
||||
|
||||
Workspace workspace(ptx);
|
||||
|
||||
if (!PreChecks(args, workspace)) return false;
|
||||
|
||||
// Only compute the precomputed transaction data if we need to verify
|
||||
// scripts (ie, other policy checks pass). We perform the inexpensive
|
||||
// checks first and avoid hashing and signature verification unless those
|
||||
// checks pass, to mitigate CPU exhaustion denial-of-service attacks.
|
||||
PrecomputedTransactionData txdata(*ptx);
|
||||
|
||||
if (!PolicyScriptChecks(args, workspace, txdata)) return false;
|
||||
|
||||
if (!ConsensusScriptChecks(args, workspace, txdata)) return false;
|
||||
|
||||
// Tx was accepted, but not added
|
||||
if (args.m_test_accept) return true;
|
||||
|
||||
if (!Finalize(args, workspace)) return false;
|
||||
|
||||
const int64_t nAcceptTime = args.m_accept_time;
|
||||
GetMainSignals().TransactionAddedToMempool(ptx, nAcceptTime);
|
||||
|
||||
const CTransaction& tx = *ptx;
|
||||
boost::posix_time::ptime finish = boost::posix_time::microsec_clock::local_time();
|
||||
boost::posix_time::time_duration diff = finish - start;
|
||||
statsClient.timing("AcceptToMemoryPool_ms", diff.total_milliseconds(), 1.0f);
|
||||
@ -807,19 +952,23 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
|
||||
return true;
|
||||
}
|
||||
|
||||
} // anon namespace
|
||||
|
||||
/** (try to) add transaction to memory pool with a specified acceptance time **/
|
||||
static bool AcceptToMemoryPoolWithTime(const CChainParams& chainparams, CTxMemPool& pool, CValidationState &state, const CTransactionRef &tx,
|
||||
bool* pfMissingInputs, int64_t nAcceptTime, bool bypass_limits,
|
||||
const CAmount nAbsurdFee, bool test_accept) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
|
||||
{
|
||||
std::vector<COutPoint> coins_to_uncache;
|
||||
bool res = AcceptToMemoryPoolWorker(chainparams, pool, state, tx, pfMissingInputs, nAcceptTime, bypass_limits, nAbsurdFee, coins_to_uncache, test_accept);
|
||||
MemPoolAccept::ATMPArgs args { chainparams, state, pfMissingInputs, nAcceptTime, bypass_limits, nAbsurdFee, coins_to_uncache, test_accept };
|
||||
bool res = MemPoolAccept(pool).AcceptSingleTransaction(tx, args);
|
||||
if (!res || test_accept) {
|
||||
if(!res) LogPrint(BCLog::MEMPOOL, "%s: %s %s (%s)\n", __func__, tx->GetHash().ToString(), state.GetRejectReason(), state.GetDebugMessage());
|
||||
// Remove coins that were not present in the coins cache before calling ATMPW;
|
||||
// this is to prevent memory DoS in case we receive a large number of
|
||||
// invalid transactions that attempt to overrun the in-memory coins cache
|
||||
// (`CCoinsViewCache::cacheCoins`).
|
||||
if (!res) LogPrint(BCLog::MEMPOOL, "%s: %s %s (%s)\n", __func__, tx->GetHash().ToString(), state.GetRejectReason(), state.GetDebugMessage());
|
||||
|
||||
// Remove coins that were not present in the coins cache before calling ATMPW;
|
||||
// this is to prevent memory DoS in case we receive a large number of
|
||||
// invalid transactions that attempt to overrun the in-memory coins cache
|
||||
// (`CCoinsViewCache::cacheCoins`).
|
||||
|
||||
for (const COutPoint& hashTx : coins_to_uncache)
|
||||
::ChainstateActive().CoinsTip().Uncache(hashTx);
|
||||
|
Loading…
Reference in New Issue
Block a user