Skip to content

Commit aed6b08

Browse files
authored
test: chainsync client tests (#565)
Fixes #318
1 parent 4b9f7a9 commit aed6b08

File tree

1 file changed

+249
-0
lines changed

1 file changed

+249
-0
lines changed

protocol/chainsync/client_test.go

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
// Copyright 2024 Blink Labs Software
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package chainsync_test
16+
17+
import (
18+
"fmt"
19+
"reflect"
20+
"testing"
21+
"time"
22+
23+
ouroboros "github.com/blinklabs-io/gouroboros"
24+
"github.com/blinklabs-io/gouroboros/cbor"
25+
"github.com/blinklabs-io/gouroboros/internal/test"
26+
"github.com/blinklabs-io/gouroboros/ledger"
27+
"github.com/blinklabs-io/gouroboros/protocol"
28+
"github.com/blinklabs-io/gouroboros/protocol/chainsync"
29+
ocommon "github.com/blinklabs-io/gouroboros/protocol/common"
30+
31+
ouroboros_mock "github.com/blinklabs-io/ouroboros-mock"
32+
"go.uber.org/goleak"
33+
)
34+
35+
var conversationHandshakeFindIntersect = []ouroboros_mock.ConversationEntry{
36+
ouroboros_mock.ConversationEntryHandshakeRequestGeneric,
37+
ouroboros_mock.ConversationEntryHandshakeNtCResponse,
38+
ouroboros_mock.ConversationEntryInput{
39+
ProtocolId: chainsync.ProtocolIdNtC,
40+
MessageType: chainsync.MessageTypeFindIntersect,
41+
},
42+
}
43+
44+
type testInnerFunc func(*testing.T, *ouroboros.Connection)
45+
46+
func runTest(t *testing.T, conversation []ouroboros_mock.ConversationEntry, innerFunc testInnerFunc) {
47+
defer goleak.VerifyNone(t)
48+
mockConn := ouroboros_mock.NewConnection(
49+
ouroboros_mock.ProtocolRoleClient,
50+
conversation,
51+
)
52+
// Async mock connection error handler
53+
asyncErrChan := make(chan error, 1)
54+
go func() {
55+
err := <-mockConn.(*ouroboros_mock.Connection).ErrorChan()
56+
if err != nil {
57+
asyncErrChan <- fmt.Errorf("received unexpected error: %s", err)
58+
}
59+
close(asyncErrChan)
60+
}()
61+
oConn, err := ouroboros.New(
62+
ouroboros.WithConnection(mockConn),
63+
ouroboros.WithNetworkMagic(ouroboros_mock.MockNetworkMagic),
64+
)
65+
if err != nil {
66+
t.Fatalf("unexpected error when creating Ouroboros object: %s", err)
67+
}
68+
// Async error handler
69+
go func() {
70+
err, ok := <-oConn.ErrorChan()
71+
if !ok {
72+
return
73+
}
74+
// We can't call t.Fatalf() from a different Goroutine, so we panic instead
75+
panic(fmt.Sprintf("unexpected Ouroboros error: %s", err))
76+
}()
77+
// Run test inner function
78+
innerFunc(t, oConn)
79+
// Wait for mock connection shutdown
80+
select {
81+
case err, ok := <-asyncErrChan:
82+
if ok {
83+
t.Fatal(err.Error())
84+
}
85+
case <-time.After(2 * time.Second):
86+
t.Fatalf("did not complete within timeout")
87+
}
88+
// Close Ouroboros connection
89+
if err := oConn.Close(); err != nil {
90+
t.Fatalf("unexpected error when closing Ouroboros object: %s", err)
91+
}
92+
// Wait for connection shutdown
93+
select {
94+
case <-oConn.ErrorChan():
95+
case <-time.After(10 * time.Second):
96+
t.Errorf("did not shutdown within timeout")
97+
}
98+
}
99+
100+
func TestIntersectNotFound(t *testing.T) {
101+
conversation := append(
102+
conversationHandshakeFindIntersect,
103+
ouroboros_mock.ConversationEntryOutput{
104+
ProtocolId: chainsync.ProtocolIdNtC,
105+
IsResponse: true,
106+
Messages: []protocol.Message{
107+
chainsync.NewMsgIntersectNotFound(
108+
chainsync.Tip{
109+
// NOTE: these values don't matter
110+
BlockNumber: 12345,
111+
Point: ocommon.NewPointOrigin(),
112+
},
113+
),
114+
},
115+
},
116+
)
117+
runTest(
118+
t,
119+
conversation,
120+
func(t *testing.T, oConn *ouroboros.Connection) {
121+
// Start sync with "bad" intersect points
122+
err := oConn.ChainSync().Client.Sync([]ocommon.Point{})
123+
if err == nil {
124+
t.Fatalf("did not receive expected error")
125+
}
126+
if err != chainsync.IntersectNotFoundError {
127+
t.Fatalf("did not receive expected error\n got: %s\n wanted: %s", err, chainsync.IntersectNotFoundError)
128+
}
129+
},
130+
)
131+
}
132+
133+
func TestGetCurrentTip(t *testing.T) {
134+
expectedTip := chainsync.Tip{
135+
BlockNumber: 12345,
136+
Point: ocommon.NewPoint(
137+
23456,
138+
test.DecodeHexString("0123456789abcdef"),
139+
),
140+
}
141+
conversation := append(
142+
conversationHandshakeFindIntersect,
143+
ouroboros_mock.ConversationEntryOutput{
144+
ProtocolId: chainsync.ProtocolIdNtC,
145+
IsResponse: true,
146+
Messages: []protocol.Message{
147+
chainsync.NewMsgIntersectNotFound(expectedTip),
148+
},
149+
},
150+
)
151+
runTest(
152+
t,
153+
conversation,
154+
func(t *testing.T, oConn *ouroboros.Connection) {
155+
tip, err := oConn.ChainSync().Client.GetCurrentTip()
156+
if err != nil {
157+
t.Fatalf("received unexpected error: %s", err)
158+
}
159+
if !reflect.DeepEqual(tip, &expectedTip) {
160+
t.Fatalf("did not receive expected tip\n got: %#v\n wanted: %#v", tip, expectedTip)
161+
}
162+
},
163+
)
164+
}
165+
166+
func TestGetAvailableBlockRange(t *testing.T) {
167+
expectedIntersect := ocommon.NewPoint(
168+
20001,
169+
test.DecodeHexString("123456789abcdef0"),
170+
)
171+
expectedTip := chainsync.Tip{
172+
BlockNumber: 12345,
173+
Point: ocommon.NewPoint(
174+
23456,
175+
test.DecodeHexString("0123456789abcdef"),
176+
),
177+
}
178+
// Create basic block and round-trip it through the CBOR encoder to get the hash populated
179+
// The slot value is one higher than our intersect point and the block height is less than
180+
// our expected tip
181+
testBlock := ledger.BabbageBlock{
182+
Header: &ledger.BabbageBlockHeader{},
183+
}
184+
testBlock.Header.Body.BlockNumber = 12001
185+
testBlock.Header.Body.Slot = 20002
186+
blockCbor, err := cbor.Encode(testBlock)
187+
if err != nil {
188+
t.Fatalf("received unexpected error: %s", err)
189+
}
190+
if _, err := cbor.Decode(blockCbor, &testBlock); err != nil {
191+
t.Fatalf("received unexpected error: %s", err)
192+
}
193+
expectedStart := ocommon.NewPoint(
194+
testBlock.SlotNumber(),
195+
test.DecodeHexString(testBlock.Hash()),
196+
)
197+
conversation := append(
198+
conversationHandshakeFindIntersect,
199+
ouroboros_mock.ConversationEntryOutput{
200+
ProtocolId: chainsync.ProtocolIdNtC,
201+
IsResponse: true,
202+
Messages: []protocol.Message{
203+
chainsync.NewMsgIntersectFound(expectedIntersect, expectedTip),
204+
},
205+
},
206+
ouroboros_mock.ConversationEntryInput{
207+
ProtocolId: chainsync.ProtocolIdNtC,
208+
MessageType: chainsync.MessageTypeRequestNext,
209+
},
210+
ouroboros_mock.ConversationEntryOutput{
211+
ProtocolId: chainsync.ProtocolIdNtC,
212+
IsResponse: true,
213+
Messages: []protocol.Message{
214+
chainsync.NewMsgRollBackward(expectedIntersect, expectedTip),
215+
},
216+
},
217+
ouroboros_mock.ConversationEntryInput{
218+
ProtocolId: chainsync.ProtocolIdNtC,
219+
MessageType: chainsync.MessageTypeRequestNext,
220+
},
221+
ouroboros_mock.ConversationEntryOutput{
222+
ProtocolId: chainsync.ProtocolIdNtC,
223+
IsResponse: true,
224+
Messages: []protocol.Message{
225+
chainsync.NewMsgRollForwardNtC(
226+
ledger.BlockTypeBabbage,
227+
blockCbor,
228+
expectedTip,
229+
),
230+
},
231+
},
232+
)
233+
runTest(
234+
t,
235+
conversation,
236+
func(t *testing.T, oConn *ouroboros.Connection) {
237+
start, end, err := oConn.ChainSync().Client.GetAvailableBlockRange([]ocommon.Point{expectedIntersect})
238+
if err != nil {
239+
t.Fatalf("received unexpected error: %s", err)
240+
}
241+
if !reflect.DeepEqual(start, expectedStart) {
242+
t.Fatalf("did not receive expected start point\n got: %#v\n wanted: %#v", start, expectedStart)
243+
}
244+
if !reflect.DeepEqual(end, expectedTip.Point) {
245+
t.Fatalf("did not receive expected end point\n got: %#v\n wanted: %#v", end, expectedTip.Point)
246+
}
247+
},
248+
)
249+
}

0 commit comments

Comments
 (0)