Skip to content

Commit 25eb5cb

Browse files
fanquakePastaPastaPasta
authored andcommitted
Merge bitcoin#18861: Do not answer GETDATA for to-be-announced tx
2896c41 Do not answer GETDATA for to-be-announced tx (Pieter Wuille) f2f32a3 Push down use of cs_main into FindTxForGetData (Pieter Wuille) c6131bf Abstract logic to determine whether to answer tx GETDATA (Pieter Wuille) Pull request description: This PR intends to improve transaction-origin privacy. In general, we should try to not leak information about what transactions we have (recently) learned about before deciding to announce them to our peers. There is a controlled transaction dissemination process that reveals our transactions to peers that has various safeguards for privacy (it's rate-limited, delayed & batched, deterministically sorted, ...), and ideally there is no way to test which transactions we have before that controlled process reveals them. The handling of the `mempool` BIP35 message has protections in this regard as well, as it would be an obvious way to bypass these protections (handled asynchronously after a delay, also deterministically sorted). However, currently, if we receive a GETDATA for a transaction that we have not yet announced to the requester, we will still respond to it if it was announced to *some* other peer already (because it needs to be in `mapRelay`, which only happens on the first announcement). This is a slight privacy leak. Thankfully, this seems easy to solve: `setInventontoryTxToSend` keeps track of the txids we have yet to announce to a peer - which almost(*) exactly corresponds to the transactions we know of that we haven't revealed to that peer. By checking whether a txid is in that set before responding to a GETDATA, we can filter these out. (*) Locally resubmitted or rebroadcasted transactions may end up in setInventoryTxToSend while the peer already knows we have them, which could result in us incorrectly claiming we don't have such transactions if coincidentally requested right after we schedule reannouncing them, but before they're actually INVed. This is made even harder by the fact that filterInventoryKnown will generally keep known reannouncements out of setInventoryTxToSend unless it overflows (which needs 50000 INVs in either direction before it happens). The condition for responding now becomes: ``` (not in setInventoryTxToSend) AND ( (in relay map) OR ( (in mempool) AND (old enough that it could have expired from relay map) AND (older than our last getmempool response) ) ) ``` ACKs for top commit: naumenkogs: utACK 2896c41 ajtowns: ACK 2896c41 amitiuttarwar: code review ACK 2896c41 jonatack: ACK 2896c41 per `git diff 2b3f101 2896c41` only change since previous review is moving the recency check up to be verified first in `FindTxForGetData`, as it was originally in 353a391 (good catch), before looking up the transaction in the relay pool. jnewbery: code review ACK 2896c41 Tree-SHA512: e7d5bc006e626f60a2c108a9334f3bbb67205ace04a7450a1e4d4db1d85922a7589e0524500b7b4953762cf70554c4a08eec62c7b38b486cbca3d86321600868
1 parent 0ecae66 commit 25eb5cb

File tree

1 file changed

+145
-138
lines changed

1 file changed

+145
-138
lines changed

src/net_processing.cpp

Lines changed: 145 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,9 @@ class PeerManagerImpl final : public PeerManager
477477
/** When our tip was last updated. */
478478
std::atomic<int64_t> m_last_tip_update{0};
479479

480+
/** Determine whether or not a peer can request a transaction, and return it (or nullptr if not found or not allowed). */
481+
CTransactionRef FindTxForGetData(CNode* peer, const uint256& txid, const std::chrono::seconds mempool_req, const std::chrono::seconds longlived_mempool_time) LOCKS_EXCLUDED(cs_main);
482+
480483
void ProcessGetData(CNode& pfrom, Peer& peer, const std::atomic<bool>& interruptMsgProc) LOCKS_EXCLUDED(cs_main) EXCLUSIVE_LOCKS_REQUIRED(peer.m_getdata_requests_mutex);
481484

482485
void ProcessBlock(CNode& pfrom, const std::shared_ptr<const CBlock>& pblock, bool fForceProcessing);
@@ -2079,6 +2082,37 @@ void PeerManagerImpl::ProcessGetBlockData(CNode& pfrom, const CChainParams& chai
20792082
}
20802083
}
20812084

