Skip to content
Open
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
59 changes: 24 additions & 35 deletions src/net_processing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,7 @@ static constexpr auto GETDATA_TX_INTERVAL{60s};
/** Limit to avoid sending big packets. Not used in processing incoming GETDATA for compatibility */
static const unsigned int MAX_GETDATA_SZ = 1000;

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

/** Height of the highest block announced using BIP 152 high-bandwidth mode. */
int m_highest_fast_announce GUARDED_BY(::cs_main){0};
Expand Down Expand Up @@ -1100,8 +1099,8 @@ class PeerManagerImpl final : public PeerManager
std::atomic<std::chrono::seconds> m_last_tip_update{0s};

/** Determine whether or not a peer can request a transaction, and return it (or nullptr if not found or not allowed). */
CTransactionRef FindTxForGetData(const Peer& peer, const uint256& txid, const std::chrono::seconds mempool_req, const std::chrono::seconds now)
LOCKS_EXCLUDED(cs_main) EXCLUSIVE_LOCKS_REQUIRED(NetEventsInterface::g_msgproc_mutex);
CTransactionRef FindTxForGetData(const Peer::TxRelay& tx_relay, const uint256& txid, const std::chrono::seconds mempool_req, const std::chrono::seconds now)
EXCLUSIVE_LOCKS_REQUIRED(!m_most_recent_block_mutex, NetEventsInterface::g_msgproc_mutex);

void ProcessGetData(CNode& pfrom, Peer& peer, const std::atomic<bool>& interruptMsgProc)
EXCLUSIVE_LOCKS_REQUIRED(!m_most_recent_block_mutex, peer.m_getdata_requests_mutex, NetEventsInterface::g_msgproc_mutex)
Expand All @@ -1114,12 +1113,6 @@ class PeerManagerImpl final : public PeerManager
void ProcessCompactBlockTxns(CNode& pfrom, Peer& peer, const BlockTransactions& block_transactions)
EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex, !m_most_recent_block_mutex);

/** Relay map (txid -> CTransactionRef) */
typedef std::map<uint256, CTransactionRef> MapRelay;
MapRelay mapRelay GUARDED_BY(cs_main);
/** Expiration-time ordered list of (expire time, relay map entry) pairs. */
std::deque<std::pair<std::chrono::microseconds, MapRelay::iterator>> g_relay_expiration GUARDED_BY(cs_main);

