-
Notifications
You must be signed in to change notification settings - Fork 37.7k
p2p: Stop relaying non-mempool txs, improve tx privacy slightly #17303
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
I think this is the eventual right direction for us to go (because mapRelay doesn't have great memory bounds), but without something like #15505 this turns nodes into accidental DoSers whenever they evict a transaction after announcement and before a getdata comes in. If you're a node running with a small mempool, then transactions can be evicted even if they're not "low feerate". I tend to think we should not be turning nodes with small mempools into DoSers; so if we solve that problem first (eg by making the peer receiving a NOTFOUND be able to find the transaction elsewhere in a reasonable amount of time), then I think we could go ahead and remove mapRelay and only serve things from our mempool. We should go ahead and fix the privacy problem (mentioned in #14220) at the same time though. |
fae27e8
to
faf64f7
Compare
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. ConflictsReviewers, this pull request conflicts with the following ones:
If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first. |
This sounds reasonable to me, but I think we should be careful here to not introduce vulns as a side-effect. |
fadf136
to
faa31e4
Compare
Minimized the diff. Also fixed the privacy issue as requested by @sdaftuar |
SummaryI think the privacy problem mentioned above is fixed here in a limited way. This proposal fixes a), but does not fix b), see below. New attackWe will not respond if something wasn't INVed, right. But imagine flooding the network with tx_A, so that everybody announces is to an attacker and an attacker now can query for it. Stuff like diffusion, previous PR addressing the problem and this proposal would make it difficult to infer tx_A or tx_B relay paths directly, but they can easily be deduced by querying for tx_A. So, the cost of an attack is increased from IdeasCrazy expensive but fundamentalKeep responding about tx_A even when it's replaced with tx_B. Less expensive but limitedAllow at most 1 (or 2 just in case) queries per transaction. So that tx_A can be requested by every node at most once. Maybe this should be implemented as a follow-up PR, I might do that sometime. |
faa31e4
to
fa5f3da
Compare
src/net_processing.cpp
Outdated
@@ -1554,20 +1554,17 @@ void static ProcessGetData(CNode* pfrom, const CChainParams& chainparams, CConnm | |||
const CInv &inv = *it; | |||
it++; | |||
|
|||
// Send stream from relay memory | |||
bool push = false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can now remove push
and then update vNotFound
in an else branch opposite the PushMessage
below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Happy to remove in a new commit if I get at least one ACK and others agree to do this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
push
is used in #18038, so to avoid a merge conflict I will leave it as is for now. Whichever pull gets merged last can address this nit.
src/net_processing.cpp
Outdated
connman->PushMessage(pfrom, msgMaker.Make(nSendFlags, NetMsgType::TX, *mi->second)); | ||
push = true; | ||
} else { | ||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this scope needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no, but it keeps the scope that came previously after the else to minimize the diff. Happy to remove in a new commit if I get at least one ACK and others agree to do this.
The PR title states that this change "improve tx privacy slightly". Could you elaborate in the PR description how? (i.e., why was it less private before?) |
Concept ACK @sdaftuar
@naumenkogs |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Concept ACK on the privacy leak fix, i.e before this PR peers could have sent us unsolicited GETDATAs to identify tx in our mempool but not previously INVed. The strict requirement of tx m_time
being earlier than m_last_inv_sent
to be broadcast guarantee we don't send TX(tx) without INV them before.
On the potential DoS vector,
That's plenty of time for the peer to request the tx from another peer.
@jnewbery but I think Suhas point is requester is going to keep sending GETDATA as tx isn't erased from m_tx_process_time
at NOTFOUND processing?
I think the concern is that a peer will send us an INV for a tx that then gets removed from its mempool because of I think that's ok because one minute later we'll request from another peer that INV'ed the transaction to us: bitcoin/src/net_processing.cpp Line 4043 in cd6cb97
SIZELIMIT , then one minute delay shouldn't be a problem (since we don't expect the tx to be mined any time soon).
|
Ok, I was making the assumption than the NOTFOUND peer is the origin one so you don't have an INV from other peers. Isn't our current logic going to loop on the sending GETDATA, receiving NOTFOUND sequence ? Or is this too an edge case? |
So the benefits of this PR are presumably to eliminate DoS attack surface on mapRelay (though I’m not sure anyone has articulated this clearly) and to solve a privacy issue. I think both those things could also be solved without introducing relay problems from announcing transactions and then not responding (and I’ve proposed one approach that I think would be satisfactory; I’m sure someone could probably propose a solution to both these things that involves keeping mapRelay around as well). Anyway I don’t understand why we would be sloppy here; it’s not like relay degradation is some kind of fundamental trade-off with what we’re trying to accomplish. (And the DoS and privacy issues are not new, they have been around for a long time, so it doesn’t really seem like they are urgent to address either?) (BTW if anyone wants to pick up #15505 and try to get that merged so that this gets unblocked, please go for it — I closed it earlier this year because there didn’t seem to be much review interest, and then reopened it in light of this PR to remind others of one approach to solving the issue that I thought needed to be fixed first, before we went in the direction proposed here.) |
I have never said it's made any worse. What I don't like is that in future when we'll want to fix privacy issue we might return |
I don't think we have a clear enough idea on how to fix privacy issues in non-token ways to do anything "right away" here? And if we'll want a different thing than |
I don't think this PR really makes reacting to notfounds a more urgent issue for all the reasons @jnewbery lists. But I had a look at #15505 again anyway, and think that logic like "if we see a notfound for an inflight tx, look through our other peers to reset the process_time for that tx that was soonest to right now" should be fine -- we don't need to take any new locks, and we're just iterating over and looking up some maps which should be plenty fast. Example patch at https://github.com/ajtowns/bitcoin/commits/202002-bump-notfound I don't think the p2p_leak_tx test is quite right -- if I take away the "break" after a GETDATA fails, it continues for a while then fails on the |
faa40d1
to
fa470f9
Compare
The test is "right". It is only single-use, as correctly assumed by you: In the next run after receiving the notfound, it will receive a bunch of invs of previous transactions (but not yet the inv of the current transaction), so the assertion will fail. |
fa63155
to
fa9b9d1
Compare
🐙 This pull request conflicts with the target branch and needs rebase. |
7bfd0f1
to
ca65330
Compare
ca65330
to
4dcefd7
Compare
Looks like there are still a couple of references to |
Please don't review yet. This has not yet properly been rebased. |
Closing for now. There have been some other changes, so if this still makes sense, it is probably best to start a new pull request |
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: #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 #18861 (comment)). * Guarantees that locally resubmitted invs after `filterInventoryKnown` rolls over can still be requested (pointed out by luke-jr, see #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
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#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#18861 (comment)). * Guarantees that locally resubmitted invs after `filterInventoryKnown` rolls over can still be requested (pointed out by luke-jr, see 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 bitcoin#17303), but that is still blocked by dealing properly with NOTFOUNDs (see bitcoin#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
There is no reason to relay transactions that have been, but are no longer, in the mempool at the time of the getdata request for the transaction.
Possible reasons for mempool removal, and my thinking why it is safe to not relay
EXPIRY
SIZELIMIT
GETDATA_TX_INTERVAL
. Assuming it ever made it to another peer.REORG
disconnectpool
later on and then relayed (TODO), so this should never be observable in practise.BLOCK
CONFLICT
REPLACED
This should also improve transaction relay privacy slightly. Previously any peer (even inbounds) could ask for transactions in
mapRelay
, even if they were never announced to them via aninv
message. After my changes, the peer can only request transactions that were added to the mempool before the lastinv
message was sent to that peer.