2085+
//! Determine whether or not a peer can request a transaction, and return it (or nullptr if not found or not allowed).
2086+
CTransactionRef PeerManagerImpl::FindTxForGetData(CNode* peer, const uint256& txid, const std::chrono::seconds mempool_req, const std::chrono::seconds longlived_mempool_time) LOCKS_EXCLUDED(cs_main)
2087+
{
2088+
// Check if the requested transaction is so recent that we're just
2089+
// about to announce it to the peer; if so, they certainly shouldn't
2090+
// know we already have it.
2091+
{
2092+
LOCK(peer->m_tx_relay->cs_tx_inventory);
2093+
if (peer->m_tx_relay->setInventoryTxToSend.count(txid)) return {};
2094+
}
2095+
2096+
{
2097+
LOCK(cs_main);
2098+
// Look up transaction in relay pool
2099+
auto mi = mapRelay.find(txid);
2100+
if (mi != mapRelay.end()) return mi->second;
2101+
}
2102+
2103+
auto txinfo = m_mempool.info(txid);
2104+
if (txinfo.tx) {
2105+
// To protect privacy, do not answer getdata using the mempool when
2106+
// that TX couldn't have been INVed in reply to a MEMPOOL request,
2107+
// or when it's too recent to have expired from mapRelay.
2108+
if ((mempool_req.count() && txinfo.m_time <= mempool_req) || txinfo.m_time <= longlived_mempool_time) {
2109+
return txinfo.tx;
2110+
}
2111+
}
2112+
2113+
return {};
2114+
}
2115+
20822116
void PeerManagerImpl::ProcessGetData(CNode& pfrom, Peer& peer, const std::atomic<bool>& interruptMsgProc)
20832117
{
20842118
AssertLockNotHeld(cs_main);
@@ -2093,182 +2127,155 @@ void PeerManagerImpl::ProcessGetData(CNode& pfrom, Peer& peer, const std::atomic
20932127
const std::chrono::seconds mempool_req = !pfrom.IsAddrRelayPeer() ? pfrom.m_tx_relay->m_last_mempool_req.load()
20942128
: std::chrono::seconds::min();
20952129

2096-
{
2097-
LOCK(cs_main);
2098-
2099-
// Process as many TX items from the front of the getdata queue as
2100-
// possible, since they're common and it's efficient to batch process
2101-
// them.
2102-
while (it != peer.m_getdata_requests.end() && it->IsKnownType()) {
2103-
if (interruptMsgProc)
2104-
return;
2105-
// The send buffer provides backpressure. If there's no space in
2106-
// the buffer, pause processing until the next call.
2107-
if (pfrom.fPauseSend)
2108-
break;
2130+
// Process as many TX items from the front of the getdata queue as
2131+
// possible, since they're common and it's efficient to batch process
2132+
// them.
2133+
while (it != peer.m_getdata_requests.end() && it->IsKnownType()) {
2134+
if (interruptMsgProc)
2135+
return;
2136+
// The send buffer provides backpressure. If there's no space in
2137+
// the buffer, pause processing until the next call.
2138+
if (pfrom.fPauseSend)
2139+
break;
21092140

2110-
const CInv &inv = *it;
2141+
const CInv &inv = *it;
21112142

2112-
if (inv.type == MSG_BLOCK || inv.type == MSG_FILTERED_BLOCK || inv.type == MSG_CMPCT_BLOCK) {
2113-
break;
2114-
}
2115-
++it;
2143+
if (inv.type == MSG_BLOCK || inv.type == MSG_FILTERED_BLOCK || inv.type == MSG_CMPCT_BLOCK) {
2144+
break;
2145+
}
2146+
++it;
21162147

2117-
if (!pfrom.IsAddrRelayPeer() && NetMessageViolatesBlocksOnly(inv.GetCommand())) {
2118-
// Note that if we receive a getdata for non-block messages
2119-
// from a block-relay-only outbound peer that violate the policy,
2120-
// we skip such getdata messages from this peer
2121-
continue;
2122-
}
2148+
if (!pfrom.IsAddrRelayPeer() && NetMessageViolatesBlocksOnly(inv.GetCommand())) {
2149+
// Note that if we receive a getdata for non-block messages
2150+
// from a block-relay-only outbound peer that violate the policy,
2151+
// we skip such getdata messages from this peer
2152+
continue;
2153+
}
21232154

2124-
// Send stream from relay memory
2125-
bool push = false;
2126-
if (inv.type == MSG_TX || inv.type == MSG_DSTX) {
2155+
bool push = false;
2156+
if (inv.type == MSG_TX || inv.type == MSG_DSTX) {
2157+
CTransactionRef tx = FindTxForGetData(&pfrom, inv.hash, mempool_req, longlived_mempool_time);
2158+
if (tx) {
21272159
CCoinJoinBroadcastTx dstx;
21282160
if (inv.type == MSG_DSTX) {
21292161
dstx = CCoinJoin::GetDSTX(inv.hash);
21302162
}
2131-
auto mi = mapRelay.find(inv.hash);
2132-
if (mi != mapRelay.end()) {
2133-
if (dstx) {
2134-
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::DSTX, dstx));
2135-
} else {
2136-
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::TX, *mi->second));
2137-
}
2138-
push = true;
2163+
if (dstx) {
2164+
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::DSTX, dstx));
21392165
} else {
2140-
auto txinfo = m_mempool.info(inv.hash);
2141-
// To protect privacy, do not answer getdata using the mempool when
2142-
// that TX couldn't have been INVed in reply to a MEMPOOL request,
2143-
// or when it's too recent to have expired from mapRelay.
2144-
if (txinfo.tx && (
2145-
(mempool_req.count() && txinfo.m_time <= mempool_req)
2146-
|| (txinfo.m_time <= longlived_mempool_time)))
2147-
{
2148-
if (dstx) {
2149-
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::DSTX, dstx));
2150-
} else {
2151-
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::TX, *txinfo.tx));
2152-
}
2153-
push = true;
2154-
}
2166+
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::TX, *tx));
21552167
}
2156-
}
2157-
2158-
if (push) {
2159-
// We interpret fulfilling a GETDATA for a transaction as a
2160-
// successful initial broadcast and remove it from our
2161-
// unbroadcast set.
21622168
m_mempool.RemoveUnbroadcastTx(inv.hash);
2169+
push = true;
21632170
}
2171+
}
21642172

