Skip to content

Commit 5550fa5

Browse files
committed
Merge #19109: Only allow getdata of recently announced invs
f32c408 Make sure unconfirmed parents are requestable (Pieter Wuille) c4626bc Drop setInventoryTxToSend based filtering (Pieter Wuille) 43f02cc Only respond to requests for recently announced transactions (Pieter Wuille) b24a17f Introduce constant for mempool-based relay separate from mapRelay caching (Pieter Wuille) a9bc563 Swap relay pool and mempool lookup (Pieter Wuille) Pull request description: This implements the follow-up suggested here: bitcoin/bitcoin#18861 (comment) . Instead of checking `setInventoryTxToSend`, maintain an explicit bloom filter with the 3500 most recently announced invs, and permit fetching any of these as long as they're in the relay pool or the mempool. In addition, permit relay from the mempool after just 2 minutes instead of 15. This: * Fixes the brief opportunity an attacker has to request unannounced invs just after the connection is established (pointed out by naumenkogs, see bitcoin/bitcoin#18861 (comment)). * Guarantees that locally resubmitted invs after `filterInventoryKnown` rolls over can still be requested (pointed out by luke-jr, see bitcoin/bitcoin#18861 (comment)). It adds 37 KiB of filter per peer. This is also a step towards dropping the relay pool entirely and always relaying from the mempool directly (see #17303), but that is still blocked by dealing properly with NOTFOUNDs (see #18238). ACKs for top commit: jnewbery: reACK f32c408 jonatack: re-ACK f32c408 per `git range-diff f7c19e8 2da7ee3 f32c408` and redid the following: code review, thought about motivation, DoS and privacy aspects, debug build to check for warnings after updating Clang from 6 to 11 since last review. ajtowns: re-ACK f32c408 Tree-SHA512: aa05b9fd01bad59581c4ec91836a52d7415dc933fa49d4c4adced79aa25aaad51e11166357e8c8b29fbf6021a7401b98c21b850b5d8e8ad773fdb5d6608e1e85
2 parents 89fa736 + f32c408 commit 5550fa5

File tree

1 file changed

+49
-24
lines changed

1 file changed

+49
-24
lines changed

src/net_processing.cpp

