block_test.go raw

   1  package block
   2  
   3  import (
   4  	"encoding/hex"
   5  	"os"
   6  	"strings"
   7  	"testing"
   8  )
   9  
  10  // The fixture is regtest block 1 from the rig node: one coinbase transaction, so a single
  11  // transaction id and the merkle root are the same value — which makes a mistake in either the
  12  // transaction scan or the root collapse into one mismatch rather than two.
  13  const (
  14  	fixtureHash   = "772aaf3a73102e9c5826524c776ede9499bcb82740f2c7e14913c70d611f271f"
  15  	fixtureMerkle = "1d89b965ec54a83d571087f604495c46e937574a8483ba089dbc9920b6589b36"
  16  )
  17  
  18  // loadFixture reads the block.
  19  func loadFixture(t *testing.T) []byte {
  20  	t.Helper()
  21  
  22  	text, err := os.ReadFile("testdata/block1.hex")
  23  
  24  	if err != nil {
  25  		t.Fatal(err)
  26  	}
  27  
  28  	raw, err := hex.DecodeString(strings.TrimSpace(string(text)))
  29  
  30  	if err != nil {
  31  		t.Fatal(err)
  32  	}
  33  
  34  	return raw
  35  }
  36  
  37  // display reverses a hash into the order a person reads it in.
  38  func display(hash [32]byte) string {
  39  	out := make([]byte, len(hash))
  40  
  41  	for index := range hash {
  42  		out[len(hash)-1-index] = hash[index]
  43  	}
  44  
  45  	return hex.EncodeToString(out)
  46  }
  47  
  48  // TestTheBlockParses: header hash, merkle root and transaction ids come out right.
  49  func TestTheBlockParses(t *testing.T) {
  50  	parsed, err := Parse(loadFixture(t))
  51  
  52  	if err != nil {
  53  		t.Fatal(err)
  54  	}
  55  
  56  	if got := display(parsed.Hash); got != fixtureHash {
  57  		t.Fatalf("the block hash is %s, want %s", got, fixtureHash)
  58  	}
  59  
  60  	if got := display(parsed.MerkleRoot); got != fixtureMerkle {
  61  		t.Fatalf("the header's root is %s, want %s", got, fixtureMerkle)
  62  	}
  63  
  64  	if len(parsed.TxIDs) != 1 {
  65  		t.Fatalf("%d transactions", len(parsed.TxIDs))
  66  	}
  67  
  68  	if got := display(parsed.TxIDs[0]); got != fixtureMerkle {
  69  		t.Fatalf("the coinbase id is %s, and a one-transaction root must equal it", got)
  70  	}
  71  }
  72  
  73  // TestTheMerkleRootVerifies: the check a fetched block has to pass.
  74  func TestTheMerkleRootVerifies(t *testing.T) {
  75  	parsed, err := Parse(loadFixture(t))
  76  
  77  	if err != nil {
  78  		t.Fatal(err)
  79  	}
  80  
  81  	if err := parsed.VerifyMerkle(); err != nil {
  82  		t.Fatal(err)
  83  	}
  84  }
  85  
  86  // TestAFabricatedBlockIsRefused: a block whose transactions do not hash to the header's root is
  87  // exactly what a lying gateway would send.
  88  func TestAFabricatedBlockIsRefused(t *testing.T) {
  89  	raw := loadFixture(t)
  90  
  91  	// Flip a byte inside the coinbase's scriptSig, which changes the transaction id.
  92  	raw[HeaderLen+1+4+40] ^= 0xff
  93  
  94  	parsed, err := Parse(raw)
  95  
  96  	if err != nil {
  97  		t.Fatal(err)
  98  	}
  99  
 100  	if err := parsed.VerifyMerkle(); err == nil {
 101  		t.Fatal("a block with a changed transaction passed the merkle check")
 102  	}
 103  }
 104  
 105  // TestTruncationIsRefused: every cut is an error, not a shorter block.
 106  func TestTruncationIsRefused(t *testing.T) {
 107  	raw := loadFixture(t)
 108  
 109  	for _, cut := range []int{0, 40, HeaderLen, HeaderLen + 5, len(raw) / 2, len(raw) - 1} {
 110  		if _, err := Parse(raw[:cut]); err == nil {
 111  			t.Fatalf("a %d-byte block parsed", cut)
 112  		}
 113  	}
 114  }
 115  
 116  // TestTheCoinbaseOutputIsFound: the point of parsing a block — which output pays this script,
 117  // in which transaction, for how much.
 118  func TestTheCoinbaseOutputIsFound(t *testing.T) {
 119  	parsed, err := Parse(loadFixture(t))
 120  
 121  	if err != nil {
 122  		t.Fatal(err)
 123  	}
 124  
 125  	if len(parsed.Outputs) != 1 || len(parsed.Outputs[0]) != 2 {
 126  		t.Fatalf("%d transactions with %d and %d outputs", len(parsed.Outputs),
 127  			len(parsed.Outputs[0]), len(parsed.Outputs[0]))
 128  	}
 129  
 130  	// The first output pays 50 BTC to a P2WPKH script; match it by its own bytes.
 131  	paid := parsed.Outputs[0][0]
 132  
 133  	if paid.Value != 5000000000 {
 134  		t.Fatalf("the coinbase pays %d satoshis", paid.Value)
 135  	}
 136  
 137  	matches := parsed.Match([][]byte{paid.Script, []byte{0x00, 0x14}})
 138  
 139  	if len(matches) != 1 {
 140  		t.Fatalf("%d matches", len(matches))
 141  	}
 142  
 143  	if matches[0].Vout != 0 || matches[0].Value != paid.Value {
 144  		t.Fatalf("the match is %+v", matches[0])
 145  	}
 146  
 147  	if matches[0].TxID != parsed.TxIDs[0] {
 148  		t.Fatal("the match names a different transaction")
 149  	}
 150  
 151  	// A script nobody pays finds nothing.
 152  	if got := parsed.Match([][]byte{{0x00, 0x14, 0x99}}); len(got) != 0 {
 153  		t.Fatalf("%d matches for an unpayed script", len(got))
 154  	}
 155  }
 156