wire_test.go raw
1 package btcwire
2
3 import (
4 "bytes"
5 "encoding/binary"
6 "errors"
7 "fmt"
8 "io"
9 "testing"
10
11 "git.smesh.lol/nostrino/pkg/crypto/chainhash"
12 )
13
14 // frameHeader builds a 24-byte frame header with the given fields.
15 func frameHeader(magic uint32, command [CommandSize]byte, length uint32) []byte {
16 hdr := make([]byte, MessageHeaderSize)
17 binary.BigEndian.PutUint32(hdr[0:4], magic)
18 copy(hdr[4:4+CommandSize], command[:])
19 binary.LittleEndian.PutUint32(hdr[16:20], length)
20 sum := Checksum(nil)
21 copy(hdr[20:24], sum[:])
22 return hdr
23 }
24
25 func TestChecksum(t *testing.T) {
26 if got, want := Checksum(nil), [4]byte{0x5d, 0xf6, 0xe0, 0xe2}; got != want {
27 t.Fatalf("Checksum(nil) = %x, want %x", got, want)
28 }
29 payload := []byte("nostrino compact filter client")
30 sum := Checksum(payload)
31 full := chainhash.DoubleHashB(payload)
32 if !bytes.Equal(sum[:], full[:4]) {
33 t.Fatalf("Checksum = %x, want first 4 bytes of %x", sum, full)
34 }
35 }
36
37 // TestEncodeMessageLayout pins the four bytes of every network magic as they
38 // appear on the wire. These are the strings Bitcoin prints for each network
39 // read left to right: a node that receives any other order sees an unknown
40 // network and drops the connection before it parses the version payload.
41 func TestEncodeMessageLayout(t *testing.T) {
42 wantMagic := []struct {
43 name string
44 magic uint32
45 bytes []byte
46 }{
47 {"mainnet", MainNet, []byte{0xf9, 0xbe, 0xb4, 0xd9}},
48 {"testnet3", TestNet3, []byte{0x0b, 0x11, 0x09, 0x07}},
49 {"regtest", RegTest, []byte{0xfa, 0xbf, 0xb5, 0xda}},
50 {"signet", SigNet, []byte{0x0a, 0x03, 0xcf, 0x40}},
51 }
52 payload := []byte("hello, wire")
53 for _, network := range wantMagic {
54 t.Run(network.name, func(t *testing.T) {
55 frame, err := EncodeMessage(network.magic, CmdPing, payload)
56 if err != nil {
57 t.Fatalf("EncodeMessage: %v", err)
58 }
59 var want []byte
60 want = append(want, network.bytes...)
61 // "ping" NUL padded to 12 bytes.
62 want = append(want, []byte("ping")...)
63 want = append(want, make([]byte, CommandSize-len("ping"))...)
64 // Payload length, little endian.
65 want = append(want, byte(len(payload)), 0, 0, 0)
66 // Checksum.
67 sum := Checksum(payload)
68 want = append(want, sum[:]...)
69 want = append(want, payload...)
70 if !bytes.Equal(frame, want) {
71 t.Fatalf("frame = %x, want %x", frame, want)
72 }
73 })
74 }
75 }
76
77 // TestTheReversedMagicIsRejected: the pre-fix codec wrote each magic little
78 // endian, which reverses its four bytes on the wire. A round trip through the
79 // broken encoder and decoder agrees with itself, so it proves nothing; this
80 // test decodes the exact bytes the old code produced and requires a refusal.
81 func TestTheReversedMagicIsRejected(t *testing.T) {
82 // A valid mainnet ping, as the fixed codec writes it.
83 good, err := EncodeMessage(MainNet, CmdPing, nil)
84 if err != nil {
85 t.Fatalf("EncodeMessage: %v", err)
86 }
87 if got, want := good[0:4], []byte{0xf9, 0xbe, 0xb4, 0xd9}; !bytes.Equal(got, want) {
88 t.Fatalf("mainnet magic on the wire = %x, want %x", got, want)
89 }
90
91 // The same frame with the magic byte-reversed, which is what the old
92 // little-endian encoder put on the wire.
93 reversed := append([]byte(nil), good...)
94 copy(reversed[0:4], []byte{0xd9, 0xb4, 0xbe, 0xf9})
95
96 if _, err := DecodeMessage(MainNet, bytes.NewReader(reversed)); !errors.Is(err, ErrBadMagic) {
97 t.Fatalf("decoding the reversed mainnet magic: %v, want ErrBadMagic", err)
98 }
99
100 // The same bug on regtest: da b5 bf fa instead of fa bf b5 da.
101 regtest, err := EncodeMessage(RegTest, CmdPing, nil)
102 if err != nil {
103 t.Fatalf("EncodeMessage: %v", err)
104 }
105 if got, want := regtest[0:4], []byte{0xfa, 0xbf, 0xb5, 0xda}; !bytes.Equal(got, want) {
106 t.Fatalf("regtest magic on the wire = %x, want %x", got, want)
107 }
108 reversedRegtest := append([]byte(nil), regtest...)
109 copy(reversedRegtest[0:4], []byte{0xda, 0xb5, 0xbf, 0xfa})
110 if _, err := DecodeMessage(RegTest, bytes.NewReader(reversedRegtest)); !errors.Is(err, ErrBadMagic) {
111 t.Fatalf("decoding the reversed regtest magic: %v, want ErrBadMagic", err)
112 }
113 }
114
115 func TestMessageRoundTrip(t *testing.T) {
116 magics := []uint32{MainNet, TestNet3, RegTest, SigNet}
117 payloads := [][]byte{
118 nil,
119 {},
120 {0x00},
121 bytes.Repeat([]byte{0xab}, 253),
122 bytes.Repeat([]byte{0xcd}, 1000),
123 }
124 for _, magic := range magics {
125 for _, command := range allCommands {
126 for _, payload := range payloads {
127 name := fmt.Sprintf("%08x/%s/%d", magic, command, len(payload))
128 t.Run(name, func(t *testing.T) {
129 frame, err := EncodeMessage(magic, command, payload)
130 if err != nil {
131 t.Fatalf("EncodeMessage: %v", err)
132 }
133 if got, want := len(frame), MessageHeaderSize+len(payload); got != want {
134 t.Fatalf("frame length = %d, want %d", got, want)
135 }
136 msg, err := DecodeMessage(magic, bytes.NewReader(frame))
137 if err != nil {
138 t.Fatalf("DecodeMessage: %v", err)
139 }
140 if msg.Command != command {
141 t.Fatalf("command = %q, want %q", msg.Command, command)
142 }
143 if !bytes.Equal(msg.Payload, payload) {
144 t.Fatalf("payload = %x, want %x", msg.Payload, payload)
145 }
146 })
147 }
148 }
149 }
150 }
151
152 func TestDecodeMessageErrors(t *testing.T) {
153 pingPayload := make([]byte, 8)
154 goodPing, err := EncodeMessage(MainNet, CmdPing, pingPayload)
155 if err != nil {
156 t.Fatalf("EncodeMessage: %v", err)
157 }
158 var zeroCommand [CommandSize]byte
159 var badCharCommand [CommandSize]byte
160 copy(badCharCommand[:], "pi\x01ng")
161
162 tests := []struct {
163 name string
164 frame func() []byte
165 want error
166 }{
167 {
168 name: "empty input",
169 frame: func() []byte { return nil },
170 want: io.EOF,
171 },
172 {
173 name: "bad magic",
174 frame: func() []byte {
175 f := append([]byte(nil), goodPing...)
176 binary.BigEndian.PutUint32(f[0:4], TestNet3)
177 return f
178 },
179 want: ErrBadMagic,
180 },
181 {
182 name: "bad checksum",
183 frame: func() []byte {
184 f, err := EncodeMessage(MainNet, CmdTx, []byte{1, 2, 3})
185 if err != nil {
186 t.Fatalf("EncodeMessage: %v", err)
187 }
188 f[len(f)-1] ^= 0xff
189 return f
190 },
191 want: ErrBadChecksum,
192 },
193 {
194 name: "truncated header",
195 frame: func() []byte { return goodPing[:10] },
196 want: io.ErrUnexpectedEOF,
197 },
198 {
199 name: "truncated payload",
200 frame: func() []byte { return goodPing[:len(goodPing)-1] },
201 want: io.ErrUnexpectedEOF,
202 },
203 {
204 name: "missing payload",
205 frame: func() []byte { return goodPing[:MessageHeaderSize] },
206 want: io.ErrUnexpectedEOF,
207 },
208 {
209 name: "oversize payload",
210 frame: func() []byte {
211 var command [CommandSize]byte
212 copy(command[:], CmdTx)
213 return frameHeader(MainNet, command, MaxPayload+1)
214 },
215 want: ErrPayloadTooLarge,
216 },
217 {
218 name: "all NUL command",
219 frame: func() []byte {
220 return frameHeader(MainNet, zeroCommand, 0)
221 },
222 want: ErrInvalidCommand,
223 },
224 {
225 name: "non-printable command",
226 frame: func() []byte {
227 return frameHeader(MainNet, badCharCommand, 0)
228 },
229 want: ErrInvalidCommand,
230 },
231 }
232 for _, tc := range tests {
233 t.Run(tc.name, func(t *testing.T) {
234 _, err := DecodeMessage(MainNet, bytes.NewReader(tc.frame()))
235 if !errors.Is(err, tc.want) {
236 t.Fatalf("DecodeMessage error = %v, want %v", err, tc.want)
237 }
238 })
239 }
240 }
241
242 func TestDecodeUnknownCommand(t *testing.T) {
243 // Commands the package does not implement must still frame correctly; the
244 // caller decides what to do with them.
245 payload := []byte{0xde, 0xad}
246 frame, err := EncodeMessage(MainNet, "notacommand", payload)
247 if err != nil {
248 t.Fatalf("EncodeMessage: %v", err)
249 }
250 msg, err := DecodeMessage(MainNet, bytes.NewReader(frame))
251 if err != nil {
252 t.Fatalf("DecodeMessage: %v", err)
253 }
254 if msg.Command != "notacommand" {
255 t.Fatalf("command = %q, want %q", msg.Command, "notacommand")
256 }
257 if !bytes.Equal(msg.Payload, payload) {
258 t.Fatalf("payload = %x, want %x", msg.Payload, payload)
259 }
260 }
261
262 func TestEncodeMessageErrors(t *testing.T) {
263 tests := []struct {
264 name string
265 command string
266 payload []byte
267 want error
268 }{
269 {"empty command", "", nil, ErrInvalidCommand},
270 {"command too long", "thirteenbytes", nil, ErrInvalidCommand},
271 {"command with NUL", "pi\x00ng", nil, ErrInvalidCommand},
272 {"command with DEL", "ping\x7f", nil, ErrInvalidCommand},
273 {"command with high bit", "ping\xff", nil, ErrInvalidCommand},
274 {"oversize payload", CmdTx, make([]byte, MaxPayload+1), ErrPayloadTooLarge},
275 }
276 for _, tc := range tests {
277 t.Run(tc.name, func(t *testing.T) {
278 _, err := EncodeMessage(MainNet, tc.command, tc.payload)
279 if !errors.Is(err, tc.want) {
280 t.Fatalf("EncodeMessage error = %v, want %v", err, tc.want)
281 }
282 })
283 }
284 }
285
286 func TestCommandConstants(t *testing.T) {
287 seen := make(map[string]bool, len(allCommands))
288 for _, command := range allCommands {
289 if len(command) == 0 {
290 t.Fatal("empty command constant")
291 }
292 if len(command) > CommandSize {
293 t.Fatalf("command %q is %d bytes, maximum is %d", command, len(command), CommandSize)
294 }
295 for i := 0; i < len(command); i++ {
296 if c := command[i]; c < 0x20 || c > 0x7e {
297 t.Fatalf("command %q contains non-printable byte 0x%02x", command, c)
298 }
299 }
300 if seen[command] {
301 t.Fatalf("duplicate command %q", command)
302 }
303 seen[command] = true
304 frame, err := EncodeMessage(MainNet, command, nil)
305 if err != nil {
306 t.Fatalf("EncodeMessage(%q): %v", command, err)
307 }
308 msg, err := DecodeMessage(MainNet, bytes.NewReader(frame))
309 if err != nil {
310 t.Fatalf("DecodeMessage(%q): %v", command, err)
311 }
312 if msg.Command != command {
313 t.Fatalf("decoded command = %q, want %q", msg.Command, command)
314 }
315 }
316 }
317
318 func TestReader(t *testing.T) {
319 var buf bytes.Buffer
320 var wantCommands []string
321 var wantPayloads [][]byte
322 for i, command := range []string{CmdVersion, CmdVerAck, CmdPing, CmdPong} {
323 payload := bytes.Repeat([]byte{byte(i)}, i)
324 frame, err := EncodeMessage(MainNet, command, payload)
325 if err != nil {
326 t.Fatalf("EncodeMessage: %v", err)
327 }
328 buf.Write(frame)
329 wantCommands = append(wantCommands, command)
330 wantPayloads = append(wantPayloads, payload)
331 }
332 r := NewReader(&buf, MainNet)
333 if r.Magic() != MainNet {
334 t.Fatalf("Magic() = %08x, want %08x", r.Magic(), MainNet)
335 }
336 for i := 0; ; i++ {
337 msg, err := r.Next()
338 if errors.Is(err, io.EOF) {
339 if i != len(wantCommands) {
340 t.Fatalf("stream ended after %d messages, want %d", i, len(wantCommands))
341 }
342 break
343 }
344 if err != nil {
345 t.Fatalf("Next: %v", err)
346 }
347 if i >= len(wantCommands) {
348 t.Fatalf("unexpected extra message %q", msg.Command)
349 }
350 if msg.Command != wantCommands[i] {
351 t.Fatalf("message %d command = %q, want %q", i, msg.Command, wantCommands[i])
352 }
353 if !bytes.Equal(msg.Payload, wantPayloads[i]) {
354 t.Fatalf("message %d payload = %x, want %x", i, msg.Payload, wantPayloads[i])
355 }
356 }
357 }
358
359 func TestReaderCleanEOF(t *testing.T) {
360 r := NewReader(bytes.NewReader(nil), MainNet)
361 if _, err := r.Next(); !errors.Is(err, io.EOF) {
362 t.Fatalf("Next on empty stream = %v, want io.EOF", err)
363 }
364 }
365
366 func TestReaderTruncatedStream(t *testing.T) {
367 frame, err := EncodeMessage(MainNet, CmdPing, make([]byte, 8))
368 if err != nil {
369 t.Fatalf("EncodeMessage: %v", err)
370 }
371 stream := append(append([]byte(nil), frame...), frame[:5]...)
372 r := NewReader(bytes.NewReader(stream), MainNet)
373 if _, err := r.Next(); err != nil {
374 t.Fatalf("first Next: %v", err)
375 }
376 _, err = r.Next()
377 if !errors.Is(err, io.ErrUnexpectedEOF) {
378 t.Fatalf("second Next = %v, want io.ErrUnexpectedEOF", err)
379 }
380 }
381
382 func TestReaderHeaderOnlyFrame(t *testing.T) {
383 // A frame header that declares a payload, followed by end of stream, is
384 // truncation, not a clean end of the message stream.
385 frame, err := EncodeMessage(MainNet, CmdPing, make([]byte, 8))
386 if err != nil {
387 t.Fatalf("EncodeMessage: %v", err)
388 }
389 r := NewReader(bytes.NewReader(frame[:MessageHeaderSize]), MainNet)
390 if _, err := r.Next(); !errors.Is(err, io.ErrUnexpectedEOF) {
391 t.Fatalf("Next = %v, want io.ErrUnexpectedEOF", err)
392 }
393 }
394
395 func TestEncodeMessagePayloadError(t *testing.T) {
396 headers := &Headers{Headers: make([]BlockHeader, MaxBlockHeadersPerMsg+1)}
397 if _, err := EncodeMessagePayload(MainNet, CmdHeaders, headers); !errors.Is(err, ErrTooManyItems) {
398 t.Fatalf("EncodeMessagePayload error = %v, want ErrTooManyItems", err)
399 }
400 }
401
402 func TestMessageDecodeError(t *testing.T) {
403 frame, err := EncodeMessage(MainNet, CmdVerAck, []byte{0x01})
404 if err != nil {
405 t.Fatalf("EncodeMessage: %v", err)
406 }
407 msg, err := DecodeMessage(MainNet, bytes.NewReader(frame))
408 if err != nil {
409 t.Fatalf("DecodeMessage: %v", err)
410 }
411 if err := msg.Decode(&VerAck{}); !errors.Is(err, ErrInvalidPayload) {
412 t.Fatalf("Decode error = %v, want ErrInvalidPayload", err)
413 }
414 }
415
416 // TestThePayloadLimitIsEnforcedBeforeAllocation: a frame that claims more than the cap is
417 // refused from its header alone, so a hostile peer cannot make a block read allocate 32 MiB.
418 func TestThePayloadLimitIsEnforcedBeforeAllocation(t *testing.T) {
419 const magic = RegTest
420
421 header := make([]byte, MessageHeaderSize)
422 putMagic(header[0:4], magic)
423 copy(header[4:4+CommandSize], CmdBlock)
424 binary.LittleEndian.PutUint32(header[16:20], 9<<20)
425
426 if _, err := decodeMessageLimit(magic, bytes.NewReader(header), 8<<20); !errors.Is(err, ErrPayloadTooLarge) {
427 t.Fatalf("a 9 MiB frame under an 8 MiB cap: %v", err)
428 }
429
430 // The reader carries the same limit, which is what the peer uses.
431 reader := NewReader(bytes.NewReader(header), magic)
432 reader.SetLimit(8 << 20)
433
434 if _, err := reader.Next(); !errors.Is(err, ErrPayloadTooLarge) {
435 t.Fatalf("the reader: %v", err)
436 }
437 }
438
439 // TestTheReaderLimitResets: a block read must not lower the cap every other read lives under.
440 func TestTheReaderLimitResets(t *testing.T) {
441 reader := NewReader(bytes.NewReader(nil), 1)
442 reader.SetLimit(8 << 20)
443 reader.SetLimit(MaxPayload)
444
445 if reader.limit != MaxPayload {
446 t.Fatalf("the limit is %d", reader.limit)
447 }
448 }
449