Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions doc/release-notes-5834.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Updated RPCs
------------

- The `testmempoolaccept` RPC returns `vsize` and a `fee` object with the `base` fee
if the transaction passes validation.
- The `testmempoolaccept` RPC now accepts multiple transactions (still experimental at the moment,
API may be unstable). This is intended for testing transaction packages with dependency
relationships; it is not recommended for batch-validating independent transactions. In addition to
mempool policy, package policies apply: the list cannot contain more than 25 transactions or have a
total size exceeding 101K virtual bytes, and cannot conflict with (spend the same inputs as) each other or
the mempool. There are some known limitations to the accuracy of the test accept: it's possible for
`testmempoolaccept` to return "allowed"=True for a group of transactions, but "too-long-mempool-chain"
if they are actually submitted.
2 changes: 2 additions & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ BITCOIN_CORE_H = \
outputtype.h \
policy/feerate.h \
policy/fees.h \
policy/packages.h \
policy/policy.h \
policy/settings.h \
pow.h \
Expand Down Expand Up @@ -483,6 +484,7 @@ libbitcoin_server_a_SOURCES = \
node/ui_interface.cpp \
noui.cpp \
policy/fees.cpp \
policy/packages.cpp \
policy/policy.cpp \
policy/settings.cpp \
pow.cpp \
Expand Down
5 changes: 2 additions & 3 deletions src/bench/block_assemble.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,8 @@ static void AssembleBlock(benchmark::Bench& bench)
LOCK(::cs_main); // Required for ::AcceptToMemoryPool.

for (const auto& txr : txs) {
TxValidationState state;
bool ret{::AcceptToMemoryPool(test_setup.m_node.chainman->ActiveChainstate(), *test_setup.m_node.mempool, state, txr, false /* bypass_limits */, /* nAbsurdFee */ 0)};
assert(ret);
const MempoolAcceptResult res = ::AcceptToMemoryPool(test_setup.m_node.chainman->ActiveChainstate(), *test_setup.m_node.mempool, txr, false /* bypass_limits */);
assert(res.m_result_type == MempoolAcceptResult::ResultType::VALID);
}
}

Expand Down
22 changes: 20 additions & 2 deletions src/coinjoin/coinjoin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,25 @@ bool CCoinJoinBaseSession::IsValidInOuts(const CTxMemPool& mempool, const std::v
return true;
}

// Responsibility for checking fee sanity is moved from the mempool to the client (BroadcastTransaction)
// but CoinJoin still requires ATMP with fee sanity checks so we need to implement them separately
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why we can't drop it?

Copy link
Copy Markdown
Collaborator Author

@kwvg kwvg Feb 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CoinJoin code expects that ATMP will check fee sanity because unlike every other non-RPC/non-GUI AcceptToMemoryPool call which uses an nAbsurdFee value of 0 (to skip absurd fee checking), CoinJoin code in a few places uses DEFAULT_MAX_RAW_TX_FEE (not to be confused with DEFAULT_MAX_RAW_TX_FEE_RATE, which has the same value, except as CFeeRate as opposed to CAmount) as nAbsurdFee, triggering the fee check.

Perhaps it could be refactored to be removed entirely but that's outside the scope of the PR and the backport attempts to preserve functionality as-is on the CoinJoin front.

bool ATMPIfSaneFee(CChainState& active_chainstate, CTxMemPool& pool, const CTransactionRef &tx, bool test_accept) {
AssertLockHeld(cs_main);

const MempoolAcceptResult result = AcceptToMemoryPool(active_chainstate, pool, tx, /* bypass_limits */ false, /* test_accept */ true);
if (result.m_result_type != MempoolAcceptResult::ResultType::VALID) {
/* Fetch fee and fast-fail if ATMP fails regardless */
return false;
} else if (result.m_base_fees.value() > DEFAULT_MAX_RAW_TX_FEE) {
/* Check fee against fixed upper limit */
return false;
} else if (test_accept) {
/* Don't re-run ATMP if only doing test run */
return true;
}
return AcceptToMemoryPool(active_chainstate, pool, tx, /* bypass_limits */ false, test_accept).m_result_type == MempoolAcceptResult::ResultType::VALID;
}