Lines changed: 49 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ static constexpr int64_t ORPHAN_TX_EXPIRE_TIME = 20 * 60;
3838
/** Minimum time between orphan transactions expire time checks in seconds */
3939
static constexpr int64_t ORPHAN_TX_EXPIRE_INTERVAL = 5 * 60;
4040
/** How long to cache transactions in mapRelay for normal relay */
41-
static constexpr std::chrono::seconds RELAY_TX_CACHE_TIME{15 * 60};
41+
static constexpr std::chrono::seconds RELAY_TX_CACHE_TIME = std::chrono::minutes{15};
42+
/** How long a transaction has to be in the mempool before it can unconditionally be relayed (even when not in mapRelay). */
43+
static constexpr std::chrono::seconds UNCONDITIONAL_RELAY_DELAY = std::chrono::minutes{2};
4244
/** Headers download timeout expressed in microseconds
4345
* Timeout = base + per_header * (expected number of headers) */
4446
static constexpr int64_t HEADERS_DOWNLOAD_TIMEOUT_BASE = 15 * 60 * 1000000; // 15 minutes
@@ -119,9 +121,18 @@ static constexpr std::chrono::seconds AVG_ADDRESS_BROADCAST_INTERVAL{30};
119121
/** Average delay between trickled inventory transmissions in seconds.
120122
* Blocks and peers with noban permission bypass this, outbound peers get half this delay. */
121123
static const unsigned int INVENTORY_BROADCAST_INTERVAL = 5;
122-
/** Maximum number of inventory items to send per transmission.
124+
/** Maximum rate of inventory items to send per second.
123125
* Limits the impact of low-fee transaction floods. */
124-
static constexpr unsigned int INVENTORY_BROADCAST_MAX = 7 * INVENTORY_BROADCAST_INTERVAL;
126+
static constexpr unsigned int INVENTORY_BROADCAST_PER_SECOND = 7;
127+
/** Maximum number of inventory items to send per transmission. */
128+
static constexpr unsigned int INVENTORY_BROADCAST_MAX = INVENTORY_BROADCAST_PER_SECOND * INVENTORY_BROADCAST_INTERVAL;
129+
/** The number of most recently announced transactions a peer can request. */
130+
static constexpr unsigned int INVENTORY_MAX_RECENT_RELAY = 3500;
131+
/** Verify that INVENTORY_MAX_RECENT_RELAY is enough to cache everything typically
132+
* relayed before unconditional relay from the mempool kicks in. This is only a
133+
* lower bound, and it should be larger to account for higher inv rate to outbound
134+
* peers, and random variations in the broadcast mechanism. */
135+
static_assert(INVENTORY_MAX_RECENT_RELAY >= INVENTORY_BROADCAST_PER_SECOND * UNCONDITIONAL_RELAY_DELAY / std::chrono::seconds{1}, "INVENTORY_RELAY_MAX too low");
125136
/** Average delay between feefilter broadcasts in seconds. */
126137
static constexpr unsigned int AVG_FEEFILTER_BROADCAST_INTERVAL = 10 * 60;
127138
/** Maximum feefilter broadcast delay after significant change. */
@@ -395,6 +406,9 @@ struct CNodeState {
395406
//! Whether this peer is a manual connection
396407
bool m_is_manual_connection;
397408

409+
//! A rolling bloom filter of all announced tx CInvs to this peer.
410+
CRollingBloomFilter m_recently_announced_invs = CRollingBloomFilter{INVENTORY_MAX_RECENT_RELAY, 0.000001};
411+
398412
CNodeState(CAddress addrIn, std::string addrNameIn, bool is_inbound, bool is_manual) :
399413
address(addrIn), name(std::move(addrNameIn)), m_is_inbound(is_inbound),
400414
m_is_manual_connection (is_manual)
@@ -422,6 +436,7 @@ struct CNodeState {
422436
fSupportsDesiredCmpctVersion = false;
423437
m_chain_sync = { 0, nullptr, false, false };
424438
m_last_block_announcement = 0;
439+
m_recently_announced_invs.reset();
425440
}
426441
};
427442

@@ -1617,30 +1632,28 @@ void static ProcessGetBlockData(CNode& pfrom, const CChainParams& chainparams, c
16171632
}
16181633

