payload_test.go raw
1 package btcwire
2
3 import (
4 "bytes"
5 "testing"
6
7 "git.smesh.lol/nostrino/pkg/crypto/chainhash"
8 )
9
10 // Compile-time assertions that every payload type implements Payload.
11 var (
12 _ Payload = (*Version)(nil)
13 _ Payload = (*VerAck)(nil)
14 _ Payload = (*SendHeaders)(nil)
15 _ Payload = (*WtxidRelay)(nil)
16 _ Payload = (*SendAddrV2)(nil)
17 _ Payload = (*Ping)(nil)
18 _ Payload = (*Pong)(nil)
19 _ Payload = (*FeeFilter)(nil)
20 _ Payload = (*GetHeaders)(nil)
21 _ Payload = (*Headers)(nil)
22 _ Payload = (*GetCFHeaders)(nil)
23 _ Payload = (*CFHeaders)(nil)
24 _ Payload = (*GetCFilters)(nil)
25 _ Payload = (*CFilter)(nil)
26 _ Payload = (*GetCFCheckpt)(nil)
27 _ Payload = (*CFCheckpt)(nil)
28 _ Payload = (*Inv)(nil)
29 _ Payload = (*GetData)(nil)
30 _ Payload = (*NotFound)(nil)
31 _ Payload = (*Tx)(nil)
32 )
33
34 // testHash returns a deterministic, non-zero hash derived from b.
35 func testHash(b byte) chainhash.Hash {
36 var h chainhash.Hash
37 for i := range h {
38 h[i] = b + byte(i)
39 }
40 return h
41 }
42
43 // allCommands lists every command constant this package defines.
44 var allCommands = []string{
45 CmdVersion, CmdVerAck, CmdPing, CmdPong,
46 CmdGetHeaders, CmdHeaders,
47 CmdGetCFilters, CmdCFilter,
48 CmdGetCFHeaders, CmdCFHeaders,
49 CmdGetCFCheckpt, CmdCFCheckpt,
50 CmdGetData, CmdTx, CmdInv, CmdNotFound,
51 CmdSendHeaders, CmdWtxidRelay, CmdSendAddrV2,
52 CmdFeeFilter,
53 }
54
55 // TestPayloadFrames round trips every payload type through a complete frame,
56 // checking the command and that re-encoding the decoded payload reproduces
57 // the original bytes.
58 func TestPayloadFrames(t *testing.T) {
59 tests := []struct {
60 command string
61 value func() Payload
62 zero func() Payload
63 }{
64 {CmdVersion, func() Payload {
65 v := &Version{
66 ProtocolVersion: ProtocolVersion,
67 Services: ServiceNodeNetwork | ServiceNodeCompactFilters,
68 Timestamp: 1700000000,
69 Nonce: 0x0102030405060708,
70 UserAgent: "nostrino:0.1.0",
71 LastBlock: 800000,
72 Relay: true,
73 }
74 v.AddrRecv = mustAddress(t, "127.0.0.1", 8333)
75 v.AddrFrom = mustAddress(t, "2001:db8::1", 18333)
76 return v
77 }, func() Payload { return &Version{} }},
78 {CmdVerAck, func() Payload { return &VerAck{} }, func() Payload { return &VerAck{} }},
79 {CmdSendHeaders, func() Payload { return &SendHeaders{} }, func() Payload { return &SendHeaders{} }},
80 {CmdWtxidRelay, func() Payload { return &WtxidRelay{} }, func() Payload { return &WtxidRelay{} }},
81 {CmdSendAddrV2, func() Payload { return &SendAddrV2{} }, func() Payload { return &SendAddrV2{} }},
82 {CmdPing, func() Payload { return &Ping{Nonce: 42} }, func() Payload { return &Ping{} }},
83 {CmdPong, func() Payload { return &Pong{Nonce: 43} }, func() Payload { return &Pong{} }},
84 {CmdFeeFilter, func() Payload { return &FeeFilter{FeeRate: 1000} }, func() Payload { return &FeeFilter{} }},
85 {CmdGetHeaders, func() Payload {
86 return &GetHeaders{
87 ProtocolVersion: ProtocolVersion,
88 LocatorHashes: []chainhash.Hash{testHash(1), testHash(2)},
89 StopHash: testHash(3),
90 }
91 }, func() Payload { return &GetHeaders{} }},
92 {CmdHeaders, func() Payload {
93 return &Headers{Headers: []BlockHeader{
94 {Version: 1, PrevBlock: testHash(4), MerkleRoot: testHash(5), Timestamp: 100, Bits: 0x1d00ffff, Nonce: 7},
95 {Version: 2, PrevBlock: testHash(6), MerkleRoot: testHash(7), Timestamp: 200, Bits: 0x1b0404cb, Nonce: 8},
96 }}
97 }, func() Payload { return &Headers{} }},
98 {CmdGetCFHeaders, func() Payload {
99 return &GetCFHeaders{FilterType: FilterTypeBasic, StartHeight: 700000, StopHash: testHash(8)}
100 }, func() Payload { return &GetCFHeaders{} }},
101 {CmdCFHeaders, func() Payload {
102 return &CFHeaders{
103 FilterType: FilterTypeBasic,
104 StopHash: testHash(9),
105 PrevFilterHeader: testHash(10),
106 FilterHashes: []chainhash.Hash{testHash(11), testHash(12)},
107 }
108 }, func() Payload { return &CFHeaders{} }},
109 {CmdGetCFilters, func() Payload {
110 return &GetCFilters{FilterType: FilterTypeBasic, StartHeight: 1, StopHash: testHash(13)}
111 }, func() Payload { return &GetCFilters{} }},
112 {CmdCFilter, func() Payload {
113 return &CFilter{FilterType: FilterTypeBasic, BlockHash: testHash(14), Filter: []byte{0x00, 0x01, 0x02}}
114 }, func() Payload { return &CFilter{} }},
115 {CmdGetCFCheckpt, func() Payload {
116 return &GetCFCheckpt{FilterType: FilterTypeBasic, StopHash: testHash(15)}
117 }, func() Payload { return &GetCFCheckpt{} }},
118 {CmdCFCheckpt, func() Payload {
119 return &CFCheckpt{
120 FilterType: FilterTypeBasic,
121 StopHash: testHash(16),
122 FilterHeaders: []chainhash.Hash{testHash(17), testHash(18)},
123 }
124 }, func() Payload { return &CFCheckpt{} }},
125 {CmdInv, func() Payload {
126 return &Inv{InvList: []InvVect{
127 {Type: InvVectTypeTx, Hash: testHash(19)},
128 {Type: InvVectTypeWitnessTx, Hash: testHash(20)},
129 }}
130 }, func() Payload { return &Inv{} }},
131 {CmdGetData, func() Payload {
132 return &GetData{InvList: []InvVect{{Type: InvVectTypeFilteredBlock, Hash: testHash(21)}}}
133 }, func() Payload { return &GetData{} }},
134 {CmdNotFound, func() Payload {
135 return &NotFound{InvList: []InvVect{{Type: InvVectTypeBlock, Hash: testHash(22)}}}
136 }, func() Payload { return &NotFound{} }},
137 {CmdTx, func() Payload {
138 return &Tx{Raw: []byte{0x01, 0x00, 0x00, 0x00, 0x00}}
139 }, func() Payload { return &Tx{} }},
140 }
141 for _, tc := range tests {
142 t.Run(tc.command, func(t *testing.T) {
143 want, err := tc.value().EncodePayload()
144 if err != nil {
145 t.Fatalf("EncodePayload: %v", err)
146 }
147 frame, err := EncodeMessagePayload(MainNet, tc.command, tc.value())
148 if err != nil {
149 t.Fatalf("EncodeMessagePayload: %v", err)
150 }
151 msg, err := DecodeMessage(MainNet, bytes.NewReader(frame))
152 if err != nil {
153 t.Fatalf("DecodeMessage: %v", err)
154 }
155 if msg.Command != tc.command {
156 t.Fatalf("command = %q, want %q", msg.Command, tc.command)
157 }
158 if !bytes.Equal(msg.Payload, want) {
159 t.Fatalf("frame payload = %x, want %x", msg.Payload, want)
160 }
161 decoded := tc.zero()
162 if err := msg.Decode(decoded); err != nil {
163 t.Fatalf("Message.Decode: %v", err)
164 }
165 got, err := decoded.EncodePayload()
166 if err != nil {
167 t.Fatalf("re-EncodePayload: %v", err)
168 }
169 if !bytes.Equal(got, want) {
170 t.Fatalf("re-encoded payload = %x, want %x", got, want)
171 }
172 })
173 }
174 }
175
176 // mustAddress parses host into a NetAddress or fails the test.
177 func mustAddress(t *testing.T, host string, port uint16) NetAddress {
178 t.Helper()
179 addr, err := NewNetAddress(host, port)
180 if err != nil {
181 t.Fatalf("NewNetAddress(%q, %d): %v", host, port, err)
182 }
183 return *addr
184 }
185