Skip to content

Commit fbe48f9

Browse files
committed
Merge bitcoin/bitcoin#27625: p2p: Stop relaying non-mempool txs
faa2976 Remove mapRelay (MarcoFalke) fccecd7 net_processing: relay txs from m_most_recent_block (Anthony Towns) Pull request description: `mapRelay` (used to relay announced transactions that are no longer in the mempool) has issues: * It doesn't have an absolute memory limit, only an implicit one based on the rate of transaction announcements * <strike>It doesn't have a use-case</strike> EDIT: see below Fix all issues by removing `mapRelay`. For more context, on why a transaction may have been removed from the mempool, see https://github.com/bitcoin/bitcoin/blob/c2f2abd0a4f4bd18bfca41b632d21d803479f3f4/src/txmempool.h#L228-L238 For my rationale on why it is fine to not relay them: Reason | | Rationale -- | -- | -- `EXPIRY` | Expired from mempool | Mempool expiry is by default 2 weeks and can not be less than 1 hour, so a transaction can not be in `mapRelay` while expiring, unless a re-broadcast happened. This should be fine, because the transaction will be re-added to the mempool and potentially announced/relayed on the next re-broadcast. `SIZELIMIT` | Removed in size limiting | A low fee transaction, which will be relayed by a different peer after `GETDATA_TX_INTERVAL` or after we sent a `notfound` message. Assuming it ever made it to another peer, otherwise it will happen on re-broadcast (same as with `EXPIRY` above). `REORG` | Removed for reorganization | Block races are rare, so reorgs should be rarer. Also, the transaction is likely to be re-accepted via the `disconnectpool` later on. If not, it seems fine to let the originating wallet deal with rebroadcast in this case. `BLOCK` | Removed for block | EDIT: Needed for compact block relay, see bitcoin/bitcoin#27625 (comment) `CONFLICT` | Removed for conflict with in-block transaction | The peer won't be able to add the tx to the mempool anyway, unless it is on a different block, in which case it seems fine to let the originating wallet take care of the rebroadcast (if needed). `REPLACED` | Removed for replacement | EDIT: Also needed for compact block relay, see bitcoin/bitcoin#27625 (comment) ? ACKs for top commit: sdaftuar: ACK faa2976 ajtowns: ACK faa2976 glozow: code review ACK faa2976 Tree-SHA512: 64ae3e387b001bf6bd5b6c938e7317f4361f9bc0b8cc5d8f63a16cda2408d2f634a22f8157dfcd8957502ef358208292ec91e7d70c9c2d8a8c47cc0114ecfebd
2 parents 5111d8e + faa2976 commit fbe48f9

File tree

2 files changed

+52
-38
lines changed

2 files changed

+52
-38
lines changed

src/net_processing.cpp

Lines changed: 21 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,7 @@
5151
#include <optional>
5252
#include <typeinfo>
5353

54-
/** How long to cache transactions in mapRelay for normal relay */
55-
static constexpr auto RELAY_TX_CACHE_TIME = 15min;
56-
/** How long a transaction has to be in the mempool before it can unconditionally be relayed (even when not in mapRelay). */
54+
/** How long a transaction has to be in the mempool before it can unconditionally be relayed. */
5755
static constexpr auto UNCONDITIONAL_RELAY_DELAY = 2min;
5856
/** Headers download timeout.
5957
* Timeout = base + per_header * (expected number of headers) */
@@ -851,6 +849,7 @@ class PeerManagerImpl final : public PeerManager
851849
std::shared_ptr<const CBlock> m_most_recent_block GUARDED_BY(m_most_recent_block_mutex);
852850
std::shared_ptr<const CBlockHeaderAndShortTxIDs> m_most_recent_compact_block GUARDED_BY(m_most_recent_block_mutex);
853851
uint256 m_most_recent_block_hash GUARDED_BY(m_most_recent_block_mutex);
852+
std::unique_ptr<const std::map<uint256, CTransactionRef>> m_most_recent_block_txs GUARDED_BY(m_most_recent_block_mutex);
854853

855854
// Data about the low-work headers synchronization, aggregated from all peers' HeadersSyncStates.
856855
/** Mutex guarding the other m_headers_presync_* variables. */
@@ -910,7 +909,7 @@ class PeerManagerImpl final : public PeerManager
910909