2165-
if (!push && inv.type == MSG_SPORK) {
2166-
if (auto opt_spork = sporkManager->GetSporkByHash(inv.hash)) {
2167-
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::SPORK, *opt_spork));
2168-
push = true;
2169-
}
2173+
if (!push && inv.type == MSG_SPORK) {
2174+
if (auto opt_spork = sporkManager->GetSporkByHash(inv.hash)) {
2175+
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::SPORK, *opt_spork));
2176+
push = true;
21702177
}
2178+
}
21712179

2172-
if (!push && inv.type == MSG_GOVERNANCE_OBJECT) {
2173-
CDataStream ss(SER_NETWORK, pfrom.GetSendVersion());
2174-
bool topush = false;
2175-
if (m_govman.HaveObjectForHash(inv.hash)) {
2176-
ss.reserve(1000);
2177-
if (m_govman.SerializeObjectForHash(inv.hash, ss)) {
2178-
topush = true;
2179-
}
2180-
}
2181-
if (topush) {
2182-
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::MNGOVERNANCEOBJECT, ss));
2183-
push = true;
2180+
if (!push && inv.type == MSG_GOVERNANCE_OBJECT) {
2181+
CDataStream ss(SER_NETWORK, pfrom.GetSendVersion());
2182+
bool topush = false;
2183+
if (m_govman.HaveObjectForHash(inv.hash)) {
2184+
ss.reserve(1000);
2185+
if (m_govman.SerializeObjectForHash(inv.hash, ss)) {
2186+
topush = true;
21842187
}
21852188
}
2186-
2187-
if (!push && inv.type == MSG_GOVERNANCE_OBJECT_VOTE) {
2188-
CDataStream ss(SER_NETWORK, pfrom.GetSendVersion());
2189-
bool topush = false;
2190-
if (m_govman.HaveVoteForHash(inv.hash)) {
2191-
ss.reserve(1000);
2192-
if (m_govman.SerializeVoteForHash(inv.hash, ss)) {
2193-
topush = true;
2194-
}
2195-
}
2196-
if (topush) {
2197-
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::MNGOVERNANCEOBJECTVOTE, ss));
2198-
push = true;
2199-
}
2189+
if (topush) {
2190+
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::MNGOVERNANCEOBJECT, ss));
2191+
push = true;
22002192
}
2193+
}
22012194