16191634
//! Determine whether or not a peer can request a transaction, and return it (or nullptr if not found or not allowed).
1620-
CTransactionRef static FindTxForGetData(CNode& peer, const uint256& txid, const std::chrono::seconds mempool_req, const std::chrono::seconds longlived_mempool_time) LOCKS_EXCLUDED(cs_main)
1635+
CTransactionRef static FindTxForGetData(const CNode& peer, const uint256& txid, const std::chrono::seconds mempool_req, const std::chrono::seconds now) LOCKS_EXCLUDED(cs_main)
16211636
{
1622-
// Check if the requested transaction is so recent that we're just
1623-
// about to announce it to the peer; if so, they certainly shouldn't
1624-
// know we already have it.
1625-
{
1626-
LOCK(peer.m_tx_relay->cs_tx_inventory);
1627-
if (peer.m_tx_relay->setInventoryTxToSend.count(txid)) return {};
1637+
auto txinfo = mempool.info(txid);
1638+
if (txinfo.tx) {
1639+
// If a TX could have been INVed in reply to a MEMPOOL request,
1640+
// or is older than UNCONDITIONAL_RELAY_DELAY, permit the request
1641+
// unconditionally.
1642+
if ((mempool_req.count() && txinfo.m_time <= mempool_req) || txinfo.m_time <= now - UNCONDITIONAL_RELAY_DELAY) {
1643+
return std::move(txinfo.tx);
1644+
}
16281645
}
16291646

16301647
{
16311648
LOCK(cs_main);
1632-
// Look up transaction in relay pool
1633-
auto mi = mapRelay.find(txid);
1634-
if (mi != mapRelay.end()) return mi->second;
1635-
}
16361649

1637-
auto txinfo = mempool.info(txid);
1638-
if (txinfo.tx) {
1639-
// To protect privacy, do not answer getdata using the mempool when
1640-
// that TX couldn't have been INVed in reply to a MEMPOOL request,
1641-
// or when it's too recent to have expired from mapRelay.
1642-
if ((mempool_req.count() && txinfo.m_time <= mempool_req) || txinfo.m_time <= longlived_mempool_time) {
1643-
return txinfo.tx;
1650+
// Otherwise, the transaction must have been announced recently.
1651+
if (State(peer.GetId())->m_recently_announced_invs.contains(txid)) {
1652+
// If it was, it can be relayed from either the mempool...
1653+
if (txinfo.tx) return std::move(txinfo.tx);
1654+
// ... or the relay pool.
1655+
auto mi = mapRelay.find(txid);
1656+
if (mi != mapRelay.end()) return mi->second;
16441657
}
16451658
}
16461659

@@ -1655,8 +1668,7 @@ void static ProcessGetData(CNode& pfrom, const CChainParams& chainparams, CConnm
16551668
std::vector<CInv> vNotFound;
16561669
const CNetMsgMaker msgMaker(pfrom.GetSendVersion());
16571670

1658-
// mempool entries added before this time have likely expired from mapRelay
1659-
const std::chrono::seconds longlived_mempool_time = GetTime<std::chrono::seconds>() - RELAY_TX_CACHE_TIME;
1671+
const std::chrono::seconds now = GetTime<std::chrono::seconds>();
16601672
// Get last mempool request time
16611673
const std::chrono::seconds mempool_req = pfrom.m_tx_relay != nullptr ? pfrom.m_tx_relay->m_last_mempool_req.load()
16621674
: std::chrono::seconds::min();
@@ -1677,11 +1689,22 @@ void static ProcessGetData(CNode& pfrom, const CChainParams& chainparams, CConnm
16771689
continue;
16781690
}
16791691

1680-
CTransactionRef tx = FindTxForGetData(pfrom, inv.hash, mempool_req, longlived_mempool_time);
1692+
CTransactionRef tx = FindTxForGetData(pfrom, inv.hash, mempool_req, now);
16811693
if (tx) {
16821694
int nSendFlags = (inv.type == MSG_TX ? SERIALIZE_TRANSACTION_NO_WITNESS : 0);
16831695
connman->PushMessage(&pfrom, msgMaker.Make(nSendFlags, NetMsgType::TX, *tx));
16841696
mempool.RemoveUnbroadcastTx(inv.hash);
1697+
// As we're going to send tx, make sure its unconfirmed parents are made requestable.
1698+
for (const auto& txin : tx->vin) {
1699+
auto txinfo = mempool.info(txin.prevout.hash);
1700+
if (txinfo.tx && txinfo.m_time > now - UNCONDITIONAL_RELAY_DELAY) {
1701+
// Relaying a transaction with a recent but unconfirmed parent.
1702+
if (WITH_LOCK(pfrom.m_tx_relay->cs_tx_inventory, return !pfrom.m_tx_relay->filterInventoryKnown.contains(txin.prevout.hash))) {
1703+
LOCK(cs_main);
1704+
State(pfrom.GetId())->m_recently_announced_invs.insert(txin.prevout.hash);
1705+
}
1706+
}
1707+
}
16851708
} else {
16861709
vNotFound.push_back(inv);
16871710
}
@@ -4149,6 +4172,7 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
41494172
if (!pto->m_tx_relay->pfilter->IsRelevantAndUpdate(*txinfo.tx)) continue;
41504173
}
41514174
pto->m_tx_relay->filterInventoryKnown.insert(hash);
4175+
// Responses to MEMPOOL requests bypass the m_recently_announced_invs filter.
41524176
vInv.push_back(inv);
41534177
if (vInv.size() == MAX_INV_SZ) {
41544178
connman->PushMessage(pto, msgMaker.Make(NetMsgType::INV, vInv));
@@ -4202,6 +4226,7 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
42024226
}
42034227
if (pto->m_tx_relay->pfilter && !pto->m_tx_relay->pfilter->IsRelevantAndUpdate(*txinfo.tx)) continue;
42044228
// Send
4229+
State(pto->GetId())->m_recently_announced_invs.insert(hash);
42054230
vInv.push_back(CInv(MSG_TX, hash));
42064231
nRelayedTransactions++;
42074232
{

0 commit comments

Comments
 (0)