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
|
||||
|
||||
import (
|
||||
"compress/bzip2"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/conformal/btcchain"
|
||||
"github.com/conformal/btcdb"
|
||||
|
@ -15,6 +19,7 @@ import (
|
|||
_ "github.com/conformal/btcdb/memdb"
|
||||
"github.com/conformal/btcnet"
|
||||
"github.com/conformal/btcutil"
|
||||
"github.com/conformal/btcwire"
|
||||
)
|
||||
|
||||
// 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)
|
||||
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 (
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/conformal/btcchain"
|
||||
"github.com/conformal/btcdb"
|
||||
_ "github.com/conformal/btcdb/memdb"
|
||||
"github.com/conformal/btcnet"
|
||||
"github.com/conformal/btcutil"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// This example demonstrates how to create a new chain instance and use
|
||||
|
|
|
@ -14,8 +14,6 @@ package btcchain
|
|||
import (
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/conformal/btcutil"
|
||||
)
|
||||
|
||||
// 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
|
||||
// available to the test package.
|
||||
func TstCheckSerializedHeight(coinbaseTx *btcutil.Tx, wantHeight int64) error {
|
||||
return checkSerializedHeight(coinbaseTx, wantHeight)
|
||||
}
|
||||
var TstCheckSerializedHeight = checkSerializedHeight
|
||||
|
||||
// TstSetMaxMedianTimeEntries makes the ability to set the maximum number of
|
||||
// median tiem entries available to the test package.
|
||||
func TstSetMaxMedianTimeEntries(val int) {
|
||||
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
|
||||
// of entries is reached.
|
||||
now := time.Unix(time.Now().Unix(), 0)
|
||||
offsetSecs := int64(now.Sub(timeVal).Seconds())
|
||||
offsetSecs := int64(timeVal.Sub(now).Seconds())
|
||||
numOffsets := len(m.offsets)
|
||||
if numOffsets == maxMedianTimeEntries && maxMedianTimeEntries > 0 {
|
||||
m.offsets = m.offsets[1:]
|
||||
|
|
|
@ -27,21 +27,21 @@ func TestMedianTime(t *testing.T) {
|
|||
|
||||
// Various number of entries. The expected offset is only
|
||||
// updated on odd number of elements.
|
||||
{in: []int64{-13, 57, -4, -23, -12}, wantOffset: 12},
|
||||
{in: []int64{55, -13, 61, -52, 39, 55}, wantOffset: -39},
|
||||
{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{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{-5, -4, -3, -2, -1}, wantOffset: 3, useDupID: true},
|
||||
{in: []int64{-13, 57, -4, -23, -12}, wantOffset: -12},
|
||||
{in: []int64{55, -13, 61, -52, 39, 55}, wantOffset: 39},
|
||||
{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{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{-5, -4, -3, -2, -1}, wantOffset: -3, useDupID: true},
|
||||
|
||||
// The offset stops being updated once the max number of entries
|
||||
// has been reached. This is actually a bug from Bitcoin Core,
|
||||
// but since the time is ultimately used as a part of the
|
||||
// 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, 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}, 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},
|
||||
|
||||
// Offsets that are too far away from the local time should
|
||||
// be ignored.
|
||||
|
|
|
@ -217,7 +217,6 @@ func ValidateTransactionScripts(tx *btcutil.Tx, txStore TxStore, flags btcscript
|
|||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// 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
|
||||
// duplicate transaction hashes for old transactions that aren't already fully
|
||||
// spent, double spends, exceeding the maximum allowed signature operations
|
||||
// per block, invalid values in relation to the expected block subisidy, or
|
||||
// fail transaction script validation.
|
||||
// per block, invalid values in relation to the expected block subsidy, or fail
|
||||
// transaction script validation.
|
||||
//
|
||||
// This function is NOT safe for concurrent access.
|
||||
func (b *BlockChain) CheckConnectBlock(block *btcutil.Block) error {
|
||||
|
|
|
@ -125,7 +125,6 @@ func TestCheckSerializedHeight(t *testing.T) {
|
|||
continue
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue