headers_test.go raw

   1  package btcwire
   2  
   3  import (
   4  	"bytes"
   5  	"errors"
   6  	"io"
   7  	"reflect"
   8  	"testing"
   9  
  10  	"git.smesh.lol/nostrino/pkg/crypto/chainhash"
  11  )
  12  
  13  func TestGetHeadersRoundTrip(t *testing.T) {
  14  	want := &GetHeaders{
  15  		ProtocolVersion: ProtocolVersion,
  16  		LocatorHashes:   []chainhash.Hash{testHash(1), testHash(2), testHash(3)},
  17  		StopHash:        testHash(4),
  18  	}
  19  	payload, err := want.EncodePayload()
  20  	if err != nil {
  21  		t.Fatalf("EncodePayload: %v", err)
  22  	}
  23  	// version (4) + count (1) + 3 hashes + stop hash.
  24  	if got, wantLen := len(payload), 4+1+4*chainhash.HashSize; got != wantLen {
  25  		t.Fatalf("payload length = %d, want %d", got, wantLen)
  26  	}
  27  	var got GetHeaders
  28  	if err := got.DecodePayload(payload); err != nil {
  29  		t.Fatalf("DecodePayload: %v", err)
  30  	}
  31  	if !reflect.DeepEqual(&got, want) {
  32  		t.Fatalf("decoded = %+v, want %+v", got, *want)
  33  	}
  34  }
  35  
  36  func TestGetHeadersEmptyLocator(t *testing.T) {
  37  	want := &GetHeaders{ProtocolVersion: ProtocolVersion}
  38  	payload, err := want.EncodePayload()
  39  	if err != nil {
  40  		t.Fatalf("EncodePayload: %v", err)
  41  	}
  42  	var got GetHeaders
  43  	if err := got.DecodePayload(payload); err != nil {
  44  		t.Fatalf("DecodePayload: %v", err)
  45  	}
  46  	if got.ProtocolVersion != want.ProtocolVersion {
  47  		t.Fatalf("ProtocolVersion = %d, want %d", got.ProtocolVersion, want.ProtocolVersion)
  48  	}
  49  	if len(got.LocatorHashes) != 0 {
  50  		t.Fatalf("LocatorHashes = %v, want empty", got.LocatorHashes)
  51  	}
  52  }
  53  
  54  func TestGetHeadersLimits(t *testing.T) {
  55  	tooMany := make([]chainhash.Hash, MaxLocatorHashesPerMsg+1)
  56  	if _, err := (&GetHeaders{LocatorHashes: tooMany}).EncodePayload(); !errors.Is(err, ErrTooManyItems) {
  57  		t.Fatalf("EncodePayload error = %v, want ErrTooManyItems", err)
  58  	}
  59  	// A decoded count above the limit is rejected before any hashes are read.
  60  	payload := []byte{0x00, 0x00, 0x00, 0x00, MaxLocatorHashesPerMsg + 1}
  61  	var got GetHeaders
  62  	if err := got.DecodePayload(payload); !errors.Is(err, ErrTooManyItems) {
  63  		t.Fatalf("DecodePayload error = %v, want ErrTooManyItems", err)
  64  	}
  65  }
  66  
  67  func TestGetHeadersTruncated(t *testing.T) {
  68  	want := &GetHeaders{
  69  		ProtocolVersion: ProtocolVersion,
  70  		LocatorHashes:   []chainhash.Hash{testHash(1), testHash(2)},
  71  		StopHash:        testHash(3),
  72  	}
  73  	payload, err := want.EncodePayload()
  74  	if err != nil {
  75  		t.Fatalf("EncodePayload: %v", err)
  76  	}
  77  	for n := 0; n < len(payload); n++ {
  78  		var got GetHeaders
  79  		if err := got.DecodePayload(payload[:n]); err == nil {
  80  			t.Fatalf("decoding %d of %d bytes succeeded", n, len(payload))
  81  		}
  82  	}
  83  }
  84  
  85  func TestHeadersRoundTrip(t *testing.T) {
  86  	want := &Headers{Headers: []BlockHeader{
  87  		mustGenesisHeader(t),
  88  		{
  89  			Version:    2,
  90  			PrevBlock:  testHash(0x30),
  91  			MerkleRoot: testHash(0x40),
  92  			Timestamp:  1700000000,
  93  			Bits:       0x1d00ffff,
  94  			Nonce:      12345,
  95  		},
  96  	}}
  97  	payload, err := want.EncodePayload()
  98  	if err != nil {
  99  		t.Fatalf("EncodePayload: %v", err)
 100  	}
 101  	// count (1) + one 80-byte header and a zero tx count per entry.
 102  	if got, wantLen := len(payload), 1+len(want.Headers)*(BlockHeaderSize+1); got != wantLen {
 103  		t.Fatalf("payload length = %d, want %d", got, wantLen)
 104  	}
 105  	if payload[1+BlockHeaderSize] != 0x00 {
 106  		t.Fatalf("transaction count byte = %x, want 0", payload[1+BlockHeaderSize])
 107  	}
 108  	var got Headers
 109  	if err := got.DecodePayload(payload); err != nil {
 110  		t.Fatalf("DecodePayload: %v", err)
 111  	}
 112  	if len(got.Headers) != len(want.Headers) {
 113  		t.Fatalf("decoded %d headers, want %d", len(got.Headers), len(want.Headers))
 114  	}
 115  	for i := range want.Headers {
 116  		if got.Headers[i] != want.Headers[i] {
 117  			t.Fatalf("header %d = %+v, want %+v", i, got.Headers[i], want.Headers[i])
 118  		}
 119  	}
 120  }
 121  
 122  func TestHeadersEmpty(t *testing.T) {
 123  	want := &Headers{}
 124  	payload, err := want.EncodePayload()
 125  	if err != nil {
 126  		t.Fatalf("EncodePayload: %v", err)
 127  	}
 128  	if !bytes.Equal(payload, []byte{0x00}) {
 129  		t.Fatalf("payload = %x, want 00", payload)
 130  	}
 131  	var got Headers
 132  	if err := got.DecodePayload(payload); err != nil {
 133  		t.Fatalf("DecodePayload: %v", err)
 134  	}
 135  	if len(got.Headers) != 0 {
 136  		t.Fatalf("decoded %d headers, want 0", len(got.Headers))
 137  	}
 138  }
 139  
 140  func TestHeadersNonZeroTxCount(t *testing.T) {
 141  	genesis := mustGenesisHeader(t)
 142  	raw := genesis.Serialize()
 143  	payload := append([]byte{0x01}, raw[:]...)
 144  	payload = append(payload, 0x01)
 145  	var got Headers
 146  	err := got.DecodePayload(payload)
 147  	if !errors.Is(err, ErrInvalidPayload) {
 148  		t.Fatalf("DecodePayload error = %v, want ErrInvalidPayload", err)
 149  	}
 150  }
 151  
 152  func TestHeadersLimits(t *testing.T) {
 153  	t.Run("encode too many", func(t *testing.T) {
 154  		h := &Headers{Headers: make([]BlockHeader, MaxBlockHeadersPerMsg+1)}
 155  		if _, err := h.EncodePayload(); !errors.Is(err, ErrTooManyItems) {
 156  			t.Fatalf("error = %v, want ErrTooManyItems", err)
 157  		}
 158  	})
 159  	t.Run("decode too many", func(t *testing.T) {
 160  		payload := []byte{0xfd}
 161  		payload = append(payload, byte((MaxBlockHeadersPerMsg+1)&0xff), byte((MaxBlockHeadersPerMsg+1)>>8))
 162  		var got Headers
 163  		if err := got.DecodePayload(payload); !errors.Is(err, ErrTooManyItems) {
 164  			t.Fatalf("error = %v, want ErrTooManyItems", err)
 165  		}
 166  	})
 167  	t.Run("decode exactly the maximum", func(t *testing.T) {
 168  		var e Encoder
 169  		e.CompactSize(MaxBlockHeadersPerMsg)
 170  		genesis := mustGenesisHeader(t)
 171  		raw := genesis.Serialize()
 172  		for i := 0; i < MaxBlockHeadersPerMsg; i++ {
 173  			e.Write(raw[:])
 174  			e.CompactSize(0)
 175  		}
 176  		var got Headers
 177  		if err := got.DecodePayload(e.Bytes()); err != nil {
 178  			t.Fatalf("DecodePayload: %v", err)
 179  		}
 180  		if len(got.Headers) != MaxBlockHeadersPerMsg {
 181  			t.Fatalf("decoded %d headers, want %d", len(got.Headers), MaxBlockHeadersPerMsg)
 182  		}
 183  	})
 184  }
 185  
 186  func TestHeadersTruncated(t *testing.T) {
 187  	want := &Headers{Headers: []BlockHeader{mustGenesisHeader(t)}}
 188  	payload, err := want.EncodePayload()
 189  	if err != nil {
 190  		t.Fatalf("EncodePayload: %v", err)
 191  	}
 192  	for n := 0; n < len(payload); n++ {
 193  		var got Headers
 194  		if err := got.DecodePayload(payload[:n]); err == nil {
 195  			t.Fatalf("decoding %d of %d bytes succeeded", n, len(payload))
 196  		}
 197  	}
 198  	// A count of one with a header but no transaction count is truncated.
 199  	var got Headers
 200  	if err := got.DecodePayload(payload[:len(payload)-1]); !errors.Is(err, io.ErrUnexpectedEOF) {
 201  		t.Fatalf("missing tx count error = %v, want io.ErrUnexpectedEOF", err)
 202  	}
 203  }
 204