diff --git a/common_test.go b/common_test.go index 882d6a36..1010f185 100644 --- a/common_test.go +++ b/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: + // + // + // + // 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< 0 { m.offsets = m.offsets[1:] diff --git a/mediantime_test.go b/mediantime_test.go index d191c9c9..1b714007 100644 --- a/mediantime_test.go +++ b/mediantime_test.go @@ -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. diff --git a/scriptval.go b/scriptval.go index a7f972f5..5980948e 100644 --- a/scriptval.go +++ b/scriptval.go @@ -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 diff --git a/scriptval_test.go b/scriptval_test.go new file mode 100644 index 00000000..558d6194 --- /dev/null +++ b/scriptval_test.go @@ -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 + } +} diff --git a/testdata/277647.dat.bz2 b/testdata/277647.dat.bz2 new file mode 100644 index 00000000..598420a6 Binary files /dev/null and b/testdata/277647.dat.bz2 differ diff --git a/testdata/277647.txstore.bz2 b/testdata/277647.txstore.bz2 new file mode 100644 index 00000000..e3e38964 Binary files /dev/null and b/testdata/277647.txstore.bz2 differ diff --git a/validate.go b/validate.go index 04cb1787..6dc04565 100644 --- a/validate.go +++ b/validate.go @@ -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 { diff --git a/validate_test.go b/validate_test.go index aa91333b..d45f7ec2 100644 --- a/validate_test.go +++ b/validate_test.go @@ -125,7 +125,6 @@ func TestCheckSerializedHeight(t *testing.T) { continue } } - } }