package btcwire import ( "bytes" "errors" "io" "reflect" "testing" "git.smesh.lol/nostrino/pkg/crypto/chainhash" ) func TestGetHeadersRoundTrip(t *testing.T) { want := &GetHeaders{ ProtocolVersion: ProtocolVersion, LocatorHashes: []chainhash.Hash{testHash(1), testHash(2), testHash(3)}, StopHash: testHash(4), } payload, err := want.EncodePayload() if err != nil { t.Fatalf("EncodePayload: %v", err) } // version (4) + count (1) + 3 hashes + stop hash. if got, wantLen := len(payload), 4+1+4*chainhash.HashSize; got != wantLen { t.Fatalf("payload length = %d, want %d", got, wantLen) } var got GetHeaders 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 TestGetHeadersEmptyLocator(t *testing.T) { want := &GetHeaders{ProtocolVersion: ProtocolVersion} payload, err := want.EncodePayload() if err != nil { t.Fatalf("EncodePayload: %v", err) } var got GetHeaders if err := got.DecodePayload(payload); err != nil { t.Fatalf("DecodePayload: %v", err) } if got.ProtocolVersion != want.ProtocolVersion { t.Fatalf("ProtocolVersion = %d, want %d", got.ProtocolVersion, want.ProtocolVersion) } if len(got.LocatorHashes) != 0 { t.Fatalf("LocatorHashes = %v, want empty", got.LocatorHashes) } } func TestGetHeadersLimits(t *testing.T) { tooMany := make([]chainhash.Hash, MaxLocatorHashesPerMsg+1) if _, err := (&GetHeaders{LocatorHashes: tooMany}).EncodePayload(); !errors.Is(err, ErrTooManyItems) { t.Fatalf("EncodePayload error = %v, want ErrTooManyItems", err) } // A decoded count above the limit is rejected before any hashes are read. payload := []byte{0x00, 0x00, 0x00, 0x00, MaxLocatorHashesPerMsg + 1} var got GetHeaders if err := got.DecodePayload(payload); !errors.Is(err, ErrTooManyItems) { t.Fatalf("DecodePayload error = %v, want ErrTooManyItems", err) } } func TestGetHeadersTruncated(t *testing.T) { want := &GetHeaders{ ProtocolVersion: ProtocolVersion, LocatorHashes: []chainhash.Hash{testHash(1), testHash(2)}, StopHash: testHash(3), } payload, err := want.EncodePayload() if err != nil { t.Fatalf("EncodePayload: %v", err) } for n := 0; n < len(payload); n++ { var got GetHeaders if err := got.DecodePayload(payload[:n]); err == nil { t.Fatalf("decoding %d of %d bytes succeeded", n, len(payload)) } } } func TestHeadersRoundTrip(t *testing.T) { want := &Headers{Headers: []BlockHeader{ mustGenesisHeader(t), { Version: 2, PrevBlock: testHash(0x30), MerkleRoot: testHash(0x40), Timestamp: 1700000000, Bits: 0x1d00ffff, Nonce: 12345, }, }} payload, err := want.EncodePayload() if err != nil { t.Fatalf("EncodePayload: %v", err) } // count (1) + one 80-byte header and a zero tx count per entry. if got, wantLen := len(payload), 1+len(want.Headers)*(BlockHeaderSize+1); got != wantLen { t.Fatalf("payload length = %d, want %d", got, wantLen) } if payload[1+BlockHeaderSize] != 0x00 { t.Fatalf("transaction count byte = %x, want 0", payload[1+BlockHeaderSize]) } var got Headers if err := got.DecodePayload(payload); err != nil { t.Fatalf("DecodePayload: %v", err) } if len(got.Headers) != len(want.Headers) { t.Fatalf("decoded %d headers, want %d", len(got.Headers), len(want.Headers)) } for i := range want.Headers { if got.Headers[i] != want.Headers[i] { t.Fatalf("header %d = %+v, want %+v", i, got.Headers[i], want.Headers[i]) } } } func TestHeadersEmpty(t *testing.T) { want := &Headers{} payload, err := want.EncodePayload() if err != nil { t.Fatalf("EncodePayload: %v", err) } if !bytes.Equal(payload, []byte{0x00}) { t.Fatalf("payload = %x, want 00", payload) } var got Headers if err := got.DecodePayload(payload); err != nil { t.Fatalf("DecodePayload: %v", err) } if len(got.Headers) != 0 { t.Fatalf("decoded %d headers, want 0", len(got.Headers)) } } func TestHeadersNonZeroTxCount(t *testing.T) { genesis := mustGenesisHeader(t) raw := genesis.Serialize() payload := append([]byte{0x01}, raw[:]...) payload = append(payload, 0x01) var got Headers err := got.DecodePayload(payload) if !errors.Is(err, ErrInvalidPayload) { t.Fatalf("DecodePayload error = %v, want ErrInvalidPayload", err) } } func TestHeadersLimits(t *testing.T) { t.Run("encode too many", func(t *testing.T) { h := &Headers{Headers: make([]BlockHeader, MaxBlockHeadersPerMsg+1)} if _, err := h.EncodePayload(); !errors.Is(err, ErrTooManyItems) { t.Fatalf("error = %v, want ErrTooManyItems", err) } }) t.Run("decode too many", func(t *testing.T) { payload := []byte{0xfd} payload = append(payload, byte((MaxBlockHeadersPerMsg+1)&0xff), byte((MaxBlockHeadersPerMsg+1)>>8)) var got Headers if err := got.DecodePayload(payload); !errors.Is(err, ErrTooManyItems) { t.Fatalf("error = %v, want ErrTooManyItems", err) } }) t.Run("decode exactly the maximum", func(t *testing.T) { var e Encoder e.CompactSize(MaxBlockHeadersPerMsg) genesis := mustGenesisHeader(t) raw := genesis.Serialize() for i := 0; i < MaxBlockHeadersPerMsg; i++ { e.Write(raw[:]) e.CompactSize(0) } var got Headers if err := got.DecodePayload(e.Bytes()); err != nil { t.Fatalf("DecodePayload: %v", err) } if len(got.Headers) != MaxBlockHeadersPerMsg { t.Fatalf("decoded %d headers, want %d", len(got.Headers), MaxBlockHeadersPerMsg) } }) } func TestHeadersTruncated(t *testing.T) { want := &Headers{Headers: []BlockHeader{mustGenesisHeader(t)}} payload, err := want.EncodePayload() if err != nil { t.Fatalf("EncodePayload: %v", err) } for n := 0; n < len(payload); n++ { var got Headers if err := got.DecodePayload(payload[:n]); err == nil { t.Fatalf("decoding %d of %d bytes succeeded", n, len(payload)) } } // A count of one with a header but no transaction count is truncated. var got Headers if err := got.DecodePayload(payload[:len(payload)-1]); !errors.Is(err, io.ErrUnexpectedEOF) { t.Fatalf("missing tx count error = %v, want io.ErrUnexpectedEOF", err) } }