// check to make sure the collateral provided by the client is valid
bool CoinJoin::IsCollateralValid(CTxMemPool& mempool, const CTransaction& txCollateral)
{
Expand Down Expand Up @@ -351,8 +370,7 @@ bool CoinJoin::IsCollateralValid(CTxMemPool& mempool, const CTransaction& txColl

{
LOCK(cs_main);
TxValidationState validationState;
if (!AcceptToMemoryPool(::ChainstateActive(), mempool, validationState, MakeTransactionRef(txCollateral), /*bypass_limits=*/false, /*nAbsurdFee=*/DEFAULT_MAX_RAW_TX_FEE, /*test_accept=*/true)) {
if (!ATMPIfSaneFee(::ChainstateActive(), mempool, MakeTransactionRef(txCollateral), /*test_accept=*/true)) {
LogPrint(BCLog::COINJOIN, "CoinJoin::IsCollateralValid -- didn't pass AcceptToMemoryPool()\n");
return false;
}
Expand Down
6 changes: 6 additions & 0 deletions src/coinjoin/coinjoin.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,20 @@
#include <optional>
#include <utility>

class CChainState;
class CConnman;
class CBLSPublicKey;
class CBlockIndex;
class CMasternodeSync;
class CTxMemPool;
class TxValidationState;

namespace llmq {
class CChainLocksHandler;
} // namespace llmq

extern RecursiveMutex cs_main;

// timeouts
static constexpr int COINJOIN_AUTO_TIMEOUT_MIN = 5;
static constexpr int COINJOIN_AUTO_TIMEOUT_MAX = 15;
Expand Down Expand Up @@ -377,6 +381,8 @@ class CDSTXManager

};

bool ATMPIfSaneFee(CChainState& active_chainstate, CTxMemPool& pool,
const CTransactionRef &tx, bool test_accept = false) EXCLUSIVE_LOCKS_REQUIRED(cs_main);

extern std::unique_ptr<CDSTXManager> dstxManager;

Expand Down
8 changes: 2 additions & 6 deletions src/coinjoin/server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@

#include <univalue.h>

constexpr static CAmount DEFAULT_MAX_RAW_TX_FEE{COIN / 10};

PeerMsgRet CCoinJoinServer::ProcessMessage(CNode& peer, std::string_view msg_type, CDataStream& vRecv)
{
if (!fMasternodeMode) return {};
Expand Down Expand Up @@ -319,9 +317,8 @@ void CCoinJoinServer::CommitFinalTransaction()
{
// See if the transaction is valid
TRY_LOCK(cs_main, lockMain);
TxValidationState validationState;
mempool.PrioritiseTransaction(hashTx, 0.1 * COIN);
if (!lockMain || !AcceptToMemoryPool(m_chainstate, mempool, validationState, finalTransaction, false /* bypass_limits */, DEFAULT_MAX_RAW_TX_FEE /* nAbsurdFee */)) {
if (!lockMain || !ATMPIfSaneFee(m_chainstate, mempool, finalTransaction)) {
LogPrint(BCLog::COINJOIN, "CCoinJoinServer::CommitFinalTransaction -- AcceptToMemoryPool() error: Transaction not valid\n");
WITH_LOCK(cs_coinjoin, SetNull());
// not much we can do in this case, just notify clients
Expand Down Expand Up @@ -453,8 +450,7 @@ void CCoinJoinServer::ChargeRandomFees() const
void CCoinJoinServer::ConsumeCollateral(const CTransactionRef& txref) const
{
LOCK(cs_main);
TxValidationState validationState;
if (!AcceptToMemoryPool(m_chainstate, mempool, validationState, txref, false /* bypass_limits */, 0 /* nAbsurdFee */)) {
if (!ATMPIfSaneFee(m_chainstate, mempool, txref, false /* bypass_limits */)) {
LogPrint(BCLog::COINJOIN, "%s -- AcceptToMemoryPool failed\n", __func__);
} else {
connman.RelayTransaction(*txref);
Expand Down
6 changes: 3 additions & 3 deletions src/llmq/ehf_signals.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -131,11 +131,11 @@ void CEHFSignalsHandler::HandleNewRecoveredSig(const CRecoveredSig& recoveredSig
CTransactionRef tx_to_sent = MakeTransactionRef(std::move(tx));
LogPrintf("CEHFSignalsHandler::HandleNewRecoveredSig Special EHF TX is created hash=%s\n", tx_to_sent->GetHash().ToString());
LOCK(cs_main);
TxValidationState state;
if (AcceptToMemoryPool(chainstate, mempool, state, tx_to_sent, /* bypass_limits=*/ false, /* nAbsurdFee=*/ 0)) {
const MempoolAcceptResult result = AcceptToMemoryPool(chainstate, mempool, tx_to_sent, /* bypass_limits */ false);
if (result.m_result_type == MempoolAcceptResult::ResultType::VALID) {
connman.RelayTransaction(*tx_to_sent);
} else {
LogPrintf("CEHFSignalsHandler::HandleNewRecoveredSig -- AcceptToMemoryPool failed: %s\n", state.ToString());
LogPrintf("CEHFSignalsHandler::HandleNewRecoveredSig -- AcceptToMemoryPool failed: %s\n", result.m_state.ToString());
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/logging.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -235,9 +235,9 @@ std::string BCLog::Logger::LogTimestampStr(const std::string& str)
strStamped.pop_back();
strStamped += strprintf(".%06dZ", nTimeMicros%1000000);
}
int64_t mocktime = GetMockTime();
if (mocktime) {
strStamped += " (mocktime: " + FormatISO8601DateTime(mocktime) + ")";
std::chrono::seconds mocktime = GetMockTime();
if (mocktime > 0s) {
strStamped += " (mocktime: " + FormatISO8601DateTime(count_seconds(mocktime)) + ")";
}
strStamped += ' ' + str;
} else
Expand Down
108 changes: 60 additions & 48 deletions src/net_processing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,11 @@ struct COrphanTx {
size_t list_pos;
size_t nTxSize;
};

/** Guards orphan transactions and extra txs for compact blocks */
RecursiveMutex g_cs_orphans;
/** Map from txid to orphan transaction record. Limited by
* -maxorphantx/DEFAULT_MAX_ORPHAN_TRANSACTIONS */
std::map<uint256, COrphanTx> mapOrphanTransactions GUARDED_BY(g_cs_orphans);

size_t nMapOrphanTransactionsSize = 0;
Expand Down Expand Up @@ -548,12 +552,19 @@ namespace {
return &(*a) < &(*b);
}
};
std::map<COutPoint, std::set<std::map<uint256, COrphanTx>::iterator, IteratorComparator>> mapOrphanTransactionsByPrev GUARDED_BY(g_cs_orphans);

std::vector<std::map<uint256, COrphanTx>::iterator> g_orphan_list GUARDED_BY(g_cs_orphans); //! For random eviction
/** Index from the parents' COutPoint into the mapOrphanTransactions. Used
* to remove orphan transactions from the mapOrphanTransactions */
std::map<COutPoint, std::set<std::map<uint256, COrphanTx>::iterator, IteratorComparator>> mapOrphanTransactionsByPrev GUARDED_BY(g_cs_orphans);
/** Orphan transactions in vector for quick random eviction */
std::vector<std::map<uint256, COrphanTx>::iterator> g_orphan_list GUARDED_BY(g_cs_orphans);

static size_t vExtraTxnForCompactIt GUARDED_BY(g_cs_orphans) = 0;
/** Orphan/conflicted/etc transactions that are kept for compact block reconstruction.
* The last -blockreconstructionextratxn/DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN of
* these are kept in a ring buffer */
static std::vector<std::pair<uint256, CTransactionRef>> vExtraTxnForCompact GUARDED_BY(g_cs_orphans);
/** Offset into vExtraTxnForCompact to insert the next tx */
static size_t vExtraTxnForCompactIt GUARDED_BY(g_cs_orphans) = 0;
} // namespace

namespace {
Expand Down Expand Up @@ -837,13 +848,12 @@ void PeerManagerImpl::MaybeSetPeerAsAnnouncingHeaderAndIDs(NodeId nodeid)
}
}
m_connman.ForNode(nodeid, [this](CNode* pfrom){
AssertLockHeld(cs_main);
LockAssertion lock(::cs_main);
Comment thread
knst marked this conversation as resolved.
Outdated
uint64_t nCMPCTBLOCKVersion = 1;
if (lNodesAnnouncingHeaderAndIDs.size() >= 3) {
// As per BIP152, we only get 3 of our peers to announce
// blocks using compact encodings.
m_connman.ForNode(lNodesAnnouncingHeaderAndIDs.front(), [this, nCMPCTBLOCKVersion](CNode* pnodeStop){
AssertLockHeld(cs_main);
m_connman.PushMessage(pnodeStop, CNetMsgMaker(pnodeStop->GetCommonVersion()).Make(NetMsgType::SENDCMPCT, /*fAnnounceUsingCMPCTBLOCK=*/false, nCMPCTBLOCKVersion));
return true;
});
Expand Down Expand Up @@ -1716,7 +1726,7 @@ void PeerManagerImpl::NewPoWValidBlock(const CBlockIndex *pindex, const std::sha
}

m_connman.ForEachNode([this, &pcmpctblock, pindex, &msgMaker, &hashBlock](CNode* pnode) {
AssertLockHeld(cs_main);
LockAssertion lock(::cs_main);
// TODO: Avoid the repeated-serialization here
if (pnode->fDisconnect)
return;
Expand Down Expand Up @@ -2533,33 +2543,34 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, const std::vector<CBlo
return;
}

/**
* Reconsider orphan transactions after a parent has been accepted to the mempool.
*
* @param[in/out] orphan_work_set The set of orphan transactions to reconsider. Generally only one
* orphan will be reconsidered on each call of this function. This set
* may be added to if accepting an orphan causes its children to be
* reconsidered.
*/
void PeerManagerImpl::ProcessOrphanTx(std::set<uint256>& orphan_work_set)
{
AssertLockHeld(cs_main);
AssertLockHeld(g_cs_orphans);
std::set<NodeId> setMisbehaving;
bool done = false;
while (!done && !orphan_work_set.empty()) {

while (!orphan_work_set.empty()) {
const uint256 orphanHash = *orphan_work_set.begin();
orphan_work_set.erase(orphan_work_set.begin());

auto orphan_it = mapOrphanTransactions.find(orphanHash);
if (orphan_it == mapOrphanTransactions.end()) continue;

const CTransactionRef porphanTx = orphan_it->second.tx;
const CTransaction& orphanTx = *porphanTx;
NodeId fromPeer = orphan_it->second.fromPeer;
// Use a new TxValidationState because orphans come from different peers (and we call
// MaybePunishNodeForTx based on the source peer from the orphan map, not based on the peer
// that relayed the previous transaction).
TxValidationState orphan_state;

if (setMisbehaving.count(fromPeer)) continue;
if (AcceptToMemoryPool(m_chainman.ActiveChainstate(), m_mempool, orphan_state, porphanTx,
false /* bypass_limits */, 0 /* nAbsurdFee */)) {
const MempoolAcceptResult result = AcceptToMemoryPool(m_chainman.ActiveChainstate(), m_mempool, porphanTx, false /* bypass_limits */);
const TxValidationState& state = result.m_state;

if (result.m_result_type == MempoolAcceptResult::ResultType::VALID) {
LogPrint(BCLog::MEMPOOL, " accepted orphan tx %s\n", orphanHash.ToString());
RelayTransaction(orphanTx.GetHash());
for (unsigned int i = 0; i < orphanTx.vout.size(); i++) {
RelayTransaction(porphanTx->GetHash());
for (unsigned int i = 0; i < porphanTx->vout.size(); i++) {
auto it_by_prev = mapOrphanTransactionsByPrev.find(COutPoint(orphanHash, i));
if (it_by_prev != mapOrphanTransactionsByPrev.end()) {
for (const auto& elem : it_by_prev->second) {
Expand All @@ -2568,24 +2579,22 @@ void PeerManagerImpl::ProcessOrphanTx(std::set<uint256>& orphan_work_set)
}
}
EraseOrphanTx(orphanHash);
done = true;
} else if (orphan_state.GetResult() != TxValidationResult::TX_MISSING_INPUTS) {
if (orphan_state.IsInvalid()) {
// Punish peer that gave us an invalid orphan tx
if (MaybePunishNodeForTx(fromPeer, orphan_state)) {
setMisbehaving.insert(fromPeer);
}
break;
} else if (state.GetResult() != TxValidationResult::TX_MISSING_INPUTS) {
if (state.IsInvalid()) {
LogPrint(BCLog::MEMPOOL, " invalid orphan tx %s\n", orphanHash.ToString());
// Maybe punish peer that gave us an invalid orphan tx
MaybePunishNodeForTx(orphan_it->second.fromPeer, state);
}
// Has inputs but not accepted to mempool
// Probably non-standard or insufficient fee
LogPrint(BCLog::MEMPOOL, " removed orphan tx %s\n", orphanHash.ToString());
m_recent_rejects.insert(orphanHash);
EraseOrphanTx(orphanHash);
done = true;
break;
}
m_mempool.check(m_chainman.ActiveChainstate());
}
m_mempool.check(m_chainman.ActiveChainstate());
}

bool PeerManagerImpl::PrepareBlockFilterRequest(CNode& peer, const CChainParams& chain_params,
Expand Down Expand Up @@ -3616,10 +3625,26 @@ void PeerManagerImpl::ProcessMessage(

LOCK2(cs_main, g_cs_orphans);

TxValidationState state;
if (AlreadyHave(inv)) {
if (pfrom.HasPermission(PF_FORCERELAY)) {
// Always relay transactions received from peers with forcerelay permission, even
// if they were already in the mempool,
// allowing the node to function as a gateway for
// nodes hidden behind it.
if (!m_mempool.exists(tx.GetHash())) {
LogPrintf("Not relaying non-mempool transaction %s from forcerelay peer=%d\n", tx.GetHash().ToString(), pfrom.GetId());
} else {
LogPrintf("Force relaying tx %s from peer=%d\n", tx.GetHash().ToString(), pfrom.GetId());
RelayTransaction(tx.GetHash());
}
}
return;
}

const MempoolAcceptResult result = AcceptToMemoryPool(m_chainman.ActiveChainstate(), m_mempool, ptx, false /* bypass_limits */);
const TxValidationState& state = result.m_state;

if (!AlreadyHave(inv) && AcceptToMemoryPool(m_chainman.ActiveChainstate(), m_mempool, state, ptx,
false /* bypass_limits */, 0 /* nAbsurdFee */)) {
if (result.m_result_type == MempoolAcceptResult::ResultType::VALID) {
// Process custom txes, this changes AlreadyHave to "true"
if (nInvType == MSG_DSTX) {
LogPrint(BCLog::COINJOIN, "DSTX -- Masternode transaction accepted, txid=%s, peer=%d\n",
Expand Down Expand Up @@ -3690,19 +3715,6 @@ void PeerManagerImpl::ProcessMessage(
if (RecursiveDynamicUsage(*ptx) < 100000) {
AddToCompactExtraTransactions(ptx);
}

if (pfrom.HasPermission(PF_FORCERELAY)) {
// Always relay transactions received from peers with forcerelay permission, even
// if they were already in the mempool,
// allowing the node to function as a gateway for
// nodes hidden behind it.
if (!m_mempool.exists(tx.GetHash())) {
LogPrintf("Not relaying non-mempool transaction %s from forcerelay peer=%d\n", tx.GetHash().ToString(), pfrom.GetId());
} else {
LogPrintf("Force relaying tx %s from peer=%d\n", tx.GetHash().ToString(), pfrom.GetId());
RelayTransaction(tx.GetHash());
}
}
}

// If a tx has been detected by m_recent_rejects, we will have reached
Expand Down Expand Up @@ -4594,7 +4606,7 @@ void PeerManagerImpl::EvictExtraOutboundPeers(int64_t time_in_seconds)
int64_t oldest_block_announcement = std::numeric_limits<int64_t>::max();

m_connman.ForEachNode([&](CNode* pnode) {
AssertLockHeld(cs_main);
LockAssertion lock(::cs_main);

// Don't disconnect masternodes just because they were slow in block announcement
if (pnode->m_masternode_connection) return;
Expand All @@ -4613,7 +4625,7 @@ void PeerManagerImpl::EvictExtraOutboundPeers(int64_t time_in_seconds)
});
if (worst_peer != -1) {
bool disconnected = m_connman.ForNode(worst_peer, [&](CNode *pnode) {
AssertLockHeld(cs_main);
LockAssertion lock(::cs_main);

// Only disconnect a peer that has been connected to us for
// some reasonable fraction of our check-frequency, to give
Expand Down
Loading