/**
* When a peer sends us a valid block, instruct it to announce blocks to us
* using CMPCTBLOCK if possible by adding its nodeid to the end of
Expand Down Expand Up @@ -2259,10 +2252,19 @@ void PeerManagerImpl::NewPoWValidBlock(const CBlockIndex *pindex, const std::sha
std::async(std::launch::deferred, [&] { return msgMaker.Make(NetMsgType::CMPCTBLOCK, *pcmpctblock); })};

{
auto most_recent_block_txs = std::make_unique<std::map<uint256, CTransactionRef>>();
for (const auto& tx : pblock->vtx) {
most_recent_block_txs->emplace(tx->GetHash(), tx);
if (IsAssetUnlockWithStableTxid(*tx)) {
most_recent_block_txs->emplace(tx->GetInstanceHash(), tx);
}
}

LOCK(m_most_recent_block_mutex);
m_most_recent_block_hash = hashBlock;
m_most_recent_block = pblock;
m_most_recent_compact_block = pcmpctblock;
m_most_recent_block_txs = std::move(most_recent_block_txs);
}

m_connman.ForEachNode([this, pindex, &lazy_ser, &hashBlock](CNode* pnode) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
Expand Down Expand Up @@ -2918,7 +2920,7 @@ void PeerManagerImpl::ProcessGetBlockData(CNode& pfrom, Peer& peer, const CInv&
}

//! Determine whether or not a peer can request a transaction, and return it (or nullptr if not found or not allowed).
CTransactionRef PeerManagerImpl::FindTxForGetData(const Peer& peer, const uint256& txid, const std::chrono::seconds mempool_req, const std::chrono::seconds now)
CTransactionRef PeerManagerImpl::FindTxForGetData(const Peer::TxRelay& tx_relay, const uint256& txid, const std::chrono::seconds mempool_req, const std::chrono::seconds now)
{
auto txinfo = m_mempool.info(txid);
if (!txinfo.tx) {
Expand All @@ -2936,16 +2938,16 @@ CTransactionRef PeerManagerImpl::FindTxForGetData(const Peer& peer, const uint25
}
}

{
LOCK(cs_main);
// Otherwise, the transaction might have been announced recently.
bool recent = tx_relay.m_recently_announced_invs.contains(txid);
if (recent && txinfo.tx) return std::move(txinfo.tx);

// Otherwise, the transaction must have been announced recently.
if (Assume(peer.GetTxRelay())->m_recently_announced_invs.contains(txid)) {
// If it was, it can be relayed from either the mempool...
if (txinfo.tx) return std::move(txinfo.tx);
// ... or the relay pool.
auto mi = mapRelay.find(txid);
if (mi != mapRelay.end()) return mi->second;
// Or it might be from the most recent block
{
LOCK(m_most_recent_block_mutex);
if (m_most_recent_block_txs != nullptr) {
auto it = m_most_recent_block_txs->find(txid);
if (it != m_most_recent_block_txs->end()) return it->second;
}
}

Expand Down Expand Up @@ -2994,7 +2996,7 @@ void PeerManagerImpl::ProcessGetData(CNode& pfrom, Peer& peer, const std::atomic

bool push = false;
if (inv.IsGenTxMsg()) {
CTransactionRef tx = FindTxForGetData(peer, inv.hash, mempool_req, now);
CTransactionRef tx = FindTxForGetData(*tx_relay, inv.hash, mempool_req, now);
if (tx) {
CCoinJoinBroadcastTx dstx;
if (inv.IsMsgDstx()) {
Expand Down Expand Up @@ -6783,19 +6785,6 @@ bool PeerManagerImpl::SendMessages(CNode* pto)
// Send
tx_relay->m_recently_announced_invs.insert(announce_hash);
nRelayedTransactions++;
{
// Expire old relay messages
while (!g_relay_expiration.empty() && g_relay_expiration.front().first < current_time)
{
mapRelay.erase(g_relay_expiration.front().second);
g_relay_expiration.pop_front();
}

auto ret = mapRelay.emplace(announce_hash, std::move(txinfo.tx));
if (ret.second) {
g_relay_expiration.emplace_back(current_time + RELAY_TX_CACHE_TIME, ret.first);
}
}
tx_relay->m_tx_inventory_known_filter.insert(announce_hash);
queueAndMaybePushInv(CInv(nInvType, announce_hash));
}
Expand Down
32 changes: 31 additions & 1 deletion test/functional/p2p_leak_tx.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"""Test transaction upload"""

from test_framework.messages import msg_getdata, CInv, MSG_TX
from test_framework.p2p import p2p_lock, P2PDataStore
from test_framework.p2p import p2p_lock, P2PDataStore, P2PTxInvStore
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
Expand All @@ -27,6 +27,7 @@ def run_test(self):
self.miniwallet = MiniWallet(self.gen_node)

self.test_tx_in_block()
self.test_notfound_on_replaced_tx()
self.test_notfound_on_unannounced_tx()

def test_tx_in_block(self):
Expand All @@ -46,8 +47,37 @@ def test_tx_in_block(self):
inbound_peer.send_and_ping(want_tx)
assert_equal(inbound_peer.last_message.get("tx").tx.rehash(), txid)

def test_notfound_on_replaced_tx(self):
self.gen_node.disconnect_p2ps()
inbound_peer = self.gen_node.add_p2p_connection(P2PTxInvStore())

self.log.info("Transaction tx_a is broadcast")
tx_a = self.miniwallet.send_self_transfer(from_node=self.gen_node)
self.bump_mocktime(60)
inbound_peer.wait_for_broadcast(txns=[tx_a["txid"]])

# Dash has no RBF: replace tx_a by mining a conflicting tx_b
tx_b = tx_a["tx"]
tx_b.vout[0].nValue -= 9000
self.generateblock(self.gen_node, output=self.miniwallet.get_address(), transactions=[tx_b.serialize().hex()])

self.log.info("Re-request of tx_a after replacement is answered with notfound")
req_vec = [
CInv(t=MSG_TX, h=int(tx_a["txid"], 16)),
]
want_tx = msg_getdata()
want_tx.inv = req_vec
with p2p_lock:
inbound_peer.last_message.pop("notfound", None)
inbound_peer.last_message.pop("tx", None)
inbound_peer.send_and_ping(want_tx)

assert_equal(inbound_peer.last_message.get("notfound").vec, req_vec)
assert "tx" not in inbound_peer.last_message

def test_notfound_on_unannounced_tx(self):
self.log.info("Check that we don't leak txs to inbound peers that we haven't yet announced to")
self.gen_node.disconnect_p2ps()
inbound_peer = self.gen_node.add_p2p_connection(P2PNode()) # An "attacking" inbound peer

MAX_REPEATS = 100
Expand Down
Loading