You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
224 lines
5.8 KiB
224 lines
5.8 KiB
9 years ago
|
// Copyright (c) 2013-2014 The btcsuite developers
|
||
|
// Use of this source code is governed by an ISC
|
||
|
// license that can be found in the LICENSE file.
|
||
|
|
||
|
package blockchain_test
|
||
|
|
||
|
import (
|
||
|
"compress/bzip2"
|
||
|
"encoding/binary"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"os"
|
||
|
"path/filepath"
|
||
|
"strings"
|
||
|
|
||
|
"github.com/btcsuite/btcd/blockchain"
|
||
|
"github.com/btcsuite/btcd/chaincfg"
|
||
|
"github.com/btcsuite/btcd/database"
|
||
|
_ "github.com/btcsuite/btcd/database/ldb"
|
||
|
_ "github.com/btcsuite/btcd/database/memdb"
|
||
|
"github.com/btcsuite/btcd/wire"
|
||
|
"github.com/btcsuite/btcutil"
|
||
|
)
|
||
|
|
||
|
// testDbType is the database backend type to use for the tests.
|
||
|
const testDbType = "memdb"
|
||
|
|
||
|
// testDbRoot is the root directory used to create all test databases.
|
||
|
const testDbRoot = "testdbs"
|
||
|
|
||
|
// filesExists returns whether or not the named file or directory exists.
|
||
|
func fileExists(name string) bool {
|
||
|
if _, err := os.Stat(name); err != nil {
|
||
|
if os.IsNotExist(err) {
|
||
|
return false
|
||
|
}
|
||
|
}
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
// isSupportedDbType returns whether or not the passed database type is
|
||
|
// currently supported.
|
||
|
func isSupportedDbType(dbType string) bool {
|
||
|
supportedDBs := database.SupportedDBs()
|
||
|
for _, sDbType := range supportedDBs {
|
||
|
if dbType == sDbType {
|
||
|
return true
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
// chainSetup is used to create a new db and chain instance with the genesis
|
||
|
// block already inserted. In addition to the new chain instnce, it returns
|
||
|
// a teardown function the caller should invoke when done testing to clean up.
|
||
|
func chainSetup(dbName string) (*blockchain.BlockChain, func(), error) {
|
||
|
if !isSupportedDbType(testDbType) {
|
||
|
return nil, nil, fmt.Errorf("unsupported db type %v", testDbType)
|
||
|
}
|
||
|
|
||
|
// Handle memory database specially since it doesn't need the disk
|
||
|
// specific handling.
|
||
|
var db database.Db
|
||
|
var teardown func()
|
||
|
if testDbType == "memdb" {
|
||
|
ndb, err := database.CreateDB(testDbType)
|
||
|
if err != nil {
|
||
|
return nil, nil, fmt.Errorf("error creating db: %v", err)
|
||
|
}
|
||
|
db = ndb
|
||
|
|
||
|
// Setup a teardown function for cleaning up. This function is
|
||
|
// returned to the caller to be invoked when it is done testing.
|
||
|
teardown = func() {
|
||
|
db.Close()
|
||
|
}
|
||
|
} else {
|
||
|
// Create the root directory for test databases.
|
||
|
if !fileExists(testDbRoot) {
|
||
|
if err := os.MkdirAll(testDbRoot, 0700); err != nil {
|
||
|
err := fmt.Errorf("unable to create test db "+
|
||
|
"root: %v", err)
|
||
|
return nil, nil, err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Create a new database to store the accepted blocks into.
|
||
|
dbPath := filepath.Join(testDbRoot, dbName)
|
||
|
_ = os.RemoveAll(dbPath)
|
||
|
ndb, err := database.CreateDB(testDbType, dbPath)
|
||
|
if err != nil {
|
||
|
return nil, nil, fmt.Errorf("error creating db: %v", err)
|
||
|
}
|
||
|
db = ndb
|
||
|
|
||
|
// Setup a teardown function for cleaning up. This function is
|
||
|
// returned to the caller to be invoked when it is done testing.
|
||
|
teardown = func() {
|
||
|
dbVersionPath := filepath.Join(testDbRoot, dbName+".ver")
|
||
|
db.Sync()
|
||
|
db.Close()
|
||
|
os.RemoveAll(dbPath)
|
||
|
os.Remove(dbVersionPath)
|
||
|
os.RemoveAll(testDbRoot)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Insert the main network genesis block. This is part of the initial
|
||
|
// database setup.
|
||
|
genesisBlock := btcutil.NewBlock(chaincfg.MainNetParams.GenesisBlock)
|
||
|
_, err := db.InsertBlock(genesisBlock)
|
||
|
if err != nil {
|
||
|
teardown()
|
||
|
err := fmt.Errorf("failed to insert genesis block: %v", err)
|
||
|
return nil, nil, err
|
||
|
}
|
||
|
|
||
|
chain := blockchain.New(db, &chaincfg.MainNetParams, nil)
|
||
|
return chain, teardown, nil
|
||
|
}
|
||
|
|
||
|
// loadTxStore returns a transaction store loaded from a file.
|
||
|
func loadTxStore(filename string) (blockchain.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(blockchain.TxStore)
|
||
|
var uintBuf uint32
|
||
|
for height := uint32(0); height < numItems; height++ {
|
||
|
txD := blockchain.TxData{}
|
||
|
|
||
|
// Serialized transaction length.
|
||
|
err = binary.Read(r, binary.LittleEndian, &uintBuf)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
serializedTxLen := uintBuf
|
||
|
if serializedTxLen > wire.MaxBlockPayload {
|
||
|
return nil, fmt.Errorf("Read serialized transaction "+
|
||
|
"length of %d is larger max allowed %d",
|
||
|
serializedTxLen, wire.MaxBlockPayload)
|
||
|
}
|
||
|
|
||
|
// Transaction.
|
||
|
var msgTx wire.MsgTx
|
||
|
err = msgTx.Deserialize(r)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
txD.Tx = btcutil.NewTx(&msgTx)
|
||
|
|
||
|
// Transaction hash.
|
||
|
txHash := msgTx.TxSha()
|
||
|
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
|
||
|
}
|