Skip to content
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

peer: Do not send inventory before version #3479

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

jrick
Copy link
Member

@jrick jrick commented Feb 27, 2025

For inbound peers, their version message is read, and the version is marked as
known, before our local peer ever publishes the version message. This could
result in a protocol messaging race where other messages (and in particular,
notifying inventory) may be published prior to our version ever being sent.

Rebased over #3478.

The access guarded by an atomic int32 was incorrect.  For example, access to
the p.conn could be performed as long as p.connected was non-zero, but
p.connected would be incremented before p.conn was ever assigned by
AssociateConnection.

While here, also add missing mutex locking protecting timeConnected and na.
For inbound peers, their version message is read, and the version is marked as
known, before our local peer ever publishes the version message.  This could
result in a protocol messaging race where other messages (and in particular,
notifying inventory) may be published prior to our version ever being sent.

While here, remove unused versionSent field from the peer.  The new
HandshakeCompleted tracking is strictly better (while it is allowed to send a
peer messages before we have read their version, whether those messages follow
the negotiation protocol version won't be known until after the fact).
@davecgh davecgh added this to the 2.1.0 milestone Mar 4, 2025
Copy link
Member

@davecgh davecgh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks fine, but I wonder if it should be more general since I think there are other cases that the same thing could really apply to. For example, there is the QueueInventoryImmediate path and, well, technically, anything via QueueMessage too which could theoretically be sent prior to the version message.

In practice, at least for dcrd, the remote peer will disconnect the local peer if it sends anything before the version message. I'm not sure if SPV clients like wallet are also doing that, but they should be, because it is technically required by the protocol.

Given we don't see a ton of disconnections, it's obviously pretty rare that anything is sent before the version, but it does seem prudent to avoid potentially violating the protocol resulting in a disconnect.

Perhaps instead of checking if the handshake has completed only in the one spot upon the receive on p.outputInvChan which drops the announcement altogether, it would be better to do something like the following:

  1. Unconditionally add the announcement to invSendQueue in case iv := <-p.outputInvChan:
  2. Add another case to the switch at the top of case <-trickleTimer.C: to reset the trickleTimer and ignore the event when the handshake isn't complete yet. This will leave any pending announcements in the queue for the next tick.
  3. Add logic in case msg := <-p.outputQueue: (or perhaps in queuePacket) that appends the message to the local queue when the handshake hasn't completed yet (instead of directly sending to the output channel) except when it is the version message itself (aka of type *wire.MsgVersion). Otherwise, once the handshake has completed, the logic stays the same as what it is now. This would have the effect of forcing the version message to be sent first even if some other goroutine tries to send another type of message first.

Finally, it doesn't matter, but maybe calling it HandshakeDone would be better since it's a bit shorter and this will be a new method that is part of the public API.

@jrick
Copy link
Member Author

jrick commented Mar 5, 2025

I'm not sure if SPV clients like wallet are also doing that, but they should be, because it is technically required by the protocol.

Yes, dcrwallet does of course. https://github.com/decred/dcrwallet/blob/76d500f80440a5ded40b11157793372e5f0d1e28/p2p/peering.go#L596

I'll take a look at the other places where we send messages and similarly ensure that both version messages have been sent and received.

@davecgh
Copy link
Member

davecgh commented Mar 5, 2025

Oh, I should've also noted that I like the overall idea. Detecting a fully completed handshake as opposed to only half of it being completed is much cleaner and less error prone. Not to mention, it's easier to reason about too, since it's a completed exchange instead of a half-open quasi state.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants