-
Notifications
You must be signed in to change notification settings - Fork 35.6k
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
Disconnect Peers for Duplicate Invalid blocks. #11446
Conversation
src/net_processing.cpp
Outdated
// Ask this peer for the block headers of all of the blocks we know are invalid | ||
// If we receive headers messages for these invalid blocks, we will ban them. | ||
std::vector<uint256> invalid_block_hashes; | ||
for (const std::pair<const uint256, CBlockIndex*>& item : mapBlockIndex) |
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.
Should probably wait until VERACK for this.
Also, walking mapBlockIndex seems a little heavy here, no?
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.
Besides walking mapBlockIndex
, what do you suggest? I was considering making something to just store the hashes of the invalid blocks instead.
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.
Yea, I'd say the fork-points need to be cached. Unsure if it'd be best to just do it here and be done with it, or maintain something in validation.cpp that (for ex) getchaintips could also use.
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.
Well it would have to be something that is loaded/generated on startup too.
3ddf32e
to
eed5dad
Compare
Is it really necessary to request as many headers as possible on each bad chain? What about something like sending a request for non-deterministic headers from m to n, where |
eed5dad
to
ad944f3
Compare
src/net_processing.cpp
Outdated
// Send a getheaders message with the hash of the invalid block to all peers. | ||
// Those who respond to this message will be banned. | ||
connman->ForEachNode([cmpctblock, connman](CNode* pnode) { | ||
connman->PushMessage(pnode, CNetMsgMaker(pnode->GetSendVersion()).Make(NetMsgType::GETHEADERS, CBlockLocator({cmpctblock.header.GetHash()}), uint256())); |
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.
Feedback loop here.
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.
how so?
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.
Node sends you bad header, you request that header from all nodes (including the one that just sent it). Same node replies with same header, you request it again from all nodes... repeat until they get banned.
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.
Isn't the node that sent it to you in the first place already banned by the time this runs?
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, not necessarily. See "high-hash" for example. But even still, the ban/disconnect should not be assumed. What if they're whitelisted?
src/net_processing.cpp
Outdated
// Send a getheaders message with the hash of the invalid block to all peers. | ||
// Those who respond to this message will be banned. | ||
connman->ForEachNode([header_hashes, connman](CNode* pnode) { | ||
connman->PushMessage(pnode, CNetMsgMaker(pnode->GetSendVersion()).Make(NetMsgType::GETHEADERS, CBlockLocator(header_hashes), uint256())); |
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.
feedback loop here too :)
No.
I'm not sure if that is useful or necessary. How do you know the difference between a node not having a block, rejected the block, or is just slow to respond? |
Cautious concept ack on doing this once, sometimes, post-handshake, but I think this needs to be thought through a bit more.
|
I don't understand why this is generating any headers requests at all, we already will synchronize all headers on the peers best chain (obviously not transferring any we already know about). We just need to see that they connect to an invalid block and disconnect. |
I don't really like this policy, I wouldn't go as far as a Concept NACK though because I think it can be adjusted to do what you want. I think returning an invalid block when requested is a weak indicator of peer correctness overall. Nodes to assuming "you get what you ask for" and feeding you invalid data -- if you ask for it and they have it -- seems like a semi-sane behavior. (e.g., the bitcoin-wiki says "Keep in mind that some clients may provide headers of blocks which are invalid if the block locator object contains a hash on the invalid branch. " of getheaders) Of course, this isn't quite what Bitcoin Core assumes currently, but introducing more dependency on it seems like questionable design. A safer alternative design would be to do a getheaders on the highest known valid previous header on that chain. Then, see if the invalid block is relayed. That way we're only ever querying for valid data. For example, if we have a block A and A is valid, and then B comes out with parent A, but B is invalid, doing GetHeaders on A and disconnecting if B is returned is safer. |
Interleaving error (was taking my sweet time writing it up), but what @gmaxwell said should cover my suggestion as well. |
I doesn't. It does this for each invalid block.
I suppose it doesn't do that much after the handshake. The part that makes a difference with peers that have been connected to a while is the banning on duplicate invalid headers.
Even with synchronizing all headers, I'm not sure that we necessarily know what a peer's best chain is. I thought that with compact blocks blocks can be relayed before a node has accepted it and made it part of its best chain. So we may not necessarily know whether they actually accepted an invalid block even though it was relayed to us. Also, I don't think we necessarily know the headers chain of a newly connected peer.
Right, that seems like a better way of checking. I think then it is harder to tell whether you are actually syncing the blockchain and whether you are testing the peer for correctness. Edit: I gave this a bit more thought. We may want to directly request an invalid block because the invalid block may not be part of their best chain yet they still could have accepted it as valid. |
I don't agree that what you describe is a sane behavior and it is not a behavior of the Bitcoin protocol in the past. It's prudent and important to that we're able to punt peers that send us invalid data. Usually when you ask for something you do not know it will be invalid. If a peer will send you invalid data because you asked for it, then they'll send you invalid data when you were not expecting it either. :) In general, being willing to forward something we know is invalid is begging for a transitive fault, cases where you send something invalid to a node, it forwards it on to a third party, and the third party really can't handle it and punts them. |
Yes, well and I think it can actually be done with basically no extra network traffic, just responding differently to messages we already get (punt a peer if they give you a header whos parent is something we've marked invalid). |
We already do that. I'm more concerned about how you get a peer to tell you what blocks are in its best chain without having to wait for a new block to be found. |
After giving this some more thought and sleeping on it, I have a slightly different propsal:
@gmaxwell @theuni @JeremyRubin What do you think? |
For peers that are sending us HB mode compact blocks we can only kick things off for a subset of invalidity reasons. |
ad944f3
to
eb1aa2c
Compare
I have updated this to be as we discussed in the IRC meeting. If we see a block header for a block we already know is invalid, we disconnect from the node that sent it. The only exception are our HB CB nodes. |
Going to test this. |
src/net_processing.cpp
Outdated
@@ -1970,6 +1970,8 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr | |||
if (nDoS > 0) { | |||
LOCK(cs_main); | |||
Misbehaving(pfrom->GetId(), nDoS); | |||
} else if (std::find(lNodesAnnouncingHeaderAndIDs.begin(), lNodesAnnouncingHeaderAndIDs.end(), pfrom->GetId()) == lNodesAnnouncingHeaderAndIDs.end()) { |
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.
Do we not want to disconnect if ban points were assigned?
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.
Most of the time when ban points are assigned, 100 points are assigned and the peer is banned and disconnected. However there are a few cases (e.g. https://github.com/bitcoin/bitcoin/blob/master/src/validation.cpp#L3060) where ban points are assigned but not 100 of them, and we don't necessarily want to disconnect on those.
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.
Mmm, that's not at all obvious. For now at least, ban points are rather arbitrary. Not completely, of course, but I don't think their values can be relied on to convey any particular meaning. Besides that, they could've already gotten some ban points for some other message.
Anyway, Misbehaving() already does what you've described. So this actually seems backwards. Now, they're disconnected immediately if no points were assigned, but giving 10 points keeps them connected...
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.
Do you think we should just disconnect always when something is invalid?
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.
This is racy. There is no way to check if a peer might have been in compact-fast-relay mode when you receive a compact block header.
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.
Would it be better to just always disconnect on an invalid block, regardless?
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.
I think we should just ignore this case. My first reaction was to suggest you do a getheaders request, but then I went down a rabbit hole trying to decide whether the peer is required to respond with the header if they decided the block was valid (it turns out BIP 152 explicitly answers this question with a no). Per BIP 152, I believe the way to "correctly" handle this would be to reply with a getdata for the same compact block that you just received, and then handle that differently (!) but that seems absolutely crazy, and not worth it given that compact blocks are only allowed to build on the best chain so we should disconnect them immediately when they get the next block (see bitcoin/bips#601)
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.
I think we should just ignore this case.
Do you mean just disconnect regardless or just don't do anything here and let invalid blocks through here?
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.
Ok, I just removed this check and disconnect. After reviewing BIP 152 again, the low bandwidth peers should already be disconnected by the disconnection in handling the headers
message so our high bandwidth peers are still unaffected.
src/net_processing.cpp
Outdated
@@ -2288,6 +2290,8 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr | |||
if (nDoS > 0) { | |||
LOCK(cs_main); | |||
Misbehaving(pfrom->GetId(), nDoS); | |||
} else { | |||
pfrom->fDisconnect = true; |
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.
Same here.
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.
Removed the else
statement so it will always disconnect on an invalid block header.
eb1aa2c
to
3943a00
Compare
If we receive a block header for a block that we already know is invalid, disconnect the peer that sent it to us unless the peer sent it with a compact block
3943a00
to
23b7af0
Compare
utACK 23b7af0 |
I'm not sure how I feel about this - the current disconnection/ban logic on invalid headers is proabbly too conservative, but it helps in the case of soft forks where there are occasional invalid blocks relayed preventing us from banning all of our un-upgraded peers. Maybe we should ratchet up the agressiveness here, but only for outbound peers? I think just that change would be sufficient for 0.15.0.2, though I'd really like to see some refactoring of the CValidationState stuff happen so ProcessNewBlockHeaders could inform us of more granular error messages for net_processing to use here. |
I think there's a small race condition in the CMPCTBLOCK handler -- there's a circumstance where we would revert to headers processing, and we release cs_main before doing so. If the block header is learned to be invalid in between initial processing and re-processing, then I think this patch would disconnect an HB compact block peer inappropriately. I'm not really sure what the best remedy is; if we moved headers processing to its own function in net_processing, then I'd say we could just pass in whether this is coming from a compact block or not. Not sure that suggestion makes sense as long as it's living in ProcessMessages(), though. |
Why would it revert to headers processing? |
@achow101 See bitcoin/src/net_processing.cpp Line 2130 in 57ee739
|
superseded by #11568 |
37886d5 Disconnect outbound peers relaying invalid headers (Suhas Daftuar) 4637f18 moveonly: factor out headers processing into separate function (Suhas Daftuar) Pull request description: Alternate to #11446. Disconnect outbound (non-manual) peers that serve us block headers that are already known to be invalid, but exempt compact block announcements from such disconnects. We restrict disconnection to outbound peers that are using up an outbound connection slot, because we rely on those peers to give us connectivity to the honest network (our inbound peers are not chosen by us and hence could all be from an attacker/sybil). Maintaining connectivity to peers that serve us invalid headers is sometimes desirable, eg after a soft-fork, to protect unupgraded software from being partitioned off the honest network, so we prefer to only disconnect when necessary. Compact block announcements are exempted from this logic to comply with BIP 152, which explicitly permits nodes to relay compact blocks before fully validating them. Tree-SHA512: 3ea88e4ccc1184f292a85b17f800d401d2c3806fefc7ad5429d05d6872c53acfa5751e3df83ce6b9c0060ab289511ed70ae1323d140ccc5b12e3c8da6de49936
37886d5 Disconnect outbound peers relaying invalid headers (Suhas Daftuar) 4637f18 moveonly: factor out headers processing into separate function (Suhas Daftuar) Pull request description: Alternate to bitcoin#11446. Disconnect outbound (non-manual) peers that serve us block headers that are already known to be invalid, but exempt compact block announcements from such disconnects. We restrict disconnection to outbound peers that are using up an outbound connection slot, because we rely on those peers to give us connectivity to the honest network (our inbound peers are not chosen by us and hence could all be from an attacker/sybil). Maintaining connectivity to peers that serve us invalid headers is sometimes desirable, eg after a soft-fork, to protect unupgraded software from being partitioned off the honest network, so we prefer to only disconnect when necessary. Compact block announcements are exempted from this logic to comply with BIP 152, which explicitly permits nodes to relay compact blocks before fully validating them. Tree-SHA512: 3ea88e4ccc1184f292a85b17f800d401d2c3806fefc7ad5429d05d6872c53acfa5751e3df83ce6b9c0060ab289511ed70ae1323d140ccc5b12e3c8da6de49936
37886d5 Disconnect outbound peers relaying invalid headers (Suhas Daftuar) 4637f18 moveonly: factor out headers processing into separate function (Suhas Daftuar) Pull request description: Alternate to bitcoin#11446. Disconnect outbound (non-manual) peers that serve us block headers that are already known to be invalid, but exempt compact block announcements from such disconnects. We restrict disconnection to outbound peers that are using up an outbound connection slot, because we rely on those peers to give us connectivity to the honest network (our inbound peers are not chosen by us and hence could all be from an attacker/sybil). Maintaining connectivity to peers that serve us invalid headers is sometimes desirable, eg after a soft-fork, to protect unupgraded software from being partitioned off the honest network, so we prefer to only disconnect when necessary. Compact block announcements are exempted from this logic to comply with BIP 152, which explicitly permits nodes to relay compact blocks before fully validating them. Tree-SHA512: 3ea88e4ccc1184f292a85b17f800d401d2c3806fefc7ad5429d05d6872c53acfa5751e3df83ce6b9c0060ab289511ed70ae1323d140ccc5b12e3c8da6de49936
37886d5 Disconnect outbound peers relaying invalid headers (Suhas Daftuar) 4637f18 moveonly: factor out headers processing into separate function (Suhas Daftuar) Pull request description: Alternate to bitcoin#11446. Disconnect outbound (non-manual) peers that serve us block headers that are already known to be invalid, but exempt compact block announcements from such disconnects. We restrict disconnection to outbound peers that are using up an outbound connection slot, because we rely on those peers to give us connectivity to the honest network (our inbound peers are not chosen by us and hence could all be from an attacker/sybil). Maintaining connectivity to peers that serve us invalid headers is sometimes desirable, eg after a soft-fork, to protect unupgraded software from being partitioned off the honest network, so we prefer to only disconnect when necessary. Compact block announcements are exempted from this logic to comply with BIP 152, which explicitly permits nodes to relay compact blocks before fully validating them. Tree-SHA512: 3ea88e4ccc1184f292a85b17f800d401d2c3806fefc7ad5429d05d6872c53acfa5751e3df83ce6b9c0060ab289511ed70ae1323d140ccc5b12e3c8da6de49936
I have updated this to be as we discussed in the IRC meeting. If we see a block header for a block we already know is invalid, we disconnect from the node that sent it. The only exception are our HB CB nodes.
This is an implementation of the bad block interrogation idea that @gmaxwell has talked about.To ensure that the nodes we are connected to are following the same consens rules as we are, ban all nodes that relay us an invalid block or block header instead of only banning the first peer.Then when we receive an invalid block, we want to check that all of our peers also found that block to be invalid, so we ask all of our peers for the header of that invalid block. If they found it valid, the header will be relayed and we will then ban them. Otherwise we keep the connection.To ensure that our newly connected nodes are also following the same consensus rules, we ask them for the headers of blocks that we know are invalid when they connect after the version handshake. If they respond with a header, then we will ban them.This still needs tests, but I'm not sure how we should do that. If you have any suggestions, I'm all ears.Note, I know the implementation is slightly wrong, still working on it.