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