blockheader_test.go raw
1 package btcwire
2
3 import (
4 "bytes"
5 "encoding/hex"
6 "errors"
7 "io"
8 "math/big"
9 "testing"
10
11 "git.smesh.lol/nostrino/pkg/crypto/chainhash"
12 )
13
14 // Bitcoin mainnet genesis block header and its display-order hash.
15 const (
16 genesisHeaderHex = "0100000000000000000000000000000000000000000000000000000000000000" +
17 "000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a" +
18 "29ab5f49ffff001d1dac2b7c"
19 genesisHashDisplay = "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
20 )
21
22 // mustHex decodes s or fails the test.
23 func mustHex(t *testing.T, s string) []byte {
24 t.Helper()
25 b, err := hex.DecodeString(s)
26 if err != nil {
27 t.Fatalf("decoding hex: %v", err)
28 }
29 return b
30 }
31
32 // mustGenesisHeader returns the parsed mainnet genesis header.
33 func mustGenesisHeader(t *testing.T) BlockHeader {
34 t.Helper()
35 raw := mustHex(t, genesisHeaderHex)
36 if len(raw) != BlockHeaderSize {
37 t.Fatalf("genesis header is %d bytes, want %d", len(raw), BlockHeaderSize)
38 }
39 var h BlockHeader
40 if err := h.Deserialize(raw); err != nil {
41 t.Fatalf("Deserialize: %v", err)
42 }
43 return h
44 }
45
46 func TestGenesisHeader(t *testing.T) {
47 raw := mustHex(t, genesisHeaderHex)
48 h := mustGenesisHeader(t)
49
50 if h.Version != 1 {
51 t.Fatalf("Version = %d, want 1", h.Version)
52 }
53 if h.PrevBlock != (chainhash.Hash{}) {
54 t.Fatalf("PrevBlock = %s, want zero", h.PrevBlock)
55 }
56 if h.Timestamp != 1231006505 {
57 t.Fatalf("Timestamp = %d, want 1231006505", h.Timestamp)
58 }
59 if h.Bits != 0x1d00ffff {
60 t.Fatalf("Bits = %08x, want 1d00ffff", h.Bits)
61 }
62 if h.Nonce != 2083236893 {
63 t.Fatalf("Nonce = %d, want 2083236893", h.Nonce)
64 }
65 serialized := h.Serialize()
66 if !bytes.Equal(serialized[:], raw) {
67 t.Fatalf("Serialize = %x, want %x", serialized, raw)
68 }
69 if got := h.BlockHash().String(); got != genesisHashDisplay {
70 t.Fatalf("BlockHash = %s, want %s", got, genesisHashDisplay)
71 }
72 if !h.CheckProofOfWork() {
73 t.Fatal("genesis header failed proof-of-work check")
74 }
75 target, err := h.Target()
76 if err != nil {
77 t.Fatalf("Target: %v", err)
78 }
79 wantTarget := new(big.Int).Lsh(big.NewInt(0xffff), 208)
80 if target.Cmp(wantTarget) != 0 {
81 t.Fatalf("Target = %x, want %x", target, wantTarget)
82 }
83 }
84
85 func TestGenesisHeaderMutationsFailPoW(t *testing.T) {
86 tests := []struct {
87 name string
88 mutate func(*BlockHeader)
89 }{
90 {"nonce zero", func(h *BlockHeader) { h.Nonce = 0 }},
91 {"nonce incremented", func(h *BlockHeader) { h.Nonce++ }},
92 {"merkle root flipped", func(h *BlockHeader) { h.MerkleRoot[0] ^= 0x01 }},
93 {"timestamp incremented", func(h *BlockHeader) { h.Timestamp++ }},
94 {"version incremented", func(h *BlockHeader) { h.Version++ }},
95 }
96 for _, tc := range tests {
97 t.Run(tc.name, func(t *testing.T) {
98 h := mustGenesisHeader(t)
99 tc.mutate(&h)
100 original := mustGenesisHeader(t)
101 if h.BlockHash() == original.BlockHash() {
102 t.Fatal("mutation did not change the block hash")
103 }
104 if h.CheckProofOfWork() {
105 t.Fatal("mutated header passed proof-of-work check")
106 }
107 })
108 }
109 }
110
111 func TestGenesisHeaderHashToBig(t *testing.T) {
112 h := mustGenesisHeader(t)
113 hash := h.BlockHash()
114 got := HashToBig(&hash)
115 want, ok := new(big.Int).SetString(genesisHashDisplay, 16)
116 if !ok {
117 t.Fatal("parsing expected hash")
118 }
119 if got.Cmp(want) != 0 {
120 t.Fatalf("HashToBig = %x, want %x", got, want)
121 }
122 // The internal hash bytes are the reverse of the display order, so the
123 // final byte of the internal hash is zero and the first is not.
124 if hash[chainhash.HashSize-1] != 0x00 || hash[0] == 0x00 {
125 t.Fatalf("internal hash byte order looks wrong: %x", hash)
126 }
127 }
128
129 func TestBlockHeaderSerializeLayout(t *testing.T) {
130 h := BlockHeader{
131 Version: 0x01020304,
132 PrevBlock: testHash(0x10),
133 MerkleRoot: testHash(0x20),
134 Timestamp: 0x05060708,
135 Bits: 0x090a0b0c,
136 Nonce: 0x0d0e0f10,
137 }
138 raw := h.Serialize()
139 if got, want := raw[0:4], []byte{0x04, 0x03, 0x02, 0x01}; !bytes.Equal(got, want) {
140 t.Fatalf("version bytes = %x, want %x", got, want)
141 }
142 if got := raw[4:36]; !bytes.Equal(got, h.PrevBlock[:]) {
143 t.Fatalf("prev block bytes = %x, want %x", got, h.PrevBlock)
144 }
145 if got := raw[36:68]; !bytes.Equal(got, h.MerkleRoot[:]) {
146 t.Fatalf("merkle root bytes = %x, want %x", got, h.MerkleRoot)
147 }
148 if got, want := raw[68:72], []byte{0x08, 0x07, 0x06, 0x05}; !bytes.Equal(got, want) {
149 t.Fatalf("timestamp bytes = %x, want %x", got, want)
150 }
151 if got, want := raw[72:76], []byte{0x0c, 0x0b, 0x0a, 0x09}; !bytes.Equal(got, want) {
152 t.Fatalf("bits bytes = %x, want %x", got, want)
153 }
154 if got, want := raw[76:80], []byte{0x10, 0x0f, 0x0e, 0x0d}; !bytes.Equal(got, want) {
155 t.Fatalf("nonce bytes = %x, want %x", got, want)
156 }
157
158 var round BlockHeader
159 if err := round.Deserialize(raw[:]); err != nil {
160 t.Fatalf("Deserialize: %v", err)
161 }
162 if round != h {
163 t.Fatalf("round trip = %+v, want %+v", round, h)
164 }
165 }
166
167 func TestBlockHeaderDeserializeShort(t *testing.T) {
168 raw := mustHex(t, genesisHeaderHex)
169 var h BlockHeader
170 for n := 0; n < BlockHeaderSize; n++ {
171 if err := h.Deserialize(raw[:n]); !errors.Is(err, io.ErrUnexpectedEOF) {
172 t.Fatalf("Deserialize(%d bytes) error = %v, want io.ErrUnexpectedEOF", n, err)
173 }
174 }
175 // Trailing bytes beyond the header are ignored.
176 if err := h.Deserialize(append(raw, 0xde, 0xad)); err != nil {
177 t.Fatalf("Deserialize with trailing bytes: %v", err)
178 }
179 if h.BlockHash().String() != genesisHashDisplay {
180 t.Fatalf("BlockHash = %s, want %s", h.BlockHash(), genesisHashDisplay)
181 }
182 }
183
184 func TestCompactToBig(t *testing.T) {
185 tests := []struct {
186 name string
187 bits uint32
188 want *big.Int
189 wantErr error
190 }{
191 {
192 name: "mainnet pow limit",
193 bits: 0x1d00ffff,
194 want: new(big.Int).Lsh(big.NewInt(0xffff), 208),
195 },
196 {
197 name: "difficulty one legacy",
198 bits: 0x1b0404cb,
199 want: mustBigInt(t, "00000000000404cb000000000000000000000000000000000000000000000000"),
200 },
201 {
202 name: "256 bit maximum target",
203 bits: 0x2100ffff,
204 want: new(big.Int).Lsh(big.NewInt(0xffff), 240),
205 },
206 {
207 name: "single satoshi target",
208 bits: 0x03000001,
209 want: big.NewInt(1),
210 },
211 {
212 name: "zero mantissa",
213 bits: 0x1d000000,
214 want: new(big.Int),
215 },
216 {
217 name: "small exponent truncates",
218 bits: 0x01003456,
219 want: new(big.Int),
220 },
221 {
222 name: "negative",
223 bits: 0x1d80ffff,
224 wantErr: ErrTargetNegative,
225 },
226 {
227 name: "overflow high exponent",
228 bits: 0xff00ffff,
229 wantErr: ErrTargetOverflow,
230 },
231 {
232 name: "overflow one bit too wide",
233 bits: 0x2200ffff,
234 wantErr: ErrTargetOverflow,
235 },
236 }
237 for _, tc := range tests {
238 t.Run(tc.name, func(t *testing.T) {
239 got, err := CompactToBig(tc.bits)
240 if tc.wantErr != nil {
241 if !errors.Is(err, tc.wantErr) {
242 t.Fatalf("error = %v, want %v", err, tc.wantErr)
243 }
244 return
245 }
246 if err != nil {
247 t.Fatalf("CompactToBig(0x%08x): %v", tc.bits, err)
248 }
249 if got.Cmp(tc.want) != 0 {
250 t.Fatalf("CompactToBig(0x%08x) = %x, want %x", tc.bits, got, tc.want)
251 }
252 })
253 }
254 }
255
256 // mustBigInt parses s as a base 16 integer or fails the test.
257 func mustBigInt(t *testing.T, s string) *big.Int {
258 t.Helper()
259 v, ok := new(big.Int).SetString(s, 16)
260 if !ok {
261 t.Fatalf("parsing big integer %q", s)
262 }
263 return v
264 }
265
266 func TestHashToBigEndianness(t *testing.T) {
267 // Internal byte order is little endian: the first byte is the least
268 // significant.
269 var low chainhash.Hash
270 low[0] = 0x01
271 if got := HashToBig(&low); got.Cmp(big.NewInt(1)) != 0 {
272 t.Fatalf("HashToBig({01,00,...}) = %x, want 1", got)
273 }
274 var high chainhash.Hash
275 high[chainhash.HashSize-1] = 0x01
276 if got, want := HashToBig(&high), new(big.Int).Lsh(big.NewInt(1), 248); got.Cmp(want) != 0 {
277 t.Fatalf("HashToBig({...,00,01}) = %x, want %x", got, want)
278 }
279 var z chainhash.Hash
280 if got := HashToBig(&z); got.Sign() != 0 {
281 t.Fatalf("HashToBig(zero) = %x, want 0", got)
282 }
283 }
284
285 func TestCheckProofOfWorkInvalidTarget(t *testing.T) {
286 genesis := mustGenesisHeader(t)
287 tests := []struct {
288 name string
289 bits uint32
290 wantErr error
291 }{
292 {"zero target", 0x1d000000, nil},
293 {"negative target", 0x1d80ffff, ErrTargetNegative},
294 {"overflow target", 0xff00ffff, ErrTargetOverflow},
295 }
296 for _, tc := range tests {
297 t.Run(tc.name, func(t *testing.T) {
298 h := genesis
299 h.Bits = tc.bits
300 _, err := h.Target()
301 if !errors.Is(err, tc.wantErr) {
302 t.Fatalf("Target error = %v, want %v", err, tc.wantErr)
303 }
304 if h.CheckProofOfWork() {
305 t.Fatal("header with invalid target passed proof-of-work check")
306 }
307 })
308 }
309 }
310