Network: Handshake mini-protocol
Mini-protocol number: 0
The Handshake mini-protocol is used to establish a connection and negotiate protocol versions and parameters between the initiator (client) and responder (server). There are two versions, one for node-to-node (N2N) and one for node-to-client (N2C), which differ only in the parameters.
State machine
stateDiagram [*] --> StPropose StPropose --> StConfirm: MsgProposeVersions StConfirm --> [*]: MsgAcceptVersion StConfirm --> [*]: MsgReplyVersion StConfirm --> [*]: MsgRefuse direction LR classDef initiator color:#080 classDef responder color:#008, text-decoration: underline class StPropose initiator class StConfirm responder
State agencies
State | Agency |
---|---|
StPropose | Initiator |
StConfirm | Responder |
State transitions
From state | Message | Parameters | to state |
---|---|---|---|
StPropose | MsgProposeVersions | versionTable | StConfirm |
StConfirm | MsgReplyVersion | versionTable | End |
StConfirm | MsgAcceptVersion | (versionNumber, versionData) | End |
StConfirm | MsgRefuse | reason | End |
TCP simultaneous open
In the rare case when both sides try to connect to each other at the same time,
it’s possible to get a “TCP simultaneous open” where you end up with a single
socket, not two. In this case, both sides will think they are the initiator
so will send a MsgProposeVersions
, and this protocol handles this by treating
the received one in StConfirm
state as a MsgReplyVersion
, which has the same
CBOR encoding.
warning
Why does the message need to change name? The state machine would be
valid with an StConfirm -- MsgProposeVersions --> End
arc.
note
Also, is the negotiation always deemed successful in this case? What if one side can’t accept the other’s version? (there is talk of resetting the connection)
warning
MsgReplyVersion
is no longer mentioned in the CDDL - is this therefore
out of date?
Messages
The MsgProposeVersions
message is sent by the initiator to propose a
set of possible versions and protocol parameters. versionTable
is a map
of version numbers to associated parameters - bear in mind that different
versions may have different sets of parameters. The version number keys
must be unique and in ascending order.
note
This seems an arbitrary constraint which could easily be avoided by implementations, although deterministic CBOR encoding would enforce it.
The MsgAcceptVersion
message is returned by the responder to confirm
a mutually acceptable version and set of parameters.
The MsgRefuse
message is returned by the responder to indicate there is
no acceptable version match, or other reason. If it is a version mismatch
it returns a set of version numbers that it could have accepted.
warning
The content of MsgRefuse
is inconsistent between the paper and CDDL -
check the above.
Message size limits
Because the Handshake protocol operates before the multiplexer is fully set up, the messages must not be split into segments, and this imposes a size limit of 5760 bytes.
warning
This seems like a protocol level mix, and since the negotiated parameters don’t seem to affect the mux config (and could be changed dynamically even if they did), it’s not clear why this constraint is needed.
note
Why 5076 when the mux protocol can handle 65535? Implementation detail?
Timeouts
The maximum time to wait for a message in StPropose
(for the responder)
or StConfirm
(for the initiator) is 10 seconds. After this the connection
should be torn down.
CDDL
Here’s the CDDL for the latest node-to-node handshake protocol:
;
; NodeToNode Handshake (>=v13)
;
handshakeMessage
= msgProposeVersions
/ msgAcceptVersion
/ msgRefuse
/ msgQueryReply
msgProposeVersions = [0, versionTable]
msgAcceptVersion = [1, versionNumber, nodeToNodeVersionData]
msgRefuse = [2, refuseReason]
msgQueryReply = [3, versionTable]
versionTable = { * versionNumber => nodeToNodeVersionData }
versionNumber = 13 / 14
nodeToNodeVersionData = [ networkMagic, initiatorOnlyDiffusionMode, peerSharing, query ]
; range between 0 and 0xffffffff
networkMagic = 0..4294967295
initiatorOnlyDiffusionMode = bool
; range between 0 and 1
peerSharing = 0..1
query = bool
refuseReason
= refuseReasonVersionMismatch
/ refuseReasonHandshakeDecodeError
/ refuseReasonRefused
refuseReasonVersionMismatch = [0, [ *versionNumber ] ]
refuseReasonHandshakeDecodeError = [1, versionNumber, tstr]
refuseReasonRefused = [2, versionNumber, tstr]
And the node-to-client version:
;
; NodeToClient Handshake
;
handshakeMessage
= msgProposeVersions
/ msgAcceptVersion
/ msgRefuse
/ msgQueryReply
msgProposeVersions = [0, versionTable]
msgAcceptVersion = [1, versionNumber, nodeToClientVersionData]
msgRefuse = [2, refuseReason]
msgQueryReply = [3, versionTable]
; Entries must be sorted by version number. For testing, this is handled in `handshakeFix`.
versionTable = { * versionNumber => nodeToClientVersionData }
; as of version 2 (which is no longer supported) we set 15th bit to 1
; 16 / 17 / 18 / 19
versionNumber = 32784 / 32785 / 32786 / 32787
; As of version 15 and higher
nodeToClientVersionData = [networkMagic, query]
networkMagic = uint
query = bool
refuseReason
= refuseReasonVersionMismatch
/ refuseReasonHandshakeDecodeError
/ refuseReasonRefused
refuseReasonVersionMismatch = [0, [ *versionNumber ] ]
refuseReasonHandshakeDecodeError = [1, versionNumber, tstr]
refuseReasonRefused = [2, versionNumber, tstr]