Skip to content

Commit

Permalink
Merge bitcoin#11742: rpc: Add testmempoolaccept
Browse files Browse the repository at this point in the history
b55555d rpc: Add testmempoolaccept (MarcoFalke)

Pull request description:

  To check if a single raw transaction makes it into the current transaction pool, one had to call `sendrawtransaction`. However, on success, this adds the transaction to the mempool with no easy way to undo.

  The call `testmempoolaccept` is introduced to provide a way to solely check the result without changing the mempool state.

Tree-SHA512: 5afd9311190135cee8fc1f229c7d39bf893f1028f29e28d34f70df820198ff97b4bf86b41cbbd6e6c36a5c30073cefa92d541c74a4939c7a2a6fa283dfd41b63
  • Loading branch information
laanwj authored and UdjinM6 committed May 21, 2021
1 parent b76e7fe commit 34b30bc
Show file tree
Hide file tree
Showing 9 changed files with 399 additions and 18 deletions.
2 changes: 2 additions & 0 deletions src/rpc/client.cpp
Expand Up @@ -126,6 +126,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "sendrawtransaction", 1, "allowhighfees" },
{ "sendrawtransaction", 2, "instantsend" },
{ "sendrawtransaction", 3, "bypasslimits" },
{ "testmempoolaccept", 0, "rawtxs" },
{ "testmempoolaccept", 1, "allowhighfees" },
{ "combinerawtransaction", 0, "txs" },
{ "fundrawtransaction", 1, "options" },
{ "gettxout", 1, "n" },
Expand Down
80 changes: 80 additions & 0 deletions src/rpc/rawtransaction.cpp
Expand Up @@ -1160,6 +1160,85 @@ UniValue sendrawtransaction(const JSONRPCRequest& request)
return hashTx.GetHex();
}

UniValue testmempoolaccept(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) {
throw std::runtime_error(
// clang-format off
"testmempoolaccept [\"rawtxs\"] ( allowhighfees )\n"
"\nReturns if raw transaction (serialized, hex-encoded) would be accepted by mempool.\n"
"\nThis checks if the transaction violates the consensus or policy rules.\n"
"\nSee sendrawtransaction call.\n"
"\nArguments:\n"
"1. [\"rawtxs\"] (array, required) An array of hex strings of raw transactions.\n"
" Length must be one for now.\n"
"2. allowhighfees (boolean, optional, default=false) Allow high fees\n"
"\nResult:\n"
"[ (array) The result of the mempool acceptance test for each raw transaction in the input array.\n"
" Length is exactly one for now.\n"
" {\n"
" \"txid\" (string) The transaction hash in hex\n"
" \"allowed\" (boolean) If the mempool allows this tx to be inserted\n"
" \"reject-reason\" (string) Rejection string (only present when 'allowed' is false)\n"
" }\n"
"]\n"
"\nExamples:\n"
"\nCreate a transaction\n"
+ HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\" : \\\"mytxid\\\",\\\"vout\\\":0}]\" \"{\\\"myaddress\\\":0.01}\"") +
"Sign the transaction, and get back the hex\n"
+ HelpExampleCli("signrawtransaction", "\"myhex\"") +
"\nTest acceptance of the transaction (signed hex)\n"
+ HelpExampleCli("testmempoolaccept", "\"signedhex\"") +
"\nAs a json rpc call\n"
+ HelpExampleRpc("testmempoolaccept", "[\"signedhex\"]")
// clang-format on
);
}

RPCTypeCheck(request.params, {UniValue::VARR, UniValue::VBOOL});
if (request.params[0].get_array().size() != 1) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Array must contain exactly one raw transaction for now");
}

CMutableTransaction mtx;
if (!DecodeHexTx(mtx, request.params[0].get_array()[0].get_str())) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
}
CTransactionRef tx(MakeTransactionRef(std::move(mtx)));
const uint256& tx_hash = tx->GetHash();

CAmount max_raw_tx_fee = ::maxTxFee;
if (!request.params[1].isNull() && request.params[1].get_bool()) {
max_raw_tx_fee = 0;
}

UniValue result(UniValue::VARR);
UniValue result_0(UniValue::VOBJ);
result_0.pushKV("txid", tx_hash.GetHex());

CValidationState state;
bool missing_inputs;
bool test_accept_res;
{
LOCK(cs_main);
test_accept_res = AcceptToMemoryPool(mempool, state, std::move(tx), &missing_inputs,
false /* bypass_limits */, max_raw_tx_fee, /* test_accpet */ true);
}
result_0.pushKV("allowed", test_accept_res);
if (!test_accept_res) {
if (state.IsInvalid()) {
result_0.pushKV("reject-reason", strprintf("%i: %s", state.GetRejectCode(), state.GetRejectReason()));
} else if (missing_inputs) {
result_0.pushKV("reject-reason", "missing-inputs");
} else {
result_0.pushKV("reject-reason", state.GetRejectReason());
}
}

result.push_back(std::move(result_0));
return result;
}

static const CRPCCommand commands[] =
{ // category name actor (function) argNames
// --------------------- ------------------------ ----------------------- ----------
Expand All @@ -1171,6 +1250,7 @@ static const CRPCCommand commands[] =
{ "rawtransactions", "combinerawtransaction", &combinerawtransaction, {"txs"} },
{ "rawtransactions", "signrawtransaction", &signrawtransaction, {"hexstring","prevtxs","privkeys","sighashtype"} }, /* uses wallet if enabled */
{ "rawtransactions", "signrawtransactionwithkey", &signrawtransactionwithkey, {"hexstring","privkeys","prevtxs","sighashtype"} },
{ "rawtransactions", "testmempoolaccept", &testmempoolaccept, {"rawtxs","allowhighfees"} },

{ "blockchain", "gettxoutproof", &gettxoutproof, {"txids", "blockhash"} },
{ "blockchain", "verifytxoutproof", &verifytxoutproof, {"proof"} },
Expand Down
2 changes: 1 addition & 1 deletion src/txmempool.cpp
Expand Up @@ -699,7 +699,7 @@ void CTxMemPool::removeUnchecked(txiter it, MemPoolRemovalReason reason)
// Also assumes that if an entry is in setDescendants already, then all
// in-mempool descendants of it are already in setDescendants as well, so that we
// can save time by not iterating over those entries.
void CTxMemPool::CalculateDescendants(txiter entryit, setEntries &setDescendants)
void CTxMemPool::CalculateDescendants(txiter entryit, setEntries& setDescendants) const
{
setEntries stage;
if (setDescendants.count(entryit) == 0) {
Expand Down
2 changes: 1 addition & 1 deletion src/txmempool.h
Expand Up @@ -640,7 +640,7 @@ class CTxMemPool
/** Populate setDescendants with all in-mempool descendants of hash.
* Assumes that setDescendants includes all in-mempool descendants of anything
* already in it. */
void CalculateDescendants(txiter it, setEntries &setDescendants) EXCLUSIVE_LOCKS_REQUIRED(cs);
void CalculateDescendants(txiter it, setEntries& setDescendants) const EXCLUSIVE_LOCKS_REQUIRED(cs);;

/** The minimum fee to get into the mempool, which may itself not be enough
* for larger-sized transactions.
Expand Down
25 changes: 13 additions & 12 deletions src/validation.cpp
Expand Up @@ -627,7 +627,7 @@ static bool CheckInputsFromMempoolAndCache(const CTransaction& tx, CValidationSt

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 fDryRun)
const CAmount& nAbsurdFee, std::vector<COutPoint>& coins_to_uncache, bool test_accept)
{
boost::posix_time::ptime start = boost::posix_time::microsec_clock::local_time();
const CTransaction& tx = *ptx;
Expand Down Expand Up @@ -814,9 +814,6 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
return state.DoS(0, false, REJECT_DUPLICATE, "protx-dup");
}

// If we aren't going to actually accept it but just were verifying it, we are fine already
if(fDryRun) return true;

constexpr unsigned int scriptVerifyFlags = STANDARD_SCRIPT_VERIFY_FLAGS;

// Check against previous transactions
Expand Down Expand Up @@ -846,6 +843,11 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
__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
Expand Down Expand Up @@ -879,8 +881,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
}
}

if(!fDryRun)
GetMainSignals().TransactionAddedToMempool(ptx, nAcceptTime);
GetMainSignals().TransactionAddedToMempool(ptx, nAcceptTime);

boost::posix_time::ptime finish = boost::posix_time::microsec_clock::local_time();
boost::posix_time::time_duration diff = finish - start;
Expand All @@ -895,11 +896,11 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
/** (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 fDryRun)
const CAmount nAbsurdFee, bool test_accept)
{
std::vector<COutPoint> coins_to_uncache;
bool res = AcceptToMemoryPoolWorker(chainparams, pool, state, tx, pfMissingInputs, nAcceptTime, bypass_limits, nAbsurdFee, coins_to_uncache, fDryRun);
if (!res || fDryRun) {
bool res = AcceptToMemoryPoolWorker(chainparams, pool, state, tx, pfMissingInputs, nAcceptTime, bypass_limits, nAbsurdFee, coins_to_uncache, test_accept);
if (!res || test_accept) {
if(!res) LogPrint(BCLog::MEMPOOL, "%s: %s %s (%s)\n", __func__, tx->GetHash().ToString(), state.GetRejectReason(), state.GetDebugMessage());
for (const COutPoint& hashTx : coins_to_uncache)
pcoinsTip->Uncache(hashTx);
Expand All @@ -911,10 +912,10 @@ static bool AcceptToMemoryPoolWithTime(const CChainParams& chainparams, CTxMemPo
}

bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransactionRef &tx,
bool* pfMissingInputs, bool bypass_limits, const CAmount nAbsurdFee, bool fDryRun)
bool* pfMissingInputs, bool bypass_limits, const CAmount nAbsurdFee, bool test_accept)
{
const CChainParams& chainparams = Params();
return AcceptToMemoryPoolWithTime(chainparams, pool, state, tx, pfMissingInputs, GetTime(), bypass_limits, nAbsurdFee, fDryRun);
return AcceptToMemoryPoolWithTime(chainparams, pool, state, tx, pfMissingInputs, GetTime(), bypass_limits, nAbsurdFee, test_accept);
}

bool GetTimestampIndex(const unsigned int &high, const unsigned int &low, std::vector<uint256> &hashes)
Expand Down Expand Up @@ -5205,7 +5206,7 @@ bool LoadMempool(void)
if (nTime + nExpiryTimeout > nNow) {
LOCK(cs_main);
AcceptToMemoryPoolWithTime(chainparams, mempool, state, tx, nullptr /* pfMissingInputs */, nTime,
false /* bypass_limits */, 0 /* nAbsurdFee */);
false /* bypass_limits */, 0 /* nAbsurdFee */, false /* test_accept */);
if (state.IsValid()) {
++count;
} else {
Expand Down
4 changes: 2 additions & 2 deletions src/validation.h
Expand Up @@ -319,10 +319,10 @@ void PruneBlockFilesManual(int nManualPruneHeight);
/** (try to) add transaction to memory pool */
bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransactionRef &tx,
bool* pfMissingInputs, bool bypass_limits,
const CAmount nAbsurdFee, bool fDryRun=false);
const CAmount nAbsurdFee, bool test_accept=false);
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 fDryRun = false);
const CAmount nAbsurdFee, bool test_accept = false);

bool GetUTXOCoin(const COutPoint& outpoint, Coin& coin);
int GetUTXOHeight(const COutPoint& outpoint);
Expand Down

0 comments on commit 34b30bc

Please sign in to comment.