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:
Dave Collins 2015-01-08 05:08:11 -06:00
parent 6af9302374
commit d58ea754f3
11 changed files with 173 additions and 21 deletions

View file

@ -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
}

View file

@ -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

View file

@ -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

View file

@ -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:]

View file

@ -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.

View file

@ -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
View 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

Binary file not shown.

BIN
testdata/277647.txstore.bz2 vendored Normal file

Binary file not shown.

View file

@ -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 {

View file

@ -125,7 +125,6 @@ func TestCheckSerializedHeight(t *testing.T) {
continue continue
} }
} }
} }
} }