276 lines
7.4 KiB
Go
276 lines
7.4 KiB
Go
// Copyright (c) 2019-2020 The Zcash developers
|
|
// Distributed under the MIT software license, see the accompanying
|
|
// file COPYING or https://www.opensource.org/licenses/mit-license.php .
|
|
package parser
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"testing"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
protobuf "github.com/golang/protobuf/proto"
|
|
)
|
|
|
|
func TestBlockParser(t *testing.T) {
|
|
// These (valid on testnet) correspond to the transactions in testdata/blocks;
|
|
// for each block, the hashes for the tx within that block.
|
|
var txhashes = [][]string{
|
|
{
|
|
"81096ff101a4f01d25ffd34a446bee4368bd46c233a59ac0faf101e1861c6b22",
|
|
}, {
|
|
"921dc41bef3a0d887c615abac60a29979efc8b4bbd3d887caeb6bb93501bde8e",
|
|
}, {
|
|
"d8e4c336ffa69dacaa4e0b4eaf8e3ae46897f1930a573c10b53837a03318c980",
|
|
"4d5ccbfc6984680c481ff5ce145b8a93d59dfea90c150dfa45c938ab076ee5b2",
|
|
}, {
|
|
"df2b03619d441ce3d347e9278d87618e975079d0e235dfb3b3d8271510f707aa",
|
|
"8d2593edfc328fa637b4ac91c7d569ee922bb9a6fda7cea230e92deb3ae4b634",
|
|
},
|
|
}
|
|
testBlocks, err := os.Open("../testdata/blocks")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer testBlocks.Close()
|
|
|
|
scan := bufio.NewScanner(testBlocks)
|
|
for blockindex := 0; scan.Scan(); blockindex++ {
|
|
blockDataHex := scan.Text()
|
|
blockData, err := hex.DecodeString(blockDataHex)
|
|
if err != nil {
|
|
t.Error(err)
|
|
continue
|
|
}
|
|
|
|
// This is just a sanity check of the test:
|
|
if int(blockData[1487]) != len(txhashes[blockindex]) {
|
|
t.Error("wrong number of transactions, test broken?")
|
|
}
|
|
|
|
// Make a copy of just the transactions alone, which,
|
|
// for these blocks, start just beyond the header and
|
|
// the one-byte nTx value, which is offset 1488.
|
|
transactions := make([]byte, len(blockData[1488:]))
|
|
copy(transactions, blockData[1488:])
|
|
|
|
// Each iteration of this loop appends the block's original
|
|
// transactions, so we build an ever-larger block. The loop
|
|
// limit is arbitrary, but make sure we get into double-digit
|
|
// transaction counts (compact integer).
|
|
for i := 0; i < 264; i++ {
|
|
b := blockData
|
|
block := NewBlock()
|
|
b, err = block.ParseFromSlice(b)
|
|
if err != nil {
|
|
t.Error(errors.Wrap(err, fmt.Sprintf("parsing block %d", i)))
|
|
continue
|
|
}
|
|
if len(b) > 0 {
|
|
t.Error("Extra data remaining")
|
|
}
|
|
|
|
// Some basic sanity checks
|
|
if block.hdr.Version != 4 {
|
|
t.Error("Read wrong version in a test block.")
|
|
break
|
|
}
|
|
if block.GetVersion() != 4 {
|
|
t.Error("Read wrong version in a test block.")
|
|
break
|
|
}
|
|
if block.GetTxCount() < 1 {
|
|
t.Error("No transactions in block")
|
|
break
|
|
}
|
|
if len(block.Transactions()) != block.GetTxCount() {
|
|
t.Error("Number of transactions mismatch")
|
|
break
|
|
}
|
|
if block.GetTxCount() != len(txhashes[blockindex])*(i+1) {
|
|
t.Error("Unexpected number of transactions")
|
|
}
|
|
if block.HasSaplingTransactions() {
|
|
t.Error("Unexpected Saping tx")
|
|
break
|
|
}
|
|
for txindex, tx := range block.Transactions() {
|
|
if tx.HasSaplingElements() {
|
|
t.Error("Unexpected Saping tx")
|
|
break
|
|
}
|
|
expectedHash := txhashes[blockindex][txindex%len(txhashes[blockindex])]
|
|
if hex.EncodeToString(tx.GetDisplayHash()) != expectedHash {
|
|
t.Error("incorrect tx hash")
|
|
}
|
|
}
|
|
// Keep appending the original transactions, which is unrealistic
|
|
// because the coinbase is being replicated, but it works; first do
|
|
// some surgery to the transaction count (see DarksideApplyStaged()).
|
|
for j := 0; j < len(txhashes[blockindex]); j++ {
|
|
nTxFirstByte := blockData[1487]
|
|
switch {
|
|
case nTxFirstByte < 252:
|
|
blockData[1487]++
|
|
case nTxFirstByte == 252:
|
|
// incrementing to 253, requires "253" followed by 2-byte length,
|
|
// extend the block by two bytes, shift existing transaction bytes
|
|
blockData = append(blockData, 0, 0)
|
|
copy(blockData[1490:], blockData[1488:len(blockData)-2])
|
|
blockData[1487] = 253
|
|
blockData[1488] = 253
|
|
blockData[1489] = 0
|
|
case nTxFirstByte == 253:
|
|
blockData[1488]++
|
|
if blockData[1488] == 0 {
|
|
// wrapped around
|
|
blockData[1489]++
|
|
}
|
|
}
|
|
}
|
|
blockData = append(blockData, transactions...)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBlockParserFail(t *testing.T) {
|
|
testBlocks, err := os.Open("../testdata/badblocks")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer testBlocks.Close()
|
|
|
|
scan := bufio.NewScanner(testBlocks)
|
|
|
|
// the first "block" contains an illegal hex character
|
|
{
|
|
scan.Scan()
|
|
blockDataHex := scan.Text()
|
|
_, err := hex.DecodeString(blockDataHex)
|
|
if err == nil {
|
|
t.Error("unexpected success parsing illegal hex bad block")
|
|
}
|
|
}
|
|
for i := 0; scan.Scan(); i++ {
|
|
blockDataHex := scan.Text()
|
|
blockData, err := hex.DecodeString(blockDataHex)
|
|
if err != nil {
|
|
t.Error(err)
|
|
continue
|
|
}
|
|
|
|
block := NewBlock()
|
|
blockData, err = block.ParseFromSlice(blockData)
|
|
if err == nil {
|
|
t.Error("unexpected success parsing bad block")
|
|
}
|
|
}
|
|
}
|
|
|
|
// Checks on the first 20 blocks from mainnet genesis.
|
|
func TestGenesisBlockParser(t *testing.T) {
|
|
blockFile, err := os.Open("../testdata/mainnet_genesis")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer blockFile.Close()
|
|
|
|
scan := bufio.NewScanner(blockFile)
|
|
for i := 0; scan.Scan(); i++ {
|
|
blockDataHex := scan.Text()
|
|
blockData, err := hex.DecodeString(blockDataHex)
|
|
if err != nil {
|
|
t.Error(err)
|
|
continue
|
|
}
|
|
|
|
block := NewBlock()
|
|
blockData, err = block.ParseFromSlice(blockData)
|
|
if err != nil {
|
|
t.Error(err)
|
|
continue
|
|
}
|
|
if len(blockData) > 0 {
|
|
t.Error("Extra data remaining")
|
|
}
|
|
|
|
// Some basic sanity checks
|
|
if block.hdr.Version != 4 {
|
|
t.Error("Read wrong version in genesis block.")
|
|
break
|
|
}
|
|
|
|
if block.GetHeight() != i {
|
|
t.Errorf("Got wrong height for block %d: %d", i, block.GetHeight())
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCompactBlocks(t *testing.T) {
|
|
type compactTest struct {
|
|
BlockHeight int `json:"block"`
|
|
BlockHash string `json:"hash"`
|
|
PrevHash string `json:"prev"`
|
|
Full string `json:"full"`
|
|
Compact string `json:"compact"`
|
|
}
|
|
var compactTests []compactTest
|
|
|
|
blockJSON, err := ioutil.ReadFile("../testdata/compact_blocks.json")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = json.Unmarshal(blockJSON, &compactTests)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
for _, test := range compactTests {
|
|
blockData, _ := hex.DecodeString(test.Full)
|
|
block := NewBlock()
|
|
blockData, err = block.ParseFromSlice(blockData)
|
|
if err != nil {
|
|
t.Error(errors.Wrap(err, fmt.Sprintf("parsing testnet block %d", test.BlockHeight)))
|
|
continue
|
|
}
|
|
if len(blockData) > 0 {
|
|
t.Error("Extra data remaining")
|
|
}
|
|
if block.GetHeight() != test.BlockHeight {
|
|
t.Errorf("incorrect block height in testnet block %d", test.BlockHeight)
|
|
continue
|
|
}
|
|
if hex.EncodeToString(block.GetDisplayHash()) != test.BlockHash {
|
|
t.Errorf("incorrect block hash in testnet block %x", test.BlockHash)
|
|
continue
|
|
}
|
|
if hex.EncodeToString(block.GetDisplayPrevHash()) != test.PrevHash {
|
|
t.Errorf("incorrect block prevhash in testnet block %x", test.BlockHash)
|
|
continue
|
|
}
|
|
if !bytes.Equal(block.GetPrevHash(), block.hdr.HashPrevBlock) {
|
|
t.Error("block and block header prevhash don't match")
|
|
}
|
|
|
|
compact := block.ToCompact()
|
|
marshaled, err := protobuf.Marshal(compact)
|
|
if err != nil {
|
|
t.Errorf("could not marshal compact testnet block %d", test.BlockHeight)
|
|
continue
|
|
}
|
|
encodedCompact := hex.EncodeToString(marshaled)
|
|
if encodedCompact != test.Compact {
|
|
t.Errorf("wrong data for compact testnet block %d\nhave: %s\nwant: %s\n", test.BlockHeight, encodedCompact, test.Compact)
|
|
break
|
|
}
|
|
}
|
|
|
|
}
|