Skip to content

Commit 4b9f7a9

Browse files
authored
test: local-state-query client tests (#564)
This tests a simple query, a simple query that requires the current era, a simple query that uses the Shelley query type, and a more complex query. Fixes #321
1 parent e04a54d commit 4b9f7a9

File tree

2 files changed

+267
-0
lines changed

2 files changed

+267
-0
lines changed

cbor/cbor.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ type DecodeStoreCbor struct {
5555
}
5656

5757
func (d *DecodeStoreCbor) SetCbor(cborData []byte) {
58+
if cborData == nil {
59+
d.cborData = nil
60+
return
61+
}
5862
d.cborData = make([]byte, len(cborData))
5963
copy(d.cborData, cborData)
6064
}
Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
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 localstatequery_test
16+
17+
import (
18+
"fmt"
19+
"reflect"
20+
"testing"
21+
"time"
22+
23+
"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+
ocommon "github.com/blinklabs-io/gouroboros/protocol/common"
29+
"github.com/blinklabs-io/gouroboros/protocol/localstatequery"
30+
31+
ouroboros_mock "github.com/blinklabs-io/ouroboros-mock"
32+
"go.uber.org/goleak"
33+
)
34+
35+
var conversationHandshakeAcquire = []ouroboros_mock.ConversationEntry{
36+
ouroboros_mock.ConversationEntryHandshakeRequestGeneric,
37+
ouroboros_mock.ConversationEntryHandshakeNtCResponse,
38+
ouroboros_mock.ConversationEntryInput{
39+
ProtocolId: localstatequery.ProtocolId,
40+
MessageType: localstatequery.MessageTypeAcquireNoPoint,
41+
},
42+
ouroboros_mock.ConversationEntryOutput{
43+
ProtocolId: localstatequery.ProtocolId,
44+
IsResponse: true,
45+
Messages: []protocol.Message{
46+
localstatequery.NewMsgAcquired(),
47+
},
48+
},
49+
}
50+
51+
var conversationCurrentEra = append(
52+
conversationHandshakeAcquire,
53+
ouroboros_mock.ConversationEntryInput{
54+
ProtocolId: localstatequery.ProtocolId,
55+
MessageType: localstatequery.MessageTypeQuery,
56+
},
57+
ouroboros_mock.ConversationEntryOutput{
58+
ProtocolId: localstatequery.ProtocolId,
59+
IsResponse: true,
60+
Messages: []protocol.Message{
61+
localstatequery.NewMsgResult([]byte{0x5}),
62+
},
63+
},
64+
)
65+
66+
type testInnerFunc func(*testing.T, *ouroboros.Connection)
67+
68+
func runTest(t *testing.T, conversation []ouroboros_mock.ConversationEntry, innerFunc testInnerFunc) {
69+
defer goleak.VerifyNone(t)
70+
mockConn := ouroboros_mock.NewConnection(
71+
ouroboros_mock.ProtocolRoleClient,
72+
conversation,
73+
)
74+
// Async mock connection error handler
75+
asyncErrChan := make(chan error, 1)
76+
go func() {
77+
err := <-mockConn.(*ouroboros_mock.Connection).ErrorChan()
78+
if err != nil {
79+
asyncErrChan <- fmt.Errorf("received unexpected error: %s", err)
80+
}
81+
close(asyncErrChan)
82+
}()
83+
oConn, err := ouroboros.New(
84+
ouroboros.WithConnection(mockConn),
85+
ouroboros.WithNetworkMagic(ouroboros_mock.MockNetworkMagic),
86+
)
87+
if err != nil {
88+
t.Fatalf("unexpected error when creating Ouroboros object: %s", err)
89+
}
90+
// Async error handler
91+
go func() {
92+
err, ok := <-oConn.ErrorChan()
93+
if !ok {
94+
return
95+
}
96+
// We can't call t.Fatalf() from a different Goroutine, so we panic instead
97+
panic(fmt.Sprintf("unexpected Ouroboros error: %s", err))
98+
}()
99+
// Run test inner function
100+
innerFunc(t, oConn)
101+
// Wait for mock connection shutdown
102+
select {
103+
case err, ok := <-asyncErrChan:
104+
if ok {
105+
t.Fatal(err.Error())
106+
}
107+
case <-time.After(2 * time.Second):
108+
t.Fatalf("did not complete within timeout")
109+
}
110+
// Close Ouroboros connection
111+
if err := oConn.Close(); err != nil {
112+
t.Fatalf("unexpected error when closing Ouroboros object: %s", err)
113+
}
114+
// Wait for connection shutdown
115+
select {
116+
case <-oConn.ErrorChan():
117+
case <-time.After(10 * time.Second):
118+
t.Errorf("did not shutdown within timeout")
119+
}
120+
}
121+
122+
func TestGetCurrentEra(t *testing.T) {
123+
runTest(
124+
t,
125+
conversationCurrentEra,
126+
func(t *testing.T, oConn *ouroboros.Connection) {
127+
// Run query
128+
currentEra, err := oConn.LocalStateQuery().Client.GetCurrentEra()
129+
if err != nil {
130+
t.Fatalf("received unexpected error: %s", err)
131+
}
132+
if currentEra != 5 {
133+
t.Fatalf("did not receive expected result: got %d, expected %d", currentEra, 5)
134+
}
135+
},
136+
)
137+
}
138+
139+
func TestGetChainPoint(t *testing.T) {
140+
expectedPoint := ocommon.NewPoint(123, []byte{0xa, 0xb, 0xc})
141+
cborData, err := cbor.Encode(expectedPoint)
142+
if err != nil {
143+
t.Fatalf("unexpected error: %s", err)
144+
}
145+
conversation := append(
146+
conversationHandshakeAcquire,
147+
ouroboros_mock.ConversationEntryInput{
148+
ProtocolId: localstatequery.ProtocolId,
149+
MessageType: localstatequery.MessageTypeQuery,
150+
},
151+
ouroboros_mock.ConversationEntryOutput{
152+
ProtocolId: localstatequery.ProtocolId,
153+
IsResponse: true,
154+
Messages: []protocol.Message{
155+
localstatequery.NewMsgResult(cborData),
156+
},
157+
},
158+
)
159+
runTest(
160+
t,
161+
conversation,
162+
func(t *testing.T, oConn *ouroboros.Connection) {
163+
chainPoint, err := oConn.LocalStateQuery().Client.GetChainPoint()
164+
if err != nil {
165+
t.Fatalf("received unexpected error: %s", err)
166+
}
167+
if !reflect.DeepEqual(chainPoint, &expectedPoint) {
168+
t.Fatalf("did not receive expected result\n got: %#v\n wanted: %#v", chainPoint, expectedPoint)
169+
}
170+
},
171+
)
172+
}
173+
174+
func TestGetEpochNo(t *testing.T) {
175+
expectedEpochNo := 123456
176+
conversation := append(
177+
conversationCurrentEra,
178+
ouroboros_mock.ConversationEntryInput{
179+
ProtocolId: localstatequery.ProtocolId,
180+
MessageType: localstatequery.MessageTypeQuery,
181+
},
182+
ouroboros_mock.ConversationEntryOutput{
183+
ProtocolId: localstatequery.ProtocolId,
184+
IsResponse: true,
185+
Messages: []protocol.Message{
186+
localstatequery.NewMsgResult(
187+
// [123456]
188+
test.DecodeHexString("811a0001e240"),
189+
),
190+
},
191+
},
192+
)
193+
runTest(
194+
t,
195+
conversation,
196+
func(t *testing.T, oConn *ouroboros.Connection) {
197+
epochNo, err := oConn.LocalStateQuery().Client.GetEpochNo()
198+
if err != nil {
199+
t.Fatalf("received unexpected error: %s", err)
200+
}
201+
if epochNo != expectedEpochNo {
202+
t.Fatalf("did not receive expected result, got %d, wanted %d", epochNo, expectedEpochNo)
203+
}
204+
},
205+
)
206+
}
207+
208+
func TestGetUTxOByAddress(t *testing.T) {
209+
testAddress, err := ledger.NewAddress("addr_test1vrk294czhxhglflvxla7vxj2cjz7wyrdpxl3fj0vych5wws77xuc7")
210+
if err != nil {
211+
t.Fatalf("unexpected error: %s", err)
212+
}
213+
expectedUtxoId := localstatequery.UtxoId{
214+
Hash: ledger.NewBlake2b256([]byte{0x1, 0x2}),
215+
Idx: 7,
216+
}
217+
expectedResult := localstatequery.UTxOByAddressResult{
218+
Results: map[localstatequery.UtxoId]ledger.BabbageTransactionOutput{
219+
expectedUtxoId: ledger.BabbageTransactionOutput{
220+
OutputAddress: testAddress,
221+
OutputAmount: ledger.MaryTransactionOutputValue{
222+
Amount: 234567,
223+
},
224+
},
225+
},
226+
}
227+
cborData, err := cbor.Encode(expectedResult)
228+
if err != nil {
229+
t.Fatalf("unexpected error: %s", err)
230+
}
231+
conversation := append(
232+
conversationCurrentEra,
233+
ouroboros_mock.ConversationEntryInput{
234+
ProtocolId: localstatequery.ProtocolId,
235+
MessageType: localstatequery.MessageTypeQuery,
236+
},
237+
ouroboros_mock.ConversationEntryOutput{
238+
ProtocolId: localstatequery.ProtocolId,
239+
IsResponse: true,
240+
Messages: []protocol.Message{
241+
localstatequery.NewMsgResult(cborData),
242+
},
243+
},
244+
)
245+
runTest(
246+
t,
247+
conversation,
248+
func(t *testing.T, oConn *ouroboros.Connection) {
249+
utxos, err := oConn.LocalStateQuery().Client.GetUTxOByAddress([]ledger.Address{testAddress})
250+
if err != nil {
251+
t.Fatalf("received unexpected error: %s", err)
252+
}
253+
// Set stored CBOR to nil to make comparison easier
254+
for k, v := range utxos.Results {
255+
v.SetCbor(nil)
256+
utxos.Results[k] = v
257+
}
258+
if !reflect.DeepEqual(utxos, &expectedResult) {
259+
t.Fatalf("did not receive expected result\n got: %#v\n wanted: %#v", utxos, expectedResult)
260+
}
261+
},
262+
)
263+
}

0 commit comments

Comments
 (0)