Add test and infrastructure for block scripts.
This commit adds a test for checking known good block scripts in a block are valid. As a part of this, it adds the infastructure needed to load a saved transaction store from a file which contains all of the input transactions needed. It also contains some changes from running goimports as well as some other cleanup.
This commit is contained in:
parent
6af9302374
commit
d58ea754f3
11 changed files with 173 additions and 21 deletions
110
common_test.go
110
common_test.go
|
@ -5,9 +5,13 @@
|
||||||
package btcchain_test
|
package btcchain_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"compress/bzip2"
|
||||||
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/conformal/btcchain"
|
"github.com/conformal/btcchain"
|
||||||
"github.com/conformal/btcdb"
|
"github.com/conformal/btcdb"
|
||||||
|
@ -15,6 +19,7 @@ import (
|
||||||
_ "github.com/conformal/btcdb/memdb"
|
_ "github.com/conformal/btcdb/memdb"
|
||||||
"github.com/conformal/btcnet"
|
"github.com/conformal/btcnet"
|
||||||
"github.com/conformal/btcutil"
|
"github.com/conformal/btcutil"
|
||||||
|
"github.com/conformal/btcwire"
|
||||||
)
|
)
|
||||||
|
|
||||||
// testDbType is the database backend type to use for the tests.
|
// testDbType is the database backend type to use for the tests.
|
||||||
|
@ -114,3 +119,108 @@ func chainSetup(dbName string) (*btcchain.BlockChain, func(), error) {
|
||||||
chain := btcchain.New(db, &btcnet.MainNetParams, nil)
|
chain := btcchain.New(db, &btcnet.MainNetParams, nil)
|
||||||
return chain, teardown, nil
|
return chain, teardown, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// loadTxStore returns a transaction store loaded from a file.
|
||||||
|
func loadTxStore(filename string) (btcchain.TxStore, error) {
|
||||||
|
// The txstore file format is:
|
||||||
|
// <num tx data entries> <tx length> <serialized tx> <blk height>
|
||||||
|
// <num spent bits> <spent bits>
|
||||||
|
//
|
||||||
|
// All num and length fields are little-endian uint32s. The spent bits
|
||||||
|
// field is padded to a byte boundary.
|
||||||
|
|
||||||
|
filename = filepath.Join("testdata/", filename)
|
||||||
|
fi, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Choose read based on whether the file is compressed or not.
|
||||||
|
var r io.Reader
|
||||||
|
if strings.HasSuffix(filename, ".bz2") {
|
||||||
|
r = bzip2.NewReader(fi)
|
||||||
|
} else {
|
||||||
|
r = fi
|
||||||
|
}
|
||||||
|
defer fi.Close()
|
||||||
|
|
||||||
|
// Num of transaction store objects.
|
||||||
|
var numItems uint32
|
||||||
|
if err := binary.Read(r, binary.LittleEndian, &numItems); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
txStore := make(btcchain.TxStore)
|
||||||
|
var uintBuf uint32
|
||||||
|
for height := uint32(0); height < numItems; height++ {
|
||||||
|
txD := btcchain.TxData{}
|
||||||
|
|
||||||
|
// Serialized transaction length.
|
||||||
|
err = binary.Read(r, binary.LittleEndian, &uintBuf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
serializedTxLen := uintBuf
|
||||||
|
if serializedTxLen > btcwire.MaxBlockPayload {
|
||||||
|
return nil, fmt.Errorf("Read serialized transaction "+
|
||||||
|
"length of %d is larger max allowed %d",
|
||||||
|
serializedTxLen, btcwire.MaxBlockPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transaction.
|
||||||
|
var msgTx btcwire.MsgTx
|
||||||
|
err = msgTx.Deserialize(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
txD.Tx = btcutil.NewTx(&msgTx)
|
||||||
|
|
||||||
|
// Transaction hash.
|
||||||
|
txHash, err := msgTx.TxSha()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
txD.Hash = &txHash
|
||||||
|
|
||||||
|
// Block height the transaction came from.
|
||||||
|
err = binary.Read(r, binary.LittleEndian, &uintBuf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
txD.BlockHeight = int64(uintBuf)
|
||||||
|
|
||||||
|
// Num spent bits.
|
||||||
|
err = binary.Read(r, binary.LittleEndian, &uintBuf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
numSpentBits := uintBuf
|
||||||
|
numSpentBytes := numSpentBits / 8
|
||||||
|
if numSpentBits%8 != 0 {
|
||||||
|
numSpentBytes++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Packed spent bytes.
|
||||||
|
spentBytes := make([]byte, numSpentBytes)
|
||||||
|
_, err = io.ReadFull(r, spentBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate spent data based on spent bits.
|
||||||
|
txD.Spent = make([]bool, numSpentBits)
|
||||||
|
for byteNum, spentByte := range spentBytes {
|
||||||
|
for bit := 0; bit < 8; bit++ {
|
||||||
|
if uint32((byteNum*8)+bit) < numSpentBits {
|
||||||
|
if spentByte&(1<<uint(bit)) != 0 {
|
||||||
|
txD.Spent[(byteNum*8)+bit] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
txStore[*txD.Hash] = &txD
|
||||||
|
}
|
||||||
|
|
||||||
|
return txStore, nil
|
||||||
|
}
|
||||||
|
|
|
@ -6,12 +6,13 @@ package btcchain_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
"github.com/conformal/btcchain"
|
"github.com/conformal/btcchain"
|
||||||
"github.com/conformal/btcdb"
|
"github.com/conformal/btcdb"
|
||||||
_ "github.com/conformal/btcdb/memdb"
|
_ "github.com/conformal/btcdb/memdb"
|
||||||
"github.com/conformal/btcnet"
|
"github.com/conformal/btcnet"
|
||||||
"github.com/conformal/btcutil"
|
"github.com/conformal/btcutil"
|
||||||
"math/big"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// This example demonstrates how to create a new chain instance and use
|
// This example demonstrates how to create a new chain instance and use
|
||||||
|
|
|
@ -14,8 +14,6 @@ package btcchain
|
||||||
import (
|
import (
|
||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/conformal/btcutil"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// TstSetCoinbaseMaturity makes the ability to set the coinbase maturity
|
// TstSetCoinbaseMaturity makes the ability to set the coinbase maturity
|
||||||
|
@ -32,12 +30,14 @@ func TstTimeSorter(times []time.Time) sort.Interface {
|
||||||
|
|
||||||
// TstCheckSerializedHeight makes the internal checkSerializedHeight function
|
// TstCheckSerializedHeight makes the internal checkSerializedHeight function
|
||||||
// available to the test package.
|
// available to the test package.
|
||||||
func TstCheckSerializedHeight(coinbaseTx *btcutil.Tx, wantHeight int64) error {
|
var TstCheckSerializedHeight = checkSerializedHeight
|
||||||
return checkSerializedHeight(coinbaseTx, wantHeight)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TstSetMaxMedianTimeEntries makes the ability to set the maximum number of
|
// TstSetMaxMedianTimeEntries makes the ability to set the maximum number of
|
||||||
// median tiem entries available to the test package.
|
// median tiem entries available to the test package.
|
||||||
func TstSetMaxMedianTimeEntries(val int) {
|
func TstSetMaxMedianTimeEntries(val int) {
|
||||||
maxMedianTimeEntries = val
|
maxMedianTimeEntries = val
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TstCheckBlockScripts makes the internal checkBlockScripts function available
|
||||||
|
// to the test package.
|
||||||
|
var TstCheckBlockScripts = checkBlockScripts
|
||||||
|
|
|
@ -119,7 +119,7 @@ func (m *medianTime) AddTimeSample(sourceID string, timeVal time.Time) {
|
||||||
// replacing the oldest entry with the new entry once the maximum number
|
// replacing the oldest entry with the new entry once the maximum number
|
||||||
// of entries is reached.
|
// of entries is reached.
|
||||||
now := time.Unix(time.Now().Unix(), 0)
|
now := time.Unix(time.Now().Unix(), 0)
|
||||||
offsetSecs := int64(now.Sub(timeVal).Seconds())
|
offsetSecs := int64(timeVal.Sub(now).Seconds())
|
||||||
numOffsets := len(m.offsets)
|
numOffsets := len(m.offsets)
|
||||||
if numOffsets == maxMedianTimeEntries && maxMedianTimeEntries > 0 {
|
if numOffsets == maxMedianTimeEntries && maxMedianTimeEntries > 0 {
|
||||||
m.offsets = m.offsets[1:]
|
m.offsets = m.offsets[1:]
|
||||||
|
|
|
@ -27,21 +27,21 @@ func TestMedianTime(t *testing.T) {
|
||||||
|
|
||||||
// Various number of entries. The expected offset is only
|
// Various number of entries. The expected offset is only
|
||||||
// updated on odd number of elements.
|
// updated on odd number of elements.
|
||||||
{in: []int64{-13, 57, -4, -23, -12}, wantOffset: 12},
|
{in: []int64{-13, 57, -4, -23, -12}, wantOffset: -12},
|
||||||
{in: []int64{55, -13, 61, -52, 39, 55}, wantOffset: -39},
|
{in: []int64{55, -13, 61, -52, 39, 55}, wantOffset: 39},
|
||||||
{in: []int64{-62, -58, -30, -62, 51, -30, 15}, wantOffset: 30},
|
{in: []int64{-62, -58, -30, -62, 51, -30, 15}, wantOffset: -30},
|
||||||
{in: []int64{29, -47, 39, 54, 42, 41, 8, -33}, wantOffset: -39},
|
{in: []int64{29, -47, 39, 54, 42, 41, 8, -33}, wantOffset: 39},
|
||||||
{in: []int64{37, 54, 9, -21, -56, -36, 5, -11, -39}, wantOffset: 11},
|
{in: []int64{37, 54, 9, -21, -56, -36, 5, -11, -39}, wantOffset: -11},
|
||||||
{in: []int64{57, -28, 25, -39, 9, 63, -16, 19, -60, 25}, wantOffset: -9},
|
{in: []int64{57, -28, 25, -39, 9, 63, -16, 19, -60, 25}, wantOffset: 9},
|
||||||
{in: []int64{-5, -4, -3, -2, -1}, wantOffset: 3, useDupID: true},
|
{in: []int64{-5, -4, -3, -2, -1}, wantOffset: -3, useDupID: true},
|
||||||
|
|
||||||
// The offset stops being updated once the max number of entries
|
// The offset stops being updated once the max number of entries
|
||||||
// has been reached. This is actually a bug from Bitcoin Core,
|
// has been reached. This is actually a bug from Bitcoin Core,
|
||||||
// but since the time is ultimately used as a part of the
|
// but since the time is ultimately used as a part of the
|
||||||
// consensus rules, it must be mirrored.
|
// consensus rules, it must be mirrored.
|
||||||
{in: []int64{-67, 67, -50, 24, 63, 17, 58, -14, 5, -32, -52}, wantOffset: -17},
|
{in: []int64{-67, 67, -50, 24, 63, 17, 58, -14, 5, -32, -52}, wantOffset: 17},
|
||||||
{in: []int64{-67, 67, -50, 24, 63, 17, 58, -14, 5, -32, -52, 45}, wantOffset: -17},
|
{in: []int64{-67, 67, -50, 24, 63, 17, 58, -14, 5, -32, -52, 45}, wantOffset: 17},
|
||||||
{in: []int64{-67, 67, -50, 24, 63, 17, 58, -14, 5, -32, -52, 45, 4}, wantOffset: -17},
|
{in: []int64{-67, 67, -50, 24, 63, 17, 58, -14, 5, -32, -52, 45, 4}, wantOffset: 17},
|
||||||
|
|
||||||
// Offsets that are too far away from the local time should
|
// Offsets that are too far away from the local time should
|
||||||
// be ignored.
|
// be ignored.
|
||||||
|
|
|
@ -217,7 +217,6 @@ func ValidateTransactionScripts(tx *btcutil.Tx, txStore TxStore, flags btcscript
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkBlockScripts executes and validates the scripts for all transactions in
|
// checkBlockScripts executes and validates the scripts for all transactions in
|
||||||
|
|
43
scriptval_test.go
Normal file
43
scriptval_test.go
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
// Copyright (c) 2013-2015 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcchain_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/conformal/btcchain"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestCheckBlockScripts ensures that validating the all of the scripts in a
|
||||||
|
// known-good block doesn't return an error.
|
||||||
|
func TestCheckBlockScripts(t *testing.T) {
|
||||||
|
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||||
|
|
||||||
|
testBlockNum := 277647
|
||||||
|
blockDataFile := fmt.Sprintf("%d.dat.bz2", testBlockNum)
|
||||||
|
blocks, err := loadBlocks(blockDataFile)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error loading file: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(blocks) > 1 {
|
||||||
|
t.Errorf("The test block file must only have one block in it")
|
||||||
|
}
|
||||||
|
|
||||||
|
txStoreDataFile := fmt.Sprintf("%d.txstore.bz2", testBlockNum)
|
||||||
|
txStore, err := loadTxStore(txStoreDataFile)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error loading txstore: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := btcchain.TstCheckBlockScripts(blocks[0], txStore); err != nil {
|
||||||
|
t.Errorf("Transaction script validation failed: %v\n",
|
||||||
|
err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
BIN
testdata/277647.dat.bz2
vendored
Normal file
BIN
testdata/277647.dat.bz2
vendored
Normal file
Binary file not shown.
BIN
testdata/277647.txstore.bz2
vendored
Normal file
BIN
testdata/277647.txstore.bz2
vendored
Normal file
Binary file not shown.
|
@ -935,8 +935,8 @@ func (b *BlockChain) checkConnectBlock(node *blockNode, block *btcutil.Block) er
|
||||||
// the checks performed are ensuring connecting the block would not cause any
|
// the checks performed are ensuring connecting the block would not cause any
|
||||||
// duplicate transaction hashes for old transactions that aren't already fully
|
// duplicate transaction hashes for old transactions that aren't already fully
|
||||||
// spent, double spends, exceeding the maximum allowed signature operations
|
// spent, double spends, exceeding the maximum allowed signature operations
|
||||||
// per block, invalid values in relation to the expected block subisidy, or
|
// per block, invalid values in relation to the expected block subsidy, or fail
|
||||||
// fail transaction script validation.
|
// transaction script validation.
|
||||||
//
|
//
|
||||||
// This function is NOT safe for concurrent access.
|
// This function is NOT safe for concurrent access.
|
||||||
func (b *BlockChain) CheckConnectBlock(block *btcutil.Block) error {
|
func (b *BlockChain) CheckConnectBlock(block *btcutil.Block) error {
|
||||||
|
|
|
@ -125,7 +125,6 @@ func TestCheckSerializedHeight(t *testing.T) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue