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