-
Notifications
You must be signed in to change notification settings - Fork 38.6k
p2p: Protect last outbound HB compact block peer #22147
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
If all our high-bandwidth compact block serving peers (BIP 152) stall block download, then we can be denied a block for (potentially) a long time. As inbound connections are much more likely to be adversarial than outbound connections, mitigate this risk by never removing our last outbound HB peer if it would be replaced by an inbound.
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 and code review ACK 6efbcec
Running a node with this patch.
A test in p2p_compactblocks.py would be nice.
If this is implemented, should BIP152 "Implementation Notes" be updated with a note?
| if (nodestate->m_is_inbound) { | ||
| // If we're adding an inbound HB peer, make sure we're not removing | ||
| // our last outbound HB peer in the process. | ||
| if (lNodesAnnouncingHeaderAndIDs.size() >= 3 && num_outbound_hb_peers == 1) { |
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.
Optional, if retouch perhaps hoist the 3 here and in line 854 to a constexpr and maybe the following mini-wishlist:
int num_outbound_hb_peers = 0;
for (std::list<NodeId>::iterator it = lNodesAnnouncingHeaderAndIDs.begin(); it != lNodesAnnouncingHeaderAndIDs.end(); it++) {
if (*it == nodeid) {
+ // Move this nodeid to the end of the list.
lNodesAnnouncingHeaderAndIDs.erase(it);
lNodesAnnouncingHeaderAndIDs.push_back(nodeid);
return;
@@ -1556,7 +1557,7 @@ void PeerManagerImpl::BlockChecked(const CBlock& block, const BlockValidationSta
!m_chainman.ActiveChainstate().IsInitialBlockDownload() &&
mapBlocksInFlight.count(hash) == mapBlocksInFlight.size()) {
if (it != mapBlockSource.end()) {
- MaybeSetPeerAsAnnouncingHeaderAndIDs(it->second.first);
+ MaybeSetPeerAsAnnouncingHeaderAndIDs(/*nodeid=*/ it->second.first);
}|
ACK 6efbcec As jonatack mentioned, I think extending the existing functional test would be good. |
|
Concept ACK. It seems reasonable to protect one outbound peer as an HB compact block source. I agree with @jonatack and @Crypt-iQ that it'd be good to see a test for this new behaviour. I've always found the |
|
I agree a test would be nice -- I'm not planning to write one myself, but if someone contributes one I'd be happy to include it here.
I don't know for sure, but my guess is that it's easiest to reason about the logic if the substitution of going from one HB peer to another all happens within one lock acquisition? At any rate, I think the real problem with readability is having |
|
Here is an RPC-based test for compact blocks HB selection: https://github.com/sipa/bitcoin/commits/sdaftuar-2021-06-reserve-outbound-hb |
|
@sipa Thanks, I've cherry-picked that commit onto this branch. |
3aba631 to
30aee2d
Compare
| def peer_info(self, from_node, to_node): | ||
| """Query from_node for its getpeerinfo about to_node.""" | ||
| for peerinfo in self.nodes[from_node].getpeerinfo(): | ||
| if "(testnode%i)" % to_node in peerinfo['subver']: |
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.
If you touch this branch again, consider using f-strings for string formatting (https://github.com/bitcoin/bitcoin/blob/master/test/functional/README.md#style-guidelines)
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.
ACK 30aee2d
Verified the test fails on master at line 81 (and at no other point) and passes on this branch; pprinting the value of status at that point shows the following differences between master and this change:
master
[True, True, False, True ]
[True, True, True, False]
[False, True, True, True ] <--- assert fails at this point
branch
[True, True, False, True ]
[True, True, True, False]
[True, False, True, True ]
A few suggestions for the test, feel free to ignore
@@ -18,7 +18,7 @@ class CompactBlocksConnectionTest(BitcoinTestFramework):
def peer_info(self, from_node, to_node):
"""Query from_node for its getpeerinfo about to_node."""
for peerinfo in self.nodes[from_node].getpeerinfo():
- if "(testnode%i)" % to_node in peerinfo['subver']:
+ if f"(testnode{to_node})" in peerinfo['subver']:
return peerinfo
return None
@@ -73,14 +73,13 @@ class CompactBlocksConnectionTest(BitcoinTestFramework):
# Now relay one block through peer 2 (outbound from node 1), so it should take HB status
# from one of the inbounds.
status = self.relay_block_through(2)
- assert_equal(status[0], True)
- assert_equal(sum(status), 3)
+ assert_equal(status, [True, False, True, True])
# Now relay again through nodes 3,4,5. Since 2 is outbound, it should remain HB.
for nodeid in range(3, 6):
status = self.relay_block_through(nodeid)
- assert status[0]
- assert status[nodeid - 2]
+ assert_equal(status[0], True)
+ assert_equal(status[nodeid - 2], True)
assert_equal(sum(status), 3)
# Reconnect peer 2, and retry. Now the three inbounds should be HB again.
@@ -88,8 +87,8 @@ class CompactBlocksConnectionTest(BitcoinTestFramework):
self.connect_nodes(1, 2)
for nodeid in range(3, 6):
status = self.relay_block_through(nodeid)
- assert not status[0]
- assert status[nodeid - 2]
+ assert_equal(status[0], False)
+ assert_equal(status[nodeid - 2], True)
assert_equal(status, [False, True, True, True])(Very nice test.)
(Out of curiousity, do people see inbounds selected as HB peers? Perhaps my peer topology has much better outbounds than inbounds, or not enough inbounds, but it's rare for even one inbound to provide a block and be selected for HB mode, much less three. Outbounds get the nod.)
|
My node at bitcoin.sipa.be currently has all 3 HB connections on inbounds (it has 250 connections). |
|
@sipa thanks -- good to hear about different peer topologies. Am probably restarting too often (for testing pulls) to see anything close to that many inbounds. |
|
ACK 30aee2d Reviewed code and checked that the test fails on master but passes with this PR. |
|
Code ACK 30aee2d Our last oubound HB compact block peer could still be evicted under our CHAIN_SYNC_TIMEOUT/EXTRA_PEER_CHECK_INTERVAL logics if it appears slow on block-delivery. Hitting this eviction on our side would require from an attacker to control our peer's block-relay, assuming this peer's implementation has the same mitigations in place against adversarial peers, it sounds hard to achieve. |
|
Concept ACK. I agree with protecting last outbound HB peer. |
30aee2d tests: Add test for compact block HB selection (Pieter Wuille) 6efbcec Protect last outbound HB compact block peer (Suhas Daftuar) Pull request description: If all our high-bandwidth compact block serving peers (BIP 152) stall block download, then we can be denied a block for (potentially) a long time. As inbound connections are much more likely to be adversarial than outbound connections, mitigate this risk by never removing our last outbound HB peer if it would be replaced by an inbound. ACKs for top commit: achow101: ACK 30aee2d ariard: Code ACK 30aee2d jonatack: ACK 30aee2d Tree-SHA512: 5c6c9326e3667b97e0864c371ae2174d2be9054dad479f4366127b9cd3ac60ffa01ec9707b16ef29cac122db6916cf56fd9985733390017134ace483278921d5
If all our high-bandwidth compact block serving peers (BIP 152) stall block download, then we can be denied a block for (potentially) a long time. As inbound connections are much more likely to be adversarial than outbound connections, mitigate this risk by never removing our last outbound HB peer if it would be replaced by an inbound. Github-Pull: bitcoin#22147 Rebased-From: 6efbcec
Github-Pull: bitcoin#22147 Rebased-From: 30aee2d
If all our high-bandwidth compact block serving peers (BIP 152) stall block
download, then we can be denied a block for (potentially) a long time. As
inbound connections are much more likely to be adversarial than outbound
connections, mitigate this risk by never removing our last outbound HB peer if
it would be replaced by an inbound.