Skip to content

Commit 82d0663

Browse files
authored
Merge pull request #115 from cloudstruct/feature/split-mini-protocols-initiator-responder
feat: split handshake protocol into client/server
2 parents b045b0c + 84f7c80 commit 82d0663

File tree

5 files changed

+202
-117
lines changed

5 files changed

+202
-117
lines changed

ouroboros.go

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ type Ouroboros struct {
1919
networkMagic uint32
2020
server bool
2121
useNodeToNodeProto bool
22-
handshakeComplete bool
2322
muxer *muxer.Muxer
2423
ErrorChan chan error
2524
protoErrorChan chan error
@@ -124,29 +123,34 @@ func (o *Ouroboros) setupConnection() error {
124123
protoOptions.Role = protocol.ProtocolRoleClient
125124
}
126125
// Perform handshake
126+
handshakeFinishedChan := make(chan interface{})
127+
var handshakeVersion uint16
128+
var handshakeFullDuplex bool
127129
handshakeConfig := &handshake.Config{
128130
ProtocolVersions: protoVersions,
129131
NetworkMagic: o.networkMagic,
130132
ClientFullDuplex: o.fullDuplex,
133+
FinishedFunc: func(version uint16, fullDuplex bool) error {
134+
handshakeVersion = version
135+
handshakeFullDuplex = fullDuplex
136+
close(handshakeFinishedChan)
137+
return nil
138+
},
131139
}
132140
o.Handshake = handshake.New(protoOptions, handshakeConfig)
133-
o.Handshake.Start()
134-
// TODO: figure out better way to signify automatic handshaking and returning the chosen version
135-
if !o.server {
136-
err := o.Handshake.ProposeVersions()
137-
if err != nil {
138-
return err
139-
}
141+
if o.server {
142+
o.Handshake.Server.Start()
143+
} else {
144+
o.Handshake.Client.Start()
140145
}
141146
// Wait for handshake completion or error
142147
select {
143148
case err := <-o.protoErrorChan:
144149
return err
145-
case finished := <-o.Handshake.Finished:
146-
o.handshakeComplete = finished
150+
case <-handshakeFinishedChan:
147151
}
148152
// Provide the negotiated protocol version to the various mini-protocols
149-
protoOptions.Version = o.Handshake.Version
153+
protoOptions.Version = handshakeVersion
150154
// Drop bit used to signify NtC protocol versions
151155
if protoOptions.Version > PROTOCOL_VERSION_NTC_FLAG {
152156
protoOptions.Version = protoOptions.Version - PROTOCOL_VERSION_NTC_FLAG
@@ -160,7 +164,7 @@ func (o *Ouroboros) setupConnection() error {
160164
}()
161165
// Configure the relevant mini-protocols
162166
if o.useNodeToNodeProto {
163-
versionNtN := GetProtocolVersionNtN(o.Handshake.Version)
167+
versionNtN := GetProtocolVersionNtN(handshakeVersion)
164168
protoOptions.Mode = protocol.ProtocolModeNodeToNode
165169
o.ChainSync = chainsync.New(protoOptions, o.chainSyncConfig)
166170
o.BlockFetch = blockfetch.New(protoOptions, o.blockFetchConfig)
@@ -172,7 +176,7 @@ func (o *Ouroboros) setupConnection() error {
172176
}
173177
}
174178
} else {
175-
versionNtC := GetProtocolVersionNtC(o.Handshake.Version)
179+
versionNtC := GetProtocolVersionNtC(handshakeVersion)
176180
protoOptions.Mode = protocol.ProtocolModeNodeToClient
177181
o.ChainSync = chainsync.New(protoOptions, o.chainSyncConfig)
178182
o.LocalTxSubmission = localtxsubmission.New(protoOptions, o.localTxSubmissionConfig)
@@ -182,7 +186,7 @@ func (o *Ouroboros) setupConnection() error {
182186
}
183187
// Start muxer
184188
diffusionMode := muxer.DiffusionModeInitiator
185-
if o.Handshake.FullDuplex {
189+
if handshakeFullDuplex {
186190
diffusionMode = muxer.DiffusionModeInitiatorAndResponder
187191
} else if o.server {
188192
diffusionMode = muxer.DiffusionModeResponder

protocol/handshake/client.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package handshake
2+
3+
import (
4+
"fmt"
5+
"github.com/cloudstruct/go-ouroboros-network/protocol"
6+
)
7+
8+
type Client struct {
9+
*protocol.Protocol
10+
config *Config
11+
}
12+
13+
func NewClient(protoOptions protocol.ProtocolOptions, cfg *Config) *Client {
14+
c := &Client{
15+
config: cfg,
16+
}
17+
protoConfig := protocol.ProtocolConfig{
18+
Name: PROTOCOL_NAME,
19+
ProtocolId: PROTOCOL_ID,
20+
Muxer: protoOptions.Muxer,
21+
ErrorChan: protoOptions.ErrorChan,
22+
Mode: protoOptions.Mode,
23+
Role: protocol.ProtocolRoleClient,
24+
MessageHandlerFunc: c.handleMessage,
25+
MessageFromCborFunc: NewMsgFromCbor,
26+
StateMap: StateMap,
27+
InitialState: STATE_PROPOSE,
28+
}
29+
c.Protocol = protocol.New(protoConfig)
30+
return c
31+
}
32+
33+
func (c *Client) Start() {
34+
c.Protocol.Start()
35+
// Send our ProposeVersions message
36+
versionMap := make(map[uint16]interface{})
37+
diffusionMode := DIFFUSION_MODE_INITIATOR_ONLY
38+
if c.config.ClientFullDuplex {
39+
diffusionMode = DIFFUSION_MODE_INITIATOR_AND_RESPONDER
40+
}
41+
for _, version := range c.config.ProtocolVersions {
42+
if c.Mode() == protocol.ProtocolModeNodeToNode {
43+
versionMap[version] = []interface{}{c.config.NetworkMagic, diffusionMode}
44+
} else {
45+
versionMap[version] = c.config.NetworkMagic
46+
}
47+
}
48+
msg := NewMsgProposeVersions(versionMap)
49+
_ = c.SendMessage(msg)
50+
}
51+
52+
func (c *Client) handleMessage(msg protocol.Message, isResponse bool) error {
53+
var err error
54+
switch msg.Type() {
55+
case MESSAGE_TYPE_ACCEPT_VERSION:
56+
err = c.handleAcceptVersion(msg)
57+
case MESSAGE_TYPE_REFUSE:
58+
err = c.handleRefuse(msg)
59+
default:
60+
err = fmt.Errorf("%s: received unexpected message type %d", PROTOCOL_NAME, msg.Type())
61+
}
62+
return err
63+
}
64+
65+
func (c *Client) handleAcceptVersion(msgGeneric protocol.Message) error {
66+
if c.config.FinishedFunc == nil {
67+
return fmt.Errorf("received handshake AcceptVersion message but no callback function is defined")
68+
}
69+
msg := msgGeneric.(*MsgAcceptVersion)
70+
fullDuplex := false
71+
versionData := msg.VersionData.([]interface{})
72+
//nolint:gosimple
73+
if versionData[1].(bool) == DIFFUSION_MODE_INITIATOR_AND_RESPONDER {
74+
fullDuplex = true
75+
}
76+
return c.config.FinishedFunc(msg.Version, fullDuplex)
77+
}
78+
79+
func (c *Client) handleRefuse(msgGeneric protocol.Message) error {
80+
msg := msgGeneric.(*MsgRefuse)
81+
var err error
82+
switch msg.Reason[0].(uint64) {
83+
case REFUSE_REASON_VERSION_MISMATCH:
84+
err = fmt.Errorf("%s: version mismatch", PROTOCOL_NAME)
85+
case REFUSE_REASON_DECODE_ERROR:
86+
err = fmt.Errorf("%s: decode error: %s", PROTOCOL_NAME, msg.Reason[2].(string))
87+
case REFUSE_REASON_REFUSED:
88+
err = fmt.Errorf("%s: refused: %s", PROTOCOL_NAME, msg.Reason[2].(string))
89+
}
90+
return err
91+
}

protocol/handshake/handshake.go

Lines changed: 10 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package handshake
22

33
import (
4-
"fmt"
54
"github.com/cloudstruct/go-ouroboros-network/protocol"
65
)
76

@@ -48,59 +47,32 @@ var StateMap = protocol.StateMap{
4847
}
4948

5049
type Handshake struct {
51-
*protocol.Protocol
52-
config *Config
53-
Version uint16
54-
FullDuplex bool
55-
Finished chan bool
50+
Client *Client
51+
Server *Server
5652
}
5753

5854
type Config struct {
5955
ProtocolVersions []uint16
6056
NetworkMagic uint32
6157
ClientFullDuplex bool
58+
FinishedFunc FinishedFunc
6259
}
6360

64-
func New(options protocol.ProtocolOptions, config *Config) *Handshake {
61+
type FinishedFunc func(uint16, bool) error
62+
63+
func New(protoOptions protocol.ProtocolOptions, cfg *Config) *Handshake {
6564
h := &Handshake{
66-
config: config,
67-
Finished: make(chan bool, 1),
68-
}
69-
protoConfig := protocol.ProtocolConfig{
70-
Name: PROTOCOL_NAME,
71-
ProtocolId: PROTOCOL_ID,
72-
Muxer: options.Muxer,
73-
ErrorChan: options.ErrorChan,
74-
Mode: options.Mode,
75-
Role: options.Role,
76-
MessageHandlerFunc: h.handleMessage,
77-
MessageFromCborFunc: NewMsgFromCbor,
78-
StateMap: StateMap,
79-
InitialState: STATE_PROPOSE,
65+
Client: NewClient(protoOptions, cfg),
66+
Server: NewServer(protoOptions, cfg),
8067
}
81-
h.Protocol = protocol.New(protoConfig)
8268
return h
8369
}
8470

71+
/*
8572
func (h *Handshake) Start() {
8673
h.Protocol.Start()
8774
}
8875
89-
func (h *Handshake) handleMessage(msg protocol.Message, isResponse bool) error {
90-
var err error
91-
switch msg.Type() {
92-
case MESSAGE_TYPE_PROPOSE_VERSIONS:
93-
err = h.handleProposeVersions(msg)
94-
case MESSAGE_TYPE_ACCEPT_VERSION:
95-
err = h.handleAcceptVersion(msg)
96-
case MESSAGE_TYPE_REFUSE:
97-
err = h.handleRefuse(msg)
98-
default:
99-
err = fmt.Errorf("%s: received unexpected message type %d", PROTOCOL_NAME, msg.Type())
100-
}
101-
return err
102-
}
103-
10476
func (h *Handshake) ProposeVersions() error {
10577
// Create our request
10678
versionMap := make(map[uint16]interface{})
@@ -118,67 +90,4 @@ func (h *Handshake) ProposeVersions() error {
11890
msg := NewMsgProposeVersions(versionMap)
11991
return h.SendMessage(msg)
12092
}
121-
122-
func (h *Handshake) handleProposeVersions(msgGeneric protocol.Message) error {
123-
msg := msgGeneric.(*MsgProposeVersions)
124-
var highestVersion uint16
125-
var fullDuplex bool
126-
var versionData []interface{}
127-
for proposedVersion := range msg.VersionMap {
128-
if proposedVersion > highestVersion {
129-
for _, allowedVersion := range h.config.ProtocolVersions {
130-
if allowedVersion == proposedVersion {
131-
highestVersion = proposedVersion
132-
versionData = msg.VersionMap[proposedVersion].([]interface{})
133-
//nolint:gosimple
134-
if versionData[1].(bool) == DIFFUSION_MODE_INITIATOR_AND_RESPONDER {
135-
fullDuplex = true
136-
} else {
137-
fullDuplex = false
138-
}
139-
break
140-
}
141-
}
142-
}
143-
}
144-
if highestVersion > 0 {
145-
resp := NewMsgAcceptVersion(highestVersion, versionData)
146-
if err := h.SendMessage(resp); err != nil {
147-
return err
148-
}
149-
h.Version = highestVersion
150-
h.FullDuplex = fullDuplex
151-
h.Finished <- true
152-
return nil
153-
} else {
154-
// TODO: handle failures
155-
// https://github.com/cloudstruct/go-ouroboros-network/issues/32
156-
return fmt.Errorf("handshake failed, but we don't yet support this")
157-
}
158-
}
159-
160-
func (h *Handshake) handleAcceptVersion(msgGeneric protocol.Message) error {
161-
msg := msgGeneric.(*MsgAcceptVersion)
162-
h.Version = msg.Version
163-
versionData := msg.VersionData.([]interface{})
164-
//nolint:gosimple
165-
if versionData[1].(bool) == DIFFUSION_MODE_INITIATOR_AND_RESPONDER {
166-
h.FullDuplex = true
167-
}
168-
h.Finished <- true
169-
return nil
170-
}
171-
172-
func (h *Handshake) handleRefuse(msgGeneric protocol.Message) error {
173-
msg := msgGeneric.(*MsgRefuse)
174-
var err error
175-
switch msg.Reason[0].(uint64) {
176-
case REFUSE_REASON_VERSION_MISMATCH:
177-
err = fmt.Errorf("%s: version mismatch", PROTOCOL_NAME)
178-
case REFUSE_REASON_DECODE_ERROR:
179-
err = fmt.Errorf("%s: decode error: %s", PROTOCOL_NAME, msg.Reason[2].(string))
180-
case REFUSE_REASON_REFUSED:
181-
err = fmt.Errorf("%s: refused: %s", PROTOCOL_NAME, msg.Reason[2].(string))
182-
}
183-
return err
184-
}
93+
*/

protocol/handshake/server.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package handshake
2+
3+
import (
4+
"fmt"
5+
"github.com/cloudstruct/go-ouroboros-network/protocol"
6+
)
7+
8+
type Server struct {
9+
*protocol.Protocol
10+
config *Config
11+
}
12+
13+
func NewServer(protoOptions protocol.ProtocolOptions, cfg *Config) *Server {
14+
s := &Server{
15+
config: cfg,
16+
}
17+
protoConfig := protocol.ProtocolConfig{
18+
Name: PROTOCOL_NAME,
19+
ProtocolId: PROTOCOL_ID,
20+
Muxer: protoOptions.Muxer,
21+
ErrorChan: protoOptions.ErrorChan,
22+
Mode: protoOptions.Mode,
23+
Role: protocol.ProtocolRoleServer,
24+
MessageHandlerFunc: s.handleMessage,
25+
MessageFromCborFunc: NewMsgFromCbor,
26+
StateMap: StateMap,
27+
InitialState: STATE_PROPOSE,
28+
}
29+
s.Protocol = protocol.New(protoConfig)
30+
return s
31+
}
32+
33+
func (s *Server) handleMessage(msg protocol.Message, isResponse bool) error {
34+
var err error
35+
switch msg.Type() {
36+
case MESSAGE_TYPE_PROPOSE_VERSIONS:
37+
err = s.handleProposeVersions(msg)
38+
default:
39+
err = fmt.Errorf("%s: received unexpected message type %d", PROTOCOL_NAME, msg.Type())
40+
}
41+
return err
42+
}
43+
44+
func (s *Server) handleProposeVersions(msgGeneric protocol.Message) error {
45+
if s.config.FinishedFunc == nil {
46+
return fmt.Errorf("received handshake ProposeVersions message but no callback function is defined")
47+
}
48+
msg := msgGeneric.(*MsgProposeVersions)
49+
var highestVersion uint16
50+
var fullDuplex bool
51+
var versionData []interface{}
52+
for proposedVersion := range msg.VersionMap {
53+
if proposedVersion > highestVersion {
54+
for _, allowedVersion := range s.config.ProtocolVersions {
55+
if allowedVersion == proposedVersion {
56+
highestVersion = proposedVersion
57+
versionData = msg.VersionMap[proposedVersion].([]interface{})
58+
//nolint:gosimple
59+
if versionData[1].(bool) == DIFFUSION_MODE_INITIATOR_AND_RESPONDER {
60+
fullDuplex = true
61+
} else {
62+
fullDuplex = false
63+
}
64+
break
65+
}
66+
}
67+
}
68+
}
69+
if highestVersion > 0 {
70+
resp := NewMsgAcceptVersion(highestVersion, versionData)
71+
if err := s.SendMessage(resp); err != nil {
72+
return err
73+
}
74+
return s.config.FinishedFunc(highestVersion, fullDuplex)
75+
} else {
76+
// TODO: handle failures
77+
// https://github.com/cloudstruct/go-ouroboros-network/issues/32
78+
return fmt.Errorf("handshake failed, but we don't yet support this")
79+
}
80+
}

0 commit comments

Comments
 (0)