2202-
if (!push && (inv.type == MSG_QUORUM_FINAL_COMMITMENT)) {
2203-
llmq::CFinalCommitment o;
2204-
if (m_llmq_ctx->quorum_block_processor->GetMineableCommitmentByHash(
2205-
inv.hash, o)) {
2206-
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::QFCOMMITMENT, o));
2207-
push = true;
2195+
if (!push && inv.type == MSG_GOVERNANCE_OBJECT_VOTE) {
2196+
CDataStream ss(SER_NETWORK, pfrom.GetSendVersion());
2197+
bool topush = false;
2198+
if (m_govman.HaveVoteForHash(inv.hash)) {
2199+
ss.reserve(1000);
2200+
if (m_govman.SerializeVoteForHash(inv.hash, ss)) {
2201+
topush = true;
22082202
}
22092203
}
2204+
if (topush) {
2205+
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::MNGOVERNANCEOBJECTVOTE, ss));
2206+
push = true;
2207+
}
2208+
}
22102209

2211-
if (!push && (inv.type == MSG_QUORUM_CONTRIB)) {
2212-
llmq::CDKGContribution o;
2213-
if (m_llmq_ctx->qdkgsman->GetContribution(inv.hash, o)) {
2214-
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::QCONTRIB, o));
2215-
push = true;
2216-
}
2210+
if (!push && (inv.type == MSG_QUORUM_FINAL_COMMITMENT)) {
2211+
llmq::CFinalCommitment o;
2212+
if (m_llmq_ctx->quorum_block_processor->GetMineableCommitmentByHash(
2213+
inv.hash, o)) {
2214+
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::QFCOMMITMENT, o));
2215+
push = true;
22172216
}
2217+
}
22182218

2219-
if (!push && (inv.type == MSG_QUORUM_COMPLAINT)) {
2220-
llmq::CDKGComplaint o;
2221-
if (m_llmq_ctx->qdkgsman->GetComplaint(inv.hash, o)) {
2222-
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::QCOMPLAINT, o));
2223-
push = true;
2224-
}
2219+
if (!push && (inv.type == MSG_QUORUM_CONTRIB)) {
2220+
llmq::CDKGContribution o;
2221+
if (m_llmq_ctx->qdkgsman->GetContribution(inv.hash, o)) {
2222+
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::QCONTRIB, o));
2223+
push = true;
22252224
}
2225+
}
22262226