911910
/** Determine whether or not a peer can request a transaction, and return it (or nullptr if not found or not allowed). */
912911
CTransactionRef FindTxForGetData(const Peer::TxRelay& tx_relay, const GenTxid& gtxid, const std::chrono::seconds mempool_req, const std::chrono::seconds now)
913-
EXCLUSIVE_LOCKS_REQUIRED(NetEventsInterface::g_msgproc_mutex);
912+
EXCLUSIVE_LOCKS_REQUIRED(!m_most_recent_block_mutex, NetEventsInterface::g_msgproc_mutex);
914913

915914
void ProcessGetData(CNode& pfrom, Peer& peer, const std::atomic<bool>& interruptMsgProc)
916915
EXCLUSIVE_LOCKS_REQUIRED(!m_most_recent_block_mutex, peer.m_getdata_requests_mutex, NetEventsInterface::g_msgproc_mutex)
@@ -919,12 +918,6 @@ class PeerManagerImpl final : public PeerManager
919918
/** Process a new block. Perform any post-processing housekeeping */
920919
void ProcessBlock(CNode& node, const std::shared_ptr<const CBlock>& block, bool force_processing, bool min_pow_checked);
921920

922-
/** Relay map (txid or wtxid -> CTransactionRef) */
923-
typedef std::map<uint256, CTransactionRef> MapRelay;
924-
MapRelay mapRelay GUARDED_BY(NetEventsInterface::g_msgproc_mutex);
925-
/** Expiration-time ordered list of (expire time, relay map entry) pairs. */
926-
std::deque<std::pair<std::chrono::microseconds, MapRelay::iterator>> g_relay_expiration GUARDED_BY(NetEventsInterface::g_msgproc_mutex);
927-
928921
/**
929922
* When a peer sends us a valid block, instruct it to announce blocks to us
930923
* using CMPCTBLOCK if possible by adding its nodeid to the end of
@@ -1927,10 +1920,17 @@ void PeerManagerImpl::NewPoWValidBlock(const CBlockIndex *pindex, const std::sha
19271920
std::async(std::launch::deferred, [&] { return msgMaker.Make(NetMsgType::CMPCTBLOCK, *pcmpctblock); })};
19281921

19291922
{
1923+
auto most_recent_block_txs = std::make_unique<std::map<uint256, CTransactionRef>>();
1924+
for (const auto& tx : pblock->vtx) {
1925+
most_recent_block_txs->emplace(tx->GetHash(), tx);
1926+
most_recent_block_txs->emplace(tx->GetWitnessHash(), tx);
1927+
}
1928+
19301929
LOCK(m_most_recent_block_mutex);
19311930
m_most_recent_block_hash = hashBlock;
19321931
m_most_recent_block = pblock;
19331932
m_most_recent_compact_block = pcmpctblock;
1933+
m_most_recent_block_txs = std::move(most_recent_block_txs);
19341934
}
19351935

19361936
m_connman.ForEachNode([this, pindex, &lazy_ser, &hashBlock](CNode* pnode) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
@@ -2301,13 +2301,17 @@ CTransactionRef PeerManagerImpl::FindTxForGetData(const Peer::TxRelay& tx_relay,
23012301
}
23022302
}
23032303

2304-
// Otherwise, the transaction must have been announced recently.
2305-
if (tx_relay.m_recently_announced_invs.contains(gtxid.GetHash())) {
2306-
// If it was, it can be relayed from either the mempool...
2307-
if (txinfo.tx) return std::move(txinfo.tx);
2308-
// ... or the relay pool.
2309-
auto mi = mapRelay.find(gtxid.GetHash());
2310-
if (mi != mapRelay.end()) return mi->second;
2304+
// Otherwise, the transaction might have been announced recently.
2305+
bool recent = tx_relay.m_recently_announced_invs.contains(gtxid.GetHash());
2306+
if (recent && txinfo.tx) return std::move(txinfo.tx);
2307+
2308+
// Or it might be from the most recent block
2309+
{
2310+
LOCK(m_most_recent_block_mutex);
2311+
if (m_most_recent_block_txs != nullptr) {
2312+
auto it = m_most_recent_block_txs->find(gtxid.GetHash());
2313+
if (it != m_most_recent_block_txs->end()) return it->second;
2314+
}
23112315
}
23122316

23132317
return {};
@@ -5778,7 +5782,6 @@ bool PeerManagerImpl::SendMessages(CNode* pto)
57785782
continue;
57795783
}
57805784
auto txid = txinfo.tx->GetHash();
5781-
auto wtxid = txinfo.tx->GetWitnessHash();
57825785
// Peer told you to not send transactions at that feerate? Don't bother sending it.
57835786
if (txinfo.fee < filterrate.GetFee(txinfo.vsize)) {
57845787
continue;
@@ -5788,24 +5791,6 @@ bool PeerManagerImpl::SendMessages(CNode* pto)
57885791
tx_relay->m_recently_announced_invs.insert(hash);
57895792
vInv.push_back(inv);
57905793
nRelayedTransactions++;
5791-
{
5792-
// Expire old relay messages
5793-
while (!g_relay_expiration.empty() && g_relay_expiration.front().first < current_time)
5794-
{
5795-
mapRelay.erase(g_relay_expiration.front().second);
5796-
g_relay_expiration.pop_front();
5797-
}
5798-
5799-
auto ret = mapRelay.emplace(txid, std::move(txinfo.tx));
5800-
if (ret.second) {
5801-
g_relay_expiration.emplace_back(current_time + RELAY_TX_CACHE_TIME, ret.first);
5802-
}
5803-
// Add wtxid-based lookup into mapRelay as well, so that peers can request by wtxid
5804-
auto ret2 = mapRelay.emplace(wtxid, ret.first->second);
5805-
if (ret2.second) {
5806-
g_relay_expiration.emplace_back(current_time + RELAY_TX_CACHE_TIME, ret2.first);
5807-
}
5808-
}
58095794
if (vInv.size() == MAX_INV_SZ) {
58105795
m_connman.PushMessage(pto, msgMaker.Make(NetMsgType::INV, vInv));
58115796
vInv.clear();

test/functional/p2p_leak_tx.py

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
55
"""Test transaction upload"""
66

7-
from test_framework.messages import msg_getdata, CInv, MSG_TX
8-
from test_framework.p2p import p2p_lock, P2PDataStore
7+
from test_framework.messages import msg_getdata, CInv, MSG_TX, MSG_WTX
8+
from test_framework.p2p import p2p_lock, P2PDataStore, P2PTxInvStore
99
from test_framework.test_framework import BitcoinTestFramework
1010
from test_framework.util import (
1111
assert_equal,
@@ -27,6 +27,7 @@ def run_test(self):
2727
self.miniwallet = MiniWallet(self.gen_node)
2828

2929
self.test_tx_in_block()
30+
self.test_notfound_on_replaced_tx()
3031
self.test_notfound_on_unannounced_tx()
3132

3233
def test_tx_in_block(self):
@@ -45,8 +46,36 @@ def test_tx_in_block(self):
4546
inbound_peer.send_and_ping(want_tx)
4647
assert_equal(inbound_peer.last_message.get("tx").tx.getwtxid(), wtxid)
4748

49+
def test_notfound_on_replaced_tx(self):
50+
self.gen_node.disconnect_p2ps()
51+
inbound_peer = self.gen_node.add_p2p_connection(P2PTxInvStore())
52+
53+
self.log.info("Transaction tx_a is broadcast")
54+
tx_a = self.miniwallet.send_self_transfer(from_node=self.gen_node)
55+
inbound_peer.wait_for_broadcast(txns=[tx_a["wtxid"]])
56+
57+
tx_b = tx_a["tx"]
58+
tx_b.vout[0].nValue -= 9000
59+
self.gen_node.sendrawtransaction(tx_b.serialize().hex())
60+
61+
self.log.info("Re-request of tx_a after replacement is answered with notfound")
62+
req_vec = [
63+
CInv(t=MSG_TX, h=int(tx_a["txid"], 16)),
64+
CInv(t=MSG_WTX, h=int(tx_a["wtxid"], 16)),
65+
]
66+
want_tx = msg_getdata()
67+
want_tx.inv = req_vec
68+
with p2p_lock:
69+
inbound_peer.last_message.pop("notfound", None)
70+
inbound_peer.last_message.pop("tx", None)
71+
inbound_peer.send_and_ping(want_tx)
72+
73+
assert_equal(inbound_peer.last_message.get("notfound").vec, req_vec)
74+
assert "tx" not in inbound_peer.last_message
75+
4876
def test_notfound_on_unannounced_tx(self):
4977
self.log.info("Check that we don't leak txs to inbound peers that we haven't yet announced to")
78+
self.gen_node.disconnect_p2ps()
5079
inbound_peer = self.gen_node.add_p2p_connection(P2PNode()) # An "attacking" inbound peer
5180

5281
MAX_REPEATS = 100

0 commit comments

Comments
 (0)