Edit: You seem to assume that the the handshake from gocardano/go-cardano-client
and the one described in node.proto
somehow are related to the same implementation. Actually, I don't think they do.
The TCP-based handshake follows the Shelley protocol specs and sends a payload with the encoded versionTable
. The gRPC-based HandshakeRequest
instead is, as you also considered, just a nonce. There's nothing in the proto schema that hints to the Shelley protocol. The comments on the Nonce
field also say that quite explicitly: "Nonce for the server to authenticate its node ID with."
So it would be a bit strange to assume that this nonce and the versionTable
payload have anything in common at all.
Edit 2: In addition, it seems the "Jormungandr" rust node implementation does not support Shelley at all, so when you say you can't connect to the nodes in the relay topology, I think you shouldn't look for answers in the Jormungandr repository. Instead, I think the relays run the Haskell implementations of the Ouroboros network.
Now as for why you can't connect, the go-cardano
client panics on some unchecked type assertions, because after the QueryTip
Shelley message chainSyncBlocks.RequestNext
, the relay servers respond with a different mini-protocol altogether, transactionSubmission.msgRequestTxIds
as shown by running the client with TCP and tracing the messages:
MiniProtocol: 4 / MessageMode: 1 / f1bb7f80800400058400f50003
Array: [4]
PositiveInteger8(0)
False
PositiveInteger8(0)
PositiveInteger8(3)
You'll also have the same result when sending a sync chain request with MiniProtocol 2
(ChainSyncHeaders
). I checked the Shelley protocol specs but couldn't find an explicit indication about why the server would switch protocols... Unfortunately I'm not familiar enough with Haskell to gain further insight from the Ouroboros sources.
In the unexpected case that the nonce in the proto HandshakeRequest
is indeed related to the Shelley protocol, its content might be the CBOR array in your linked Cardano client (speculations follows):
arr := cbor.NewArray()
arr.Add(cbor.NewPositiveInteger8(handshakeMessagePropose))
versionTable := cbor.NewMap()
arr.Add(versionTable)
versionTable.Add(...)
versionTable.Add(...)
versionTable.Add(...)
return []cbor.DataItem{arr}
By inspecting the client where the handshake request is used, we can see:
messageResponse, err := c.queryNode(multiplex.MiniProtocolIDMuxControl, handshakeRequest())
and then in queryNode
:
sdu := multiplex.NewServiceDataUnit(miniProtocol, multiplex.MessageModeInitiator, dataItems)
...
c.socket.Write(sdu.Bytes())
The sdu.Bytes()
method serializes the entire payload, in particular:
// EncodeList return CBOR representation for each item in the list
func EncodeList(list []DataItem) []byte {
result := []byte{}
for _, item := range list {
result = append(result, item.EncodeCBOR()...)
}
return result
}
The EncodeCBOR()
method is implemented by both the Array
and the Map
used in the handshakeRequest() []cbor.DataItem
function. Watch out that the handshake function returns a slice []cbor.DataItem
which contains one Array
item which contains (as documented) the handshakeMessagePropose
and the versionTable
map.
If you carefully follow how the serialization proceeds, you'll eventually obtain the breakdown of the byte array — hereafter in decimal:
[130 0 163 1 26 45 150 74 9 25 128 2 26 45 150 74 9 25 128 3 26 45 150 74 9]
Where:
- 130 is the array data item prefix
- 0 is the
handshakeMessagePropose
- 163 is the map data item prefix
- and the subsequent bytes are the
versionTable
map
It is 25 bytes in total. At this point, I don't know if the multiplex wrapper built in queryNode
function is part of the nonce or not. With the full wrapper, the length of the serialized byte array goes up to 33. So excluding some control bits or whatnot, this might be what you're supposed to write into the HandshakeRequest.Nonce
.