package block import ( "encoding/hex" "os" "strings" "testing" ) // The fixture is regtest block 1 from the rig node: one coinbase transaction, so a single // transaction id and the merkle root are the same value — which makes a mistake in either the // transaction scan or the root collapse into one mismatch rather than two. const ( fixtureHash = "772aaf3a73102e9c5826524c776ede9499bcb82740f2c7e14913c70d611f271f" fixtureMerkle = "1d89b965ec54a83d571087f604495c46e937574a8483ba089dbc9920b6589b36" ) // loadFixture reads the block. func loadFixture(t *testing.T) []byte { t.Helper() text, err := os.ReadFile("testdata/block1.hex") if err != nil { t.Fatal(err) } raw, err := hex.DecodeString(strings.TrimSpace(string(text))) if err != nil { t.Fatal(err) } return raw } // display reverses a hash into the order a person reads it in. func display(hash [32]byte) string { out := make([]byte, len(hash)) for index := range hash { out[len(hash)-1-index] = hash[index] } return hex.EncodeToString(out) } // TestTheBlockParses: header hash, merkle root and transaction ids come out right. func TestTheBlockParses(t *testing.T) { parsed, err := Parse(loadFixture(t)) if err != nil { t.Fatal(err) } if got := display(parsed.Hash); got != fixtureHash { t.Fatalf("the block hash is %s, want %s", got, fixtureHash) } if got := display(parsed.MerkleRoot); got != fixtureMerkle { t.Fatalf("the header's root is %s, want %s", got, fixtureMerkle) } if len(parsed.TxIDs) != 1 { t.Fatalf("%d transactions", len(parsed.TxIDs)) } if got := display(parsed.TxIDs[0]); got != fixtureMerkle { t.Fatalf("the coinbase id is %s, and a one-transaction root must equal it", got) } } // TestTheMerkleRootVerifies: the check a fetched block has to pass. func TestTheMerkleRootVerifies(t *testing.T) { parsed, err := Parse(loadFixture(t)) if err != nil { t.Fatal(err) } if err := parsed.VerifyMerkle(); err != nil { t.Fatal(err) } } // TestAFabricatedBlockIsRefused: a block whose transactions do not hash to the header's root is // exactly what a lying gateway would send. func TestAFabricatedBlockIsRefused(t *testing.T) { raw := loadFixture(t) // Flip a byte inside the coinbase's scriptSig, which changes the transaction id. raw[HeaderLen+1+4+40] ^= 0xff parsed, err := Parse(raw) if err != nil { t.Fatal(err) } if err := parsed.VerifyMerkle(); err == nil { t.Fatal("a block with a changed transaction passed the merkle check") } } // TestTruncationIsRefused: every cut is an error, not a shorter block. func TestTruncationIsRefused(t *testing.T) { raw := loadFixture(t) for _, cut := range []int{0, 40, HeaderLen, HeaderLen + 5, len(raw) / 2, len(raw) - 1} { if _, err := Parse(raw[:cut]); err == nil { t.Fatalf("a %d-byte block parsed", cut) } } } // TestTheCoinbaseOutputIsFound: the point of parsing a block — which output pays this script, // in which transaction, for how much. func TestTheCoinbaseOutputIsFound(t *testing.T) { parsed, err := Parse(loadFixture(t)) if err != nil { t.Fatal(err) } if len(parsed.Outputs) != 1 || len(parsed.Outputs[0]) != 2 { t.Fatalf("%d transactions with %d and %d outputs", len(parsed.Outputs), len(parsed.Outputs[0]), len(parsed.Outputs[0])) } // The first output pays 50 BTC to a P2WPKH script; match it by its own bytes. paid := parsed.Outputs[0][0] if paid.Value != 5000000000 { t.Fatalf("the coinbase pays %d satoshis", paid.Value) } matches := parsed.Match([][]byte{paid.Script, []byte{0x00, 0x14}}) if len(matches) != 1 { t.Fatalf("%d matches", len(matches)) } if matches[0].Vout != 0 || matches[0].Value != paid.Value { t.Fatalf("the match is %+v", matches[0]) } if matches[0].TxID != parsed.TxIDs[0] { t.Fatal("the match names a different transaction") } // A script nobody pays finds nothing. if got := parsed.Match([][]byte{{0x00, 0x14, 0x99}}); len(got) != 0 { t.Fatalf("%d matches for an unpayed script", len(got)) } }