2227-
if (!push && (inv.type == MSG_QUORUM_JUSTIFICATION)) {
2228-
llmq::CDKGJustification o;
2229-
if (m_llmq_ctx->qdkgsman->GetJustification(inv.hash, o)) {
2230-
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::QJUSTIFICATION, o));
2231-
push = true;
2232-
}
2227+
if (!push && (inv.type == MSG_QUORUM_COMPLAINT)) {
2228+
llmq::CDKGComplaint o;
2229+
if (m_llmq_ctx->qdkgsman->GetComplaint(inv.hash, o)) {
2230+
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::QCOMPLAINT, o));
2231+
push = true;
22332232
}
2233+
}
22342234

2235-
if (!push && (inv.type == MSG_QUORUM_PREMATURE_COMMITMENT)) {
2236-
llmq::CDKGPrematureCommitment o;
2237-
if (m_llmq_ctx->qdkgsman->GetPrematureCommitment(inv.hash, o)) {
2238-
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::QPCOMMITMENT, o));
2239-
push = true;
2240-
}
2235+
if (!push && (inv.type == MSG_QUORUM_JUSTIFICATION)) {
2236+
llmq::CDKGJustification o;
2237+
if (m_llmq_ctx->qdkgsman->GetJustification(inv.hash, o)) {
2238+
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::QJUSTIFICATION, o));
2239+
push = true;
22412240
}
2241+
}
22422242

2243-
if (!push && (inv.type == MSG_QUORUM_RECOVERED_SIG)) {
2244-
llmq::CRecoveredSig o;
2245-
if (m_llmq_ctx->sigman->GetRecoveredSigForGetData(inv.hash, o)) {
2246-
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::QSIGREC, o));
2247-
push = true;
2248-
}
2243+
if (!push && (inv.type == MSG_QUORUM_PREMATURE_COMMITMENT)) {
2244+
llmq::CDKGPrematureCommitment o;
2245+
if (m_llmq_ctx->qdkgsman->GetPrematureCommitment(inv.hash, o)) {
2246+
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::QPCOMMITMENT, o));
2247+
push = true;
22492248
}
2249+
}
22502250

2251-
if (!push && (inv.type == MSG_CLSIG)) {
2252-
llmq::CChainLockSig o;
2253-
if (m_llmq_ctx->clhandler->GetChainLockByHash(inv.hash, o)) {
2254-
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::CLSIG, o));
2255-
push = true;
2256-
}
2251+
if (!push && (inv.type == MSG_QUORUM_RECOVERED_SIG)) {
2252+
llmq::CRecoveredSig o;
2253+
if (m_llmq_ctx->sigman->GetRecoveredSigForGetData(inv.hash, o)) {
2254+
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::QSIGREC, o));
2255+
push = true;
22572256
}
2257+
}
22582258

2259-
if (!push && inv.type == MSG_ISDLOCK) {
2260-
llmq::CInstantSendLock o;
2261-
if (m_llmq_ctx->isman->GetInstantSendLockByHash(inv.hash, o)) {
2262-
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::ISDLOCK, o));
2263-
push = true;
2264-
}
2259+
if (!push && (inv.type == MSG_CLSIG)) {
2260+
llmq::CChainLockSig o;
2261+
if (m_llmq_ctx->clhandler->GetChainLockByHash(inv.hash, o)) {
2262+
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::CLSIG, o));
2263+
push = true;
22652264
}
2265+
}
22662266

2267-
if (!push) {
2268-
vNotFound.push_back(inv);
2267+
if (!push && inv.type == MSG_ISDLOCK) {
2268+
llmq::CInstantSendLock o;
2269+
if (m_llmq_ctx->isman->GetInstantSendLockByHash(inv.hash, o)) {
2270+
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::ISDLOCK, o));
2271+
push = true;
22692272
}
22702273
}
2271-
} // release cs_main
2274+
2275+
if (!push) {
2276+
vNotFound.push_back(inv);
2277+
}
2278+
}
22722279

22732280
// Only process one BLOCK item per call, since they're uncommon and can be
22742281
// expensive to process.

0 commit comments

Comments
 (0)