Auto merge of #2342 - bitcartel:1081__mempoolpatch, r=str4d

Add ability for node to reject tx from mempool by number of tx inputs

Implement short-term solution described in #2343 so that users can respond promptly to critical short-term problems caused by quadratic validation scaling, such as the getblocktemplate latency, block propagation latency, and mempool size inflation issues described in #2333.
This commit is contained in:
Homu
2017-06-21 14:42:13 -07:00
7 changed files with 245 additions and 1 deletions

View File

@@ -1,10 +1,13 @@
#include <gtest/gtest.h>
#include <gtest/gtest-spi.h>
#include "consensus/validation.h"
#include "core_io.h"
#include "main.h"
#include "primitives/transaction.h"
#include "txmempool.h"
#include "policy/fees.h"
#include "util.h"
// Fake the input of transaction 5295156213414ed77f6e538e7e8ebe14492156906b9fe995b242477818789364
// - 532639cc6bebed47c1c69ae36dd498c68a012e74ad12729adbd3dbb56f8f3f4a, 0
@@ -87,3 +90,49 @@ TEST(Mempool, PriorityStatsDoNotCrash) {
EXPECT_EQ(dPriority, MAX_PRIORITY);
}
TEST(Mempool, TxInputLimit) {
CTxMemPool pool(::minRelayTxFee);
bool missingInputs;
// Create an obviously-invalid transaction
// We intentionally set tx.nVersion = 0 to reliably trigger an error, as
// it's the first check that occurs after the -mempooltxinputlimit check,
// and it means that we don't have to mock out a lot of global state.
CMutableTransaction mtx;
mtx.nVersion = 0;
mtx.vin.resize(10);
// Check it fails as expected
CValidationState state1;
CTransaction tx1(mtx);
EXPECT_FALSE(AcceptToMemoryPool(pool, state1, tx1, false, &missingInputs));
EXPECT_EQ(state1.GetRejectReason(), "bad-txns-version-too-low");
// Set a limit
mapArgs["-mempooltxinputlimit"] = "10";
// Check it stil fails as expected
CValidationState state2;
EXPECT_FALSE(AcceptToMemoryPool(pool, state2, tx1, false, &missingInputs));
EXPECT_EQ(state2.GetRejectReason(), "bad-txns-version-too-low");
// Resize the transaction
mtx.vin.resize(11);
// Check it now fails due to exceeding the limit
CValidationState state3;
CTransaction tx3(mtx);
EXPECT_FALSE(AcceptToMemoryPool(pool, state3, tx3, false, &missingInputs));
// The -mempooltxinputlimit check doesn't set a reason
EXPECT_EQ(state3.GetRejectReason(), "");
// Clear the limit
mapArgs.erase("-mempooltxinputlimit");
// Check it no longer fails due to exceeding the limit
CValidationState state4;
EXPECT_FALSE(AcceptToMemoryPool(pool, state4, tx3, false, &missingInputs));
EXPECT_EQ(state4.GetRejectReason(), "bad-txns-version-too-low");
}

View File

@@ -352,6 +352,7 @@ std::string HelpMessage(HelpMessageMode mode)
strUsage += HelpMessageOpt("-dbcache=<n>", strprintf(_("Set database cache size in megabytes (%d to %d, default: %d)"), nMinDbCache, nMaxDbCache, nDefaultDbCache));
strUsage += HelpMessageOpt("-loadblock=<file>", _("Imports blocks from external blk000??.dat file") + " " + _("on startup"));
strUsage += HelpMessageOpt("-maxorphantx=<n>", strprintf(_("Keep at most <n> unconnectable transactions in memory (default: %u)"), DEFAULT_MAX_ORPHAN_TRANSACTIONS));
strUsage += HelpMessageOpt("-mempooltxinputlimit=<n>", _("Set the maximum number of transparent inputs in a transaction that the mempool will accept (default: 0 = no limit applied)"));
strUsage += HelpMessageOpt("-par=<n>", strprintf(_("Set the number of script verification threads (%u to %d, 0 = auto, <0 = leave that many cores free, default: %d)"),
-GetNumCores(), MAX_SCRIPTCHECK_THREADS, DEFAULT_SCRIPTCHECK_THREADS));
#ifndef WIN32
@@ -1036,6 +1037,17 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
}
#endif
// Default value of 0 for mempooltxinputlimit means no limit is applied
if (mapArgs.count("-mempooltxinputlimit")) {
int64_t limit = GetArg("-mempooltxinputlimit", 0);
if (limit < 0) {
return InitError(_("Mempool limit on transparent inputs to a transaction cannot be negative"));
} else if (limit > 0) {
LogPrintf("Mempool configured to reject transactions with greater than %lld transparent inputs\n", limit);
}
}
// ********************************************************* Step 4: application initialization: dir lock, daemonize, pidfile, debug log
// Initialize libsodium

View File

@@ -1058,6 +1058,17 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
if (pfMissingInputs)
*pfMissingInputs = false;
// Node operator can choose to reject tx by number of transparent inputs
static_assert(std::numeric_limits<size_t>::max() >= std::numeric_limits<int64_t>::max(), "size_t too small");
size_t limit = (size_t) GetArg("-mempooltxinputlimit", 0);
if (limit > 0) {
size_t n = tx.vin.size();
if (n > limit) {
LogPrint("mempool", "Dropping txid %s : too many transparent inputs %zu > limit %zu\n", tx.GetHash().ToString(), n, limit );
return false;
}
}
auto verifier = libzcash::ProofVerifier::Strict();
if (!CheckTransaction(tx, state, verifier))
return error("AcceptToMemoryPool: CheckTransaction failed");

View File

@@ -203,7 +203,7 @@ bool AsyncRPCOperation_sendmany::main_impl() {
if (isfromzaddr_ && !find_unspent_notes()) {
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds, no unspent notes found for zaddr from address.");
}
CAmount t_inputs_total = 0;
for (SendManyInputUTXO & t : t_inputs_) {
t_inputs_total += std::get<2>(t);
@@ -281,6 +281,15 @@ bool AsyncRPCOperation_sendmany::main_impl() {
t_inputs_ = selectedTInputs;
t_inputs_total = selectedUTXOAmount;
// Check mempooltxinputlimit to avoid creating a transaction which the local mempool rejects
size_t limit = (size_t)GetArg("-mempooltxinputlimit", 0);
if (limit > 0) {
size_t n = t_inputs_.size();
if (n > limit) {
throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Too many transparent inputs %zu > limit %zu", n, limit));
}
}
// update the transaction with these inputs
CMutableTransaction rawTx(tx_);
for (SendManyInputUTXO & t : t_inputs_) {

View File

@@ -2717,6 +2717,16 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
txNew.vin.push_back(CTxIn(coin.first->GetHash(),coin.second,CScript(),
std::numeric_limits<unsigned int>::max()-1));
// Check mempooltxinputlimit to avoid creating a transaction which the local mempool rejects
size_t limit = (size_t)GetArg("-mempooltxinputlimit", 0);
if (limit > 0) {
size_t n = txNew.vin.size();
if (n > limit) {
strFailReason = _(strprintf("Too many transparent inputs %zu > limit %zu", n, limit).c_str());
return false;
}
}
// Sign
int nIn = 0;
CTransaction txNewConst(txNew);