// Copyright (c) 2013-2014 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 ( "compress/bzip2" "encoding/binary" "io" "os" "path/filepath" "strings" "testing" "github.com/conformal/btcchain" "github.com/conformal/btcutil" "github.com/conformal/btcwire" ) // TestReorganization loads a set of test blocks which force a chain // reorganization to test the block chain handling code. // The test blocks were originally from a post on the bitcoin talk forums: // https://bitcointalk.org/index.php?topic=46370.msg577556#msg577556 func TestReorganization(t *testing.T) { // Intentionally load the side chain blocks out of order to ensure // orphans are handled properly along with chain reorganization. testFiles := []string{ "blk_0_to_4.dat.bz2", "blk_4A.dat.bz2", "blk_5A.dat.bz2", "blk_3A.dat.bz2", } var blocks []*btcutil.Block for _, file := range testFiles { blockTmp, err := loadBlocks(file) if err != nil { t.Errorf("Error loading file: %v\n", err) } for _, block := range blockTmp { blocks = append(blocks, block) } } t.Logf("Number of blocks: %v\n", len(blocks)) // Create a new database and chain instance to run tests against. chain, teardownFunc, err := chainSetup("reorg") if err != nil { t.Errorf("Failed to setup chain instance: %v", err) return } defer teardownFunc() // Since we're not dealing with the real block chain, disable // checkpoints and set the coinbase maturity to 1. chain.DisableCheckpoints(true) btcchain.TstSetCoinbaseMaturity(1) timeSource := btcchain.NewMedianTime() expectedOrphans := map[int]struct{}{5: struct{}{}, 6: struct{}{}} for i := 1; i < len(blocks); i++ { isOrphan, err := chain.ProcessBlock(blocks[i], timeSource, btcchain.BFNone) if err != nil { t.Errorf("ProcessBlock fail on block %v: %v\n", i, err) return } if _, ok := expectedOrphans[i]; !ok && isOrphan { t.Errorf("ProcessBlock incorrectly returned block %v "+ "is an orphan\n", i) } } return } // loadBlocks reads files containing bitcoin block data (gzipped but otherwise // in the format bitcoind writes) from disk and returns them as an array of // btcutil.Block. This is largely borrowed from the test code in btcdb. func loadBlocks(filename string) (blocks []*btcutil.Block, err error) { filename = filepath.Join("testdata/", filename) var network = btcwire.MainNet var dr io.Reader var fi io.ReadCloser fi, err = os.Open(filename) if err != nil { return } if strings.HasSuffix(filename, ".bz2") { dr = bzip2.NewReader(fi) } else { dr = fi } defer fi.Close() var block *btcutil.Block err = nil for height := int64(1); err == nil; height++ { var rintbuf uint32 err = binary.Read(dr, binary.LittleEndian, &rintbuf) if err == io.EOF { // hit end of file at expected offset: no warning height-- err = nil break } if err != nil { break } if rintbuf != uint32(network) { break } err = binary.Read(dr, binary.LittleEndian, &rintbuf) blocklen := rintbuf rbytes := make([]byte, blocklen) // read block dr.Read(rbytes) block, err = btcutil.NewBlockFromBytes(rbytes) if err != nil { return } blocks = append(blocks, block) } return }