Skip to content

Commit a63a30e

Browse files
committed
Chain-sync mini-protocol (part 1)
1 parent c51b45f commit a63a30e

File tree

11 files changed

+582
-10
lines changed

11 files changed

+582
-10
lines changed

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,24 @@
33
A Go client implementation of the Cardano Ouroboros network protocol
44

55
This is loosely based on the [official Haskell implementation](https://github.com/input-output-hk/ouroboros-network)
6+
7+
## Implementation status
8+
9+
The Ouroboros protocol consists of a simple multiplexer protocol and various mini-protocols that run on top of it.
10+
This makes it easy to implement only parts of the protocol without negatively affecting usability of this library.
11+
12+
The multiplexer and handshake mini-protocol are "fully" working. The focus will be on the node-to-client (local) protocols,
13+
but the node-to-node protocols will also be implemented in time.
14+
15+
### Mini-protocols
16+
17+
| Name | Status |
18+
| --- | --- |
19+
| Handshake | Implemented |
20+
| Chain-Sync | In Progress |
21+
| Block-Fetch | Not Implemented |
22+
| TxSubmission | Not Implemented |
23+
| Local TxSubmission | Not Implemented |
24+
| Local State Query | Not Implemented |
25+
| Keep-Alive | Not Implemented |
26+

cmd/go-ouroboros-network/main.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import (
55
"flag"
66
"fmt"
77
"github.com/cloudstruct/go-ouroboros-network"
8+
"github.com/cloudstruct/go-ouroboros-network/protocol/chainsync"
9+
"github.com/cloudstruct/go-ouroboros-network/protocol/common"
10+
"github.com/cloudstruct/go-ouroboros-network/utils"
811
"io"
912
"net"
1013
"os"
@@ -85,4 +88,23 @@ func main() {
8588
os.Exit(1)
8689
}
8790
}()
91+
// Test chain-sync
92+
for {
93+
resp, err := o.ChainSync.RequestNext()
94+
if err != nil {
95+
fmt.Printf("ERROR: %s\n", err)
96+
os.Exit(1)
97+
}
98+
//fmt.Printf("resp = %#v, err = %#v\n", resp, err)
99+
switch resp.BlockType {
100+
case chainsync.BLOCK_TYPE_BYRON_EBB:
101+
fmt.Printf("found Byron EBB block\n")
102+
case chainsync.BLOCK_TYPE_BYRON_MAIN:
103+
block := resp.Block.(common.ByronMainBlock)
104+
fmt.Printf("epoch = %d, slot = %d, prevBlock = %s\n", block.Header.ConsensusData.SlotId.Epoch, block.Header.ConsensusData.SlotId.Slot, block.Header.PrevBlock)
105+
default:
106+
fmt.Printf("unsupported (so far) block type %d\n", resp.BlockType)
107+
fmt.Printf("%s\n", utils.DumpCborStructure(resp.Block, ""))
108+
}
109+
}
88110
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ module github.com/cloudstruct/go-ouroboros-network
22

33
go 1.16
44

5-
require github.com/fxamacker/cbor/v2 v2.3.0
5+
require github.com/fxamacker/cbor/v2 v2.3.1

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
github.com/fxamacker/cbor/v2 v2.3.0 h1:aM45YGMctNakddNNAezPxDUpv38j44Abh+hifNuqXik=
2-
github.com/fxamacker/cbor/v2 v2.3.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
1+
github.com/fxamacker/cbor/v2 v2.3.1 h1:4sjmfkL6jTl8jChPYfGms0cSSsCJRlA/JdkLjGnZxPk=
2+
github.com/fxamacker/cbor/v2 v2.3.1/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
33
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
44
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=

ouroboros.go

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,32 @@ package ouroboros
33
import (
44
"fmt"
55
"github.com/cloudstruct/go-ouroboros-network/muxer"
6+
"github.com/cloudstruct/go-ouroboros-network/protocol/chainsync"
67
"github.com/cloudstruct/go-ouroboros-network/protocol/handshake"
78
"io"
89
"net"
910
)
1011

1112
type Ouroboros struct {
12-
conn io.ReadWriteCloser
13-
networkMagic uint32
14-
waitForHandshake bool
15-
handshakeComplete bool
16-
muxer *muxer.Muxer
17-
ErrorChan chan error
13+
conn io.ReadWriteCloser
14+
networkMagic uint32
15+
waitForHandshake bool
16+
useNodeToNodeProto bool
17+
handshakeComplete bool
18+
muxer *muxer.Muxer
19+
ErrorChan chan error
1820
// Mini-protocols
1921
Handshake *handshake.Handshake
22+
ChainSync *chainsync.ChainSync
2023
}
2124

2225
type OuroborosOptions struct {
2326
Conn io.ReadWriteCloser
2427
NetworkMagic uint32
2528
// Whether to wait for the other side to initiate the handshake. This is useful
2629
// for servers
27-
WaitForHandshake bool
30+
WaitForHandshake bool
31+
UseNodeToNodeProtocol bool
2832
}
2933

3034
func New(options *OuroborosOptions) (*Ouroboros, error) {
@@ -75,5 +79,6 @@ func (o *Ouroboros) setupConnection() error {
7579
o.handshakeComplete = <-o.Handshake.Finished
7680
fmt.Printf("negotiated protocol version %d\n", o.Handshake.Version)
7781
// TODO: register additional mini-protocols
82+
o.ChainSync = chainsync.New(o.muxer, o.ErrorChan, o.useNodeToNodeProto)
7883
return nil
7984
}

protocol/chainsync/chainsync.go

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
package chainsync
2+
3+
import (
4+
"bytes"
5+
"errors"
6+
"fmt"
7+
"github.com/cloudstruct/go-ouroboros-network/muxer"
8+
"github.com/cloudstruct/go-ouroboros-network/protocol/common"
9+
"github.com/cloudstruct/go-ouroboros-network/utils"
10+
"io"
11+
)
12+
13+
const (
14+
PROTOCOL_ID_NTN uint16 = 2
15+
PROTOCOL_ID_NTC uint16 = 5
16+
17+
STATE_IDLE = iota
18+
STATE_CAN_AWAIT
19+
STATE_MUST_REPLY
20+
STATE_INTERSECT
21+
22+
BLOCK_TYPE_BYRON_EBB = 0
23+
BLOCK_TYPE_BYRON_MAIN = 1
24+
// TODO: more block types
25+
)
26+
27+
type ChainSync struct {
28+
errorChan chan error
29+
sendChan chan *muxer.Message
30+
recvChan chan *muxer.Message
31+
state uint8
32+
nodeToNode bool
33+
protocolId uint16
34+
requestNextChan chan *RequestNextResult
35+
recvBuffer bytes.Buffer
36+
}
37+
38+
func New(m *muxer.Muxer, errorChan chan error, nodeToNode bool) *ChainSync {
39+
// Use node-to-client protocol ID
40+
protocolId := PROTOCOL_ID_NTC
41+
if nodeToNode {
42+
// Use node-to-node protocol ID
43+
protocolId = PROTOCOL_ID_NTN
44+
}
45+
sendChan, recvChan := m.RegisterProtocol(protocolId)
46+
c := &ChainSync{
47+
errorChan: errorChan,
48+
sendChan: sendChan,
49+
recvChan: recvChan,
50+
state: STATE_IDLE,
51+
nodeToNode: nodeToNode,
52+
protocolId: protocolId,
53+
requestNextChan: make(chan *RequestNextResult, 10),
54+
}
55+
go c.recvLoop()
56+
return c
57+
}
58+
59+
func (c *ChainSync) recvLoop() {
60+
for {
61+
var err error
62+
// Wait for response
63+
msg := <-c.recvChan
64+
c.recvBuffer.Write(msg.Payload)
65+
// Decode response into generic list until we can determine what type of response it is
66+
var resp []interface{}
67+
if err := utils.CborDecode(c.recvBuffer.Bytes(), &resp); err != nil {
68+
if errors.Is(err, io.ErrUnexpectedEOF) {
69+
continue
70+
}
71+
c.errorChan <- fmt.Errorf("chain-sync: decode error: %s", err)
72+
}
73+
switch resp[0].(uint64) {
74+
case MESSAGE_TYPE_AWAIT_REPLY:
75+
//err = c.handleRequest(msg)
76+
fmt.Printf("MESSAGE_TYPE_AWAIT_REPLY\n")
77+
case MESSAGE_TYPE_ROLL_FORWARD:
78+
err = c.handleRollForward(c.recvBuffer.Bytes())
79+
case MESSAGE_TYPE_ROLL_BACKWARD:
80+
err = c.handleRollBackward(c.recvBuffer.Bytes())
81+
case MESSAGE_TYPE_INTERSECT_FOUND:
82+
// TODO
83+
fmt.Printf("MESSAGE_TYPE_INTERSECT_FOUND\n")
84+
case MESSAGE_TYPE_INTERSECT_NOT_FOUND:
85+
// TODO
86+
fmt.Printf("MESSAGE_TYPE_INTERSECT_NOT_FOUND\n")
87+
case MESSAGE_TYPE_DONE:
88+
// TODO
89+
fmt.Printf("MESSAGE_TYPE_DONE\n")
90+
default:
91+
err = fmt.Errorf("chain-sync: received unexpected message: %#v", resp)
92+
}
93+
c.recvBuffer.Reset()
94+
if err != nil {
95+
c.errorChan <- err
96+
}
97+
}
98+
}
99+
100+
func (c *ChainSync) RequestNext() (*RequestNextResult, error) {
101+
// TODO
102+
// if c.state != STATE_IDLE {
103+
// return nil, fmt.Errorf("protocol not in expected state")
104+
// }
105+
// Create our request
106+
data := newMsgRequestNext()
107+
dataBytes, err := utils.CborEncode(data)
108+
if err != nil {
109+
return nil, err
110+
}
111+
msg := muxer.NewMessage(c.protocolId, dataBytes, false)
112+
// Send request
113+
c.sendChan <- msg
114+
// Set the new state
115+
c.state = STATE_CAN_AWAIT
116+
resp := <-c.requestNextChan
117+
return resp, nil
118+
}
119+
120+
func (c *ChainSync) FindIntersect(points []string) {
121+
return
122+
}
123+
124+
func (c *ChainSync) handleAwaitReply(msg *muxer.Message) error {
125+
return nil
126+
}
127+
128+
func (c *ChainSync) handleRollForward(data []byte) error {
129+
// TODO
130+
// if c.state != STATE_CONFIRM {
131+
// return fmt.Errorf("received handshake accept response when protocol is in wrong state")
132+
// }
133+
var msg msgRollForward
134+
if err := utils.CborDecode(data, &msg); err != nil {
135+
return fmt.Errorf("chain-sync: decode error: %s", err)
136+
}
137+
/*
138+
if len(msg.WrappedData) < 8000 {
139+
for _, x := range msg.WrappedData {
140+
fmt.Printf("%02x ", x)
141+
}
142+
fmt.Printf("\n")
143+
}
144+
*/
145+
var block wrappedBlock
146+
if err := utils.CborDecode(msg.WrappedData, &block); err != nil {
147+
return fmt.Errorf("chain-sync: decode error: %s", err)
148+
//fmt.Printf("ignoring wrapped data decode error for now...%s\n", err)
149+
}
150+
resp := &RequestNextResult{
151+
BlockType: block.Type,
152+
}
153+
switch block.Type {
154+
case BLOCK_TYPE_BYRON_EBB:
155+
var block2 common.ByronEpochBoundaryBlock
156+
if err := utils.CborDecode(block.RawBlock, &block2); err != nil {
157+
return fmt.Errorf("chain-sync: decode error: %s", err)
158+
}
159+
resp.Block = block2
160+
case BLOCK_TYPE_BYRON_MAIN:
161+
var block2 common.ByronMainBlock
162+
if err := utils.CborDecode(block.RawBlock, &block2); err != nil {
163+
return fmt.Errorf("chain-sync: decode error: %s", err)
164+
}
165+
//fmt.Printf("epoch = %d, slot = %d, prevBlock = %s\n", block2.Header.ConsensusData.SlotId.Epoch, block2.Header.ConsensusData.SlotId.Slot, block2.Header.PrevBlock)
166+
//fmt.Printf("block2 = %#v\n", block2)
167+
resp.Block = block2
168+
// TODO: support more block types
169+
default:
170+
var payload interface{}
171+
if err := utils.CborDecode(msg.WrappedData, &payload); err != nil {
172+
//return fmt.Errorf("chain-sync: decode error: %s", err)
173+
//fmt.Printf("ignoring generic payload decode error for now...%s\n", err)
174+
}
175+
//fmt.Printf("payload = %s\n", utils.DumpCborStructure(payload, ""))
176+
resp.Block = payload
177+
}
178+
c.requestNextChan <- resp
179+
// TODO
180+
// c.state = STATE_DONE
181+
return nil
182+
}
183+
184+
func (c *ChainSync) handleRollBackward(data []byte) error {
185+
// TODO
186+
// if c.state != STATE_CONFIRM {
187+
// return fmt.Errorf("received handshake accept response when protocol is in wrong state")
188+
// }
189+
var msg msgRollBackward
190+
if err := utils.CborDecode(data, &msg); err != nil {
191+
return fmt.Errorf("chain-sync: decode error: %s", err)
192+
}
193+
fmt.Printf("handleRollBackward: msg = %#v\n", msg)
194+
resp := &RequestNextResult{
195+
Rollback: true,
196+
}
197+
c.requestNextChan <- resp
198+
// TODO
199+
// c.state = STATE_DONE
200+
return nil
201+
}
202+
203+
func (c *ChainSync) handleIntersectFound(msg *muxer.Message) error {
204+
return nil
205+
}
206+
207+
func (c *ChainSync) handleIntersectNotFound(msg *muxer.Message) error {
208+
return nil
209+
}
210+
211+
func (c *ChainSync) handleDone(msg *muxer.Message) error {
212+
return nil
213+
}

0 commit comments

Comments
 (0)