package btcwire import ( "bytes" "errors" "math" "reflect" "testing" ) func TestVersionRoundTrip(t *testing.T) { tests := []struct { name string recv string from string ua string relay bool }{ {"ipv4", "127.0.0.1", "10.0.0.1", "Satoshi:27.0.0", true}, {"ipv6", "2001:db8::1", "::1", "nostrino:0.1.0", false}, {"mixed", "::ffff:192.168.1.1", "192.168.1.2", "Satoshi:25.0.0/", true}, {"empty user agent", "0.0.0.0", "0.0.0.0", "", true}, {"long user agent", "127.0.0.1", "127.0.0.1", string(bytes.Repeat([]byte("x"), MaxUserAgentSize)), false}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { want := Version{ ProtocolVersion: ProtocolVersion, Services: ServiceNodeNetwork | ServiceNodeWitness | ServiceNodeCompactFilters, Timestamp: 1700000000, AddrRecv: mustAddress(t, tc.recv, 8333), AddrFrom: mustAddress(t, tc.from, 18333), Nonce: 0x0102030405060708, UserAgent: tc.ua, LastBlock: 800000, Relay: tc.relay, } payload, err := want.EncodePayload() if err != nil { t.Fatalf("EncodePayload: %v", err) } var got Version if err := got.DecodePayload(payload); err != nil { t.Fatalf("DecodePayload: %v", err) } if !reflect.DeepEqual(got, want) { t.Fatalf("decoded = %+v, want %+v", got, want) } }) } } func TestVersionWireLayout(t *testing.T) { v := Version{ ProtocolVersion: ProtocolVersion, Services: ServiceNodeNetwork, Timestamp: 1700000000, AddrRecv: mustAddress(t, "127.0.0.1", 8333), AddrFrom: mustAddress(t, "1.2.3.4", 18333), Nonce: 0x0102030405060708, UserAgent: "abc", LastBlock: 100, Relay: true, } payload, err := v.EncodePayload() if err != nil { t.Fatalf("EncodePayload: %v", err) } // 4 + 8 + 8 + 26 + 26 + 8 + 1 + 3 + 4 + 1. if got, want := len(payload), 89; got != want { t.Fatalf("payload length = %d, want %d", got, want) } checks := []struct { name string got []byte want []byte }{ {"protocol version", payload[0:4], []byte{0x80, 0x11, 0x01, 0x00}}, {"services", payload[4:12], []byte{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, {"timestamp", payload[12:20], []byte{0x00, 0xf1, 0x53, 0x65, 0x00, 0x00, 0x00, 0x00}}, {"addr recv port", payload[44:46], []byte{0x20, 0x8d}}, {"addr from port", payload[70:72], []byte{0x47, 0x9d}}, {"nonce", payload[72:80], []byte{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01}}, {"user agent length", payload[80:81], []byte{0x03}}, {"user agent", payload[81:84], []byte("abc")}, {"last block", payload[84:88], []byte{0x64, 0x00, 0x00, 0x00}}, {"relay", payload[88:89], []byte{0x01}}, } for _, c := range checks { if !bytes.Equal(c.got, c.want) { t.Fatalf("%s bytes = %x, want %x", c.name, c.got, c.want) } } // 8333 = 0x208d and 18333 = 0x479d, big endian on the wire. if got := payload[28:44]; !bytes.Equal(got, []byte{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x7f, 0x00, 0x00, 0x01, }) { t.Fatalf("addr recv IP = %x", got) } } func TestVersionDecodeTruncated(t *testing.T) { v := Version{ ProtocolVersion: ProtocolVersion, Services: ServiceNodeNetwork, Timestamp: 1700000000, AddrRecv: mustAddress(t, "127.0.0.1", 8333), AddrFrom: mustAddress(t, "127.0.0.1", 8333), Nonce: 7, UserAgent: "abc", LastBlock: 42, Relay: true, } payload, err := v.EncodePayload() if err != nil { t.Fatalf("EncodePayload: %v", err) } // Everything before the optional relay byte is required. required := len(payload) - 1 for n := 0; n < required; n++ { var got Version if err := got.DecodePayload(payload[:n]); err == nil { t.Fatalf("decoding %d of %d bytes succeeded", n, len(payload)) } } // The relay byte is optional: without it the message is still valid and // relay defaults to true. var got Version if err := got.DecodePayload(payload[:required]); err != nil { t.Fatalf("decoding without relay byte: %v", err) } if !got.Relay { t.Fatal("missing relay byte did not default to true") } if got.UserAgent != "abc" || got.LastBlock != 42 || got.Nonce != 7 { t.Fatalf("decoded fields = %+v", got) } } func TestVersionRelayDecoding(t *testing.T) { v := Version{ ProtocolVersion: ProtocolVersion, UserAgent: "ua", LastBlock: 1, } payload, err := v.EncodePayload() if err != nil { t.Fatalf("EncodePayload: %v", err) } payload[len(payload)-1] = 0 var got Version if err := got.DecodePayload(payload); err != nil { t.Fatalf("DecodePayload: %v", err) } if got.Relay { t.Fatal("relay byte 0 decoded as true") } // Any non-zero byte is treated as true, matching Bitcoin Core. payload[len(payload)-1] = 2 if err := got.DecodePayload(payload); err != nil { t.Fatalf("DecodePayload: %v", err) } if !got.Relay { t.Fatal("relay byte 2 decoded as false") } } func TestVersionUserAgentLimit(t *testing.T) { t.Run("encode rejects oversize", func(t *testing.T) { v := &Version{UserAgent: string(bytes.Repeat([]byte("x"), MaxUserAgentSize+1))} if _, err := v.EncodePayload(); !errors.Is(err, ErrInvalidPayload) { t.Fatalf("error = %v, want ErrInvalidPayload", err) } }) t.Run("decode rejects oversize", func(t *testing.T) { // Fixed fields, then a 257-byte user agent, then the last block and // relay byte. payload := make([]byte, 80) payload = append(payload, 0xfd, 0x01, 0x01) payload = append(payload, bytes.Repeat([]byte("x"), 257)...) payload = append(payload, 0, 0, 0, 0, 0) var got Version if err := got.DecodePayload(payload); !errors.Is(err, ErrVarBytesTooLarge) { t.Fatalf("error = %v, want ErrVarBytesTooLarge", err) } }) } func TestEmptyPayloadMessages(t *testing.T) { tests := []struct { name string value Payload }{ {"verack", &VerAck{}}, {"sendheaders", &SendHeaders{}}, {"wtxidrelay", &WtxidRelay{}}, {"sendaddrv2", &SendAddrV2{}}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { payload, err := tc.value.EncodePayload() if err != nil { t.Fatalf("EncodePayload: %v", err) } if len(payload) != 0 { t.Fatalf("payload = %x, want empty", payload) } if err := tc.value.DecodePayload(nil); err != nil { t.Fatalf("DecodePayload(nil): %v", err) } if err := tc.value.DecodePayload([]byte{0x00}); !errors.Is(err, ErrInvalidPayload) { t.Fatalf("DecodePayload([0]) error = %v, want ErrInvalidPayload", err) } }) } } func TestPingPong(t *testing.T) { nonces := []uint64{0, 1, 0xffffffffffffffff} for _, nonce := range nonces { t.Run("ping", func(t *testing.T) { want := &Ping{Nonce: nonce} payload, err := want.EncodePayload() if err != nil { t.Fatalf("EncodePayload: %v", err) } if len(payload) != 8 { t.Fatalf("payload length = %d, want 8", len(payload)) } var got Ping if err := got.DecodePayload(payload); err != nil { t.Fatalf("DecodePayload: %v", err) } if got.Nonce != nonce { t.Fatalf("Nonce = %d, want %d", got.Nonce, nonce) } }) t.Run("pong", func(t *testing.T) { want := &Pong{Nonce: nonce} payload, err := want.EncodePayload() if err != nil { t.Fatalf("EncodePayload: %v", err) } var got Pong if err := got.DecodePayload(payload); err != nil { t.Fatalf("DecodePayload: %v", err) } if got.Nonce != nonce { t.Fatalf("Nonce = %d, want %d", got.Nonce, nonce) } }) } for _, n := range []int{0, 7, 9} { var p Ping if err := p.DecodePayload(make([]byte, n)); !errors.Is(err, ErrInvalidPayload) { t.Fatalf("ping payload of %d bytes error = %v, want ErrInvalidPayload", n, err) } var q Pong if err := q.DecodePayload(make([]byte, n)); !errors.Is(err, ErrInvalidPayload) { t.Fatalf("pong payload of %d bytes error = %v, want ErrInvalidPayload", n, err) } } } func TestFeeFilter(t *testing.T) { rates := []int64{0, 1, 1000, -1, math.MaxInt64, math.MinInt64} for _, rate := range rates { want := &FeeFilter{FeeRate: rate} payload, err := want.EncodePayload() if err != nil { t.Fatalf("EncodePayload: %v", err) } if len(payload) != 8 { t.Fatalf("payload length = %d, want 8", len(payload)) } var got FeeFilter if err := got.DecodePayload(payload); err != nil { t.Fatalf("DecodePayload: %v", err) } if got.FeeRate != rate { t.Fatalf("FeeRate = %d, want %d", got.FeeRate, rate) } } var f FeeFilter if err := f.DecodePayload(make([]byte, 4)); !errors.Is(err, ErrInvalidPayload) { t.Fatalf("short payload error = %v, want ErrInvalidPayload", err) } if err := f.DecodePayload(make([]byte, 9)); !errors.Is(err, ErrInvalidPayload) { t.Fatalf("long payload error = %v, want ErrInvalidPayload", err) } } func TestProtocolConstants(t *testing.T) { if ProtocolVersion != 70016 { t.Fatalf("ProtocolVersion = %d, want 70016", ProtocolVersion) } if ServiceNodeNetwork != 1 { t.Fatalf("ServiceNodeNetwork = %d, want 1", ServiceNodeNetwork) } if ServiceNodeWitness != 1<<3 { t.Fatalf("ServiceNodeWitness = %d, want %d", ServiceNodeWitness, 1<<3) } if ServiceNodeCompactFilters != 1<<6 { t.Fatalf("ServiceNodeCompactFilters = %d, want %d", ServiceNodeCompactFilters, 1<<6) } } func TestVersionFrameRoundTrip(t *testing.T) { v := &Version{ ProtocolVersion: ProtocolVersion, Services: ServiceNodeCompactFilters, Timestamp: 1, Nonce: 2, UserAgent: "Satoshi:27.0.0", LastBlock: 3, Relay: true, } frame, err := EncodeMessagePayload(MainNet, CmdVersion, v) if err != nil { t.Fatalf("EncodeMessagePayload: %v", err) } msg, err := DecodeMessage(MainNet, bytes.NewReader(frame)) if err != nil { t.Fatalf("DecodeMessage: %v", err) } var got Version if err := msg.Decode(&got); err != nil { t.Fatalf("Decode: %v", err) } if !reflect.DeepEqual(got, *v) { t.Fatalf("decoded = %+v, want %+v", got, *v) } }