cfilter_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 TestFilterHeader(t *testing.T) {
  14  	filterHash := testHash(0x11)
  15  	prevFilterHeader := testHash(0x22)
  16  	got := FilterHeader(filterHash, prevFilterHeader)
  17  
  18  	// SHA256d(filterHash || prevFilterHeader): the new filter hash comes
  19  	// first.
  20  	var concat [2 * chainhash.HashSize]byte
  21  	copy(concat[:chainhash.HashSize], filterHash[:])
  22  	copy(concat[chainhash.HashSize:], prevFilterHeader[:])
  23  	want := chainhash.DoubleHashH(concat[:])
  24  	if got != want {
  25  		t.Fatalf("FilterHeader = %s, want %s", got, want)
  26  	}
  27  
  28  	// The arguments are not interchangeable.
  29  	swapped := FilterHeader(prevFilterHeader, filterHash)
  30  	if swapped == got {
  31  		t.Fatal("FilterHeader is symmetric in its arguments")
  32  	}
  33  	// A tag-style order (previous header first) would produce the swapped
  34  	// value, so verify the concatenation order explicitly.
  35  	var reversed [2 * chainhash.HashSize]byte
  36  	copy(reversed[:chainhash.HashSize], prevFilterHeader[:])
  37  	copy(reversed[chainhash.HashSize:], filterHash[:])
  38  	if want := chainhash.DoubleHashH(reversed[:]); swapped != want {
  39  		t.Fatalf("swapped FilterHeader = %s, want %s", swapped, want)
  40  	}
  41  }
  42  
  43  func TestGetCFHeadersRoundTrip(t *testing.T) {
  44  	want := &GetCFHeaders{
  45  		FilterType:  FilterTypeBasic,
  46  		StartHeight: 700000,
  47  		StopHash:    testHash(0x33),
  48  	}
  49  	payload, err := want.EncodePayload()
  50  	if err != nil {
  51  		t.Fatalf("EncodePayload: %v", err)
  52  	}
  53  	if got, wantLen := len(payload), 1+4+chainhash.HashSize; got != wantLen {
  54  		t.Fatalf("payload length = %d, want %d", got, wantLen)
  55  	}
  56  	if payload[0] != FilterTypeBasic {
  57  		t.Fatalf("filter type byte = %d, want %d", payload[0], FilterTypeBasic)
  58  	}
  59  	// Start height, little endian.
  60  	if !bytes.Equal(payload[1:5], []byte{0x60, 0xae, 0x0a, 0x00}) {
  61  		t.Fatalf("start height bytes = %x, want 60ae0a00", payload[1:5])
  62  	}
  63  	var got GetCFHeaders
  64  	if err := got.DecodePayload(payload); err != nil {
  65  		t.Fatalf("DecodePayload: %v", err)
  66  	}
  67  	if !reflect.DeepEqual(&got, want) {
  68  		t.Fatalf("decoded = %+v, want %+v", got, *want)
  69  	}
  70  }
  71  
  72  func TestCFHeadersRoundTrip(t *testing.T) {
  73  	tests := []struct {
  74  		name   string
  75  		hashes []chainhash.Hash
  76  	}{
  77  		{"empty", []chainhash.Hash{}},
  78  		{"one", []chainhash.Hash{testHash(0x44)}},
  79  		{"several", []chainhash.Hash{testHash(0x45), testHash(0x46), testHash(0x47)}},
  80  	}
  81  	for _, tc := range tests {
  82  		t.Run(tc.name, func(t *testing.T) {
  83  			want := &CFHeaders{
  84  				FilterType:       FilterTypeBasic,
  85  				StopHash:         testHash(0x48),
  86  				PrevFilterHeader: testHash(0x49),
  87  				FilterHashes:     tc.hashes,
  88  			}
  89  			payload, err := want.EncodePayload()
  90  			if err != nil {
  91  				t.Fatalf("EncodePayload: %v", err)
  92  			}
  93  			if got, wantLen := len(payload), 1+2*chainhash.HashSize+1+len(tc.hashes)*chainhash.HashSize; got != wantLen {
  94  				t.Fatalf("payload length = %d, want %d", got, wantLen)
  95  			}
  96  			var got CFHeaders
  97  			if err := got.DecodePayload(payload); err != nil {
  98  				t.Fatalf("DecodePayload: %v", err)
  99  			}
 100  			if !reflect.DeepEqual(&got, want) {
 101  				t.Fatalf("decoded = %+v, want %+v", got, *want)
 102  			}
 103  		})
 104  	}
 105  }
 106  
 107  func TestCFHeadersLimits(t *testing.T) {
 108  	h := &CFHeaders{FilterHashes: make([]chainhash.Hash, MaxFilterHashesPerMsg+1)}
 109  	if _, err := h.EncodePayload(); !errors.Is(err, ErrTooManyItems) {
 110  		t.Fatalf("EncodePayload error = %v, want ErrTooManyItems", err)
 111  	}
 112  	// 2001 encoded as 0xfd 0xd1 0x07.
 113  	payload := []byte{FilterTypeBasic}
 114  	payload = append(payload, make([]byte, 2*chainhash.HashSize)...)
 115  	payload = append(payload, 0xfd, byte((MaxFilterHashesPerMsg+1)&0xff), byte((MaxFilterHashesPerMsg+1)>>8))
 116  	var got CFHeaders
 117  	if err := got.DecodePayload(payload); !errors.Is(err, ErrTooManyItems) {
 118  		t.Fatalf("DecodePayload error = %v, want ErrTooManyItems", err)
 119  	}
 120  }
 121  
 122  func TestCFHeadersTruncated(t *testing.T) {
 123  	want := &CFHeaders{
 124  		FilterType:       FilterTypeBasic,
 125  		StopHash:         testHash(0x50),
 126  		PrevFilterHeader: testHash(0x51),
 127  		FilterHashes:     []chainhash.Hash{testHash(0x52), testHash(0x53)},
 128  	}
 129  	payload, err := want.EncodePayload()
 130  	if err != nil {
 131  		t.Fatalf("EncodePayload: %v", err)
 132  	}
 133  	for n := 0; n < len(payload); n++ {
 134  		var got CFHeaders
 135  		if err := got.DecodePayload(payload[:n]); err == nil {
 136  			t.Fatalf("decoding %d of %d bytes succeeded", n, len(payload))
 137  		}
 138  	}
 139  }
 140  
 141  func TestGetCFiltersRoundTrip(t *testing.T) {
 142  	want := &GetCFilters{
 143  		FilterType:  FilterTypeBasic,
 144  		StartHeight: 1,
 145  		StopHash:    testHash(0x54),
 146  	}
 147  	payload, err := want.EncodePayload()
 148  	if err != nil {
 149  		t.Fatalf("EncodePayload: %v", err)
 150  	}
 151  	if got, wantLen := len(payload), 1+4+chainhash.HashSize; got != wantLen {
 152  		t.Fatalf("payload length = %d, want %d", got, wantLen)
 153  	}
 154  	var got GetCFilters
 155  	if err := got.DecodePayload(payload); err != nil {
 156  		t.Fatalf("DecodePayload: %v", err)
 157  	}
 158  	if !reflect.DeepEqual(&got, want) {
 159  		t.Fatalf("decoded = %+v, want %+v", got, *want)
 160  	}
 161  }
 162  
 163  func TestCFilterRoundTrip(t *testing.T) {
 164  	tests := []struct {
 165  		name   string
 166  		filter []byte
 167  	}{
 168  		{"empty filter", []byte{}},
 169  		{"header only", []byte{0x00}},
 170  		{"basic filter", append([]byte{0x01, 0x02}, bytes.Repeat([]byte{0x99}, 100)...)},
 171  	}
 172  	for _, tc := range tests {
 173  		t.Run(tc.name, func(t *testing.T) {
 174  			want := &CFilter{
 175  				FilterType: FilterTypeBasic,
 176  				BlockHash:  testHash(0x55),
 177  				Filter:     tc.filter,
 178  			}
 179  			payload, err := want.EncodePayload()
 180  			if err != nil {
 181  				t.Fatalf("EncodePayload: %v", err)
 182  			}
 183  			if got, wantLen := len(payload), 1+chainhash.HashSize+1+len(tc.filter); got != wantLen {
 184  				t.Fatalf("payload length = %d, want %d", got, wantLen)
 185  			}
 186  			var got CFilter
 187  			if err := got.DecodePayload(payload); err != nil {
 188  				t.Fatalf("DecodePayload: %v", err)
 189  			}
 190  			if got.FilterType != want.FilterType || got.BlockHash != want.BlockHash {
 191  				t.Fatalf("decoded = %+v, want %+v", got, *want)
 192  			}
 193  			if !bytes.Equal(got.Filter, tc.filter) {
 194  				t.Fatalf("filter = %x, want %x", got.Filter, tc.filter)
 195  			}
 196  		})
 197  	}
 198  }
 199  
 200  func TestCFilterTruncated(t *testing.T) {
 201  	want := &CFilter{FilterType: FilterTypeBasic, BlockHash: testHash(0x56), Filter: []byte{1, 2, 3}}
 202  	payload, err := want.EncodePayload()
 203  	if err != nil {
 204  		t.Fatalf("EncodePayload: %v", err)
 205  	}
 206  	for n := 0; n < len(payload); n++ {
 207  		var got CFilter
 208  		if err := got.DecodePayload(payload[:n]); err == nil {
 209  			t.Fatalf("decoding %d of %d bytes succeeded", n, len(payload))
 210  		}
 211  	}
 212  	// A declared filter length longer than the remaining bytes is truncated.
 213  	payload[1+chainhash.HashSize] = 0xff
 214  	var got CFilter
 215  	if err := got.DecodePayload(payload); !errors.Is(err, io.ErrUnexpectedEOF) {
 216  		t.Fatalf("error = %v, want io.ErrUnexpectedEOF", err)
 217  	}
 218  }
 219  
 220  func TestGetCFCheckptRoundTrip(t *testing.T) {
 221  	want := &GetCFCheckpt{FilterType: FilterTypeBasic, StopHash: testHash(0x57)}
 222  	payload, err := want.EncodePayload()
 223  	if err != nil {
 224  		t.Fatalf("EncodePayload: %v", err)
 225  	}
 226  	if got, wantLen := len(payload), 1+chainhash.HashSize; got != wantLen {
 227  		t.Fatalf("payload length = %d, want %d", got, wantLen)
 228  	}
 229  	var got GetCFCheckpt
 230  	if err := got.DecodePayload(payload); err != nil {
 231  		t.Fatalf("DecodePayload: %v", err)
 232  	}
 233  	if !reflect.DeepEqual(&got, want) {
 234  		t.Fatalf("decoded = %+v, want %+v", got, *want)
 235  	}
 236  }
 237  
 238  func TestCFCheckptRoundTrip(t *testing.T) {
 239  	tests := []struct {
 240  		name    string
 241  		headers []chainhash.Hash
 242  	}{
 243  		{"empty", []chainhash.Hash{}},
 244  		{"one", []chainhash.Hash{testHash(0x58)}},
 245  		{"two", []chainhash.Hash{testHash(0x59), testHash(0x5a)}},
 246  	}
 247  	for _, tc := range tests {
 248  		t.Run(tc.name, func(t *testing.T) {
 249  			want := &CFCheckpt{
 250  				FilterType:    FilterTypeBasic,
 251  				StopHash:      testHash(0x5b),
 252  				FilterHeaders: tc.headers,
 253  			}
 254  			payload, err := want.EncodePayload()
 255  			if err != nil {
 256  				t.Fatalf("EncodePayload: %v", err)
 257  			}
 258  			if got, wantLen := len(payload), 1+chainhash.HashSize+1+len(tc.headers)*chainhash.HashSize; got != wantLen {
 259  				t.Fatalf("payload length = %d, want %d", got, wantLen)
 260  			}
 261  			var got CFCheckpt
 262  			if err := got.DecodePayload(payload); err != nil {
 263  				t.Fatalf("DecodePayload: %v", err)
 264  			}
 265  			if !reflect.DeepEqual(&got, want) {
 266  				t.Fatalf("decoded = %+v, want %+v", got, *want)
 267  			}
 268  		})
 269  	}
 270  }
 271  
 272  func TestCFCheckptTruncated(t *testing.T) {
 273  	want := &CFCheckpt{
 274  		FilterType:    FilterTypeBasic,
 275  		StopHash:      testHash(0x5c),
 276  		FilterHeaders: []chainhash.Hash{testHash(0x5d)},
 277  	}
 278  	payload, err := want.EncodePayload()
 279  	if err != nil {
 280  		t.Fatalf("EncodePayload: %v", err)
 281  	}
 282  	for n := 0; n < len(payload); n++ {
 283  		var got CFCheckpt
 284  		if err := got.DecodePayload(payload[:n]); err == nil {
 285  			t.Fatalf("decoding %d of %d bytes succeeded", n, len(payload))
 286  		}
 287  	}
 288  }
 289  
 290  func TestFilterTypeNotValidated(t *testing.T) {
 291  	// Unknown filter types are carried through unchanged; rejecting them is
 292  	// the caller's decision.
 293  	const unknown uint8 = 42
 294  	payload, err := (&GetCFCheckpt{FilterType: unknown, StopHash: testHash(0x5e)}).EncodePayload()
 295  	if err != nil {
 296  		t.Fatalf("EncodePayload: %v", err)
 297  	}
 298  	var got GetCFCheckpt
 299  	if err := got.DecodePayload(payload); err != nil {
 300  		t.Fatalf("DecodePayload: %v", err)
 301  	}
 302  	if got.FilterType != unknown {
 303  		t.Fatalf("FilterType = %d, want %d", got.FilterType, unknown)
 304  	}
 305  }
 306