Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #340 and DropAfterBlockBySha/NewestSha bug. #357

Merged
merged 1 commit into from
Mar 30, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion database/db_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2013-2014 Conformal Systems LLC.
// 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.

Expand Down Expand Up @@ -177,3 +177,12 @@ func TestInterface(t *testing.T) {
}
}
}

// TestReorganization performs reorganization tests for each supported DB type
func TestReorganization(t *testing.T) {
for _, dbType := range database.SupportedDBs() {
if _, exists := ignoreDbTypes[dbType]; !exists {
testReorganization(t, dbType)
}
}
}
13 changes: 9 additions & 4 deletions database/ldb/leveldb.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2013-2014 Conformal Systems LLC.
// 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.

Expand Down Expand Up @@ -348,6 +348,10 @@ func (db *LevelDb) DropAfterBlockBySha(sha *wire.ShaHash) (rerr error) {
db.lBatch().Delete(int64ToKey(height))
}

// update the last block cache
db.lastBlkShaCached = true
db.lastBlkSha = *sha
db.lastBlkIdx = keepidx
db.nextBlock = keepidx + 1

return nil
Expand Down Expand Up @@ -546,10 +550,11 @@ func (db *LevelDb) setclearSpentData(txsha *wire.ShaHash, idx uint32, set bool)
spentTxList[len(spentTxList)-1] = nil
if len(spentTxList) == 1 {
// write entry to delete tx from spent pool
// XXX
db.txSpentUpdateMap[*txsha] = &spentTxUpdate{delete: true}
} else {
spentTxList = spentTxList[:len(spentTxList)-1]
// XXX format sTxList and set update Table
// This code should never be hit - aakselrod
return fmt.Errorf("fully-spent tx %v does not have 1 record: "+
"%v", txsha, len(spentTxList))
}

// Create 'new' Tx update data.
Expand Down
175 changes: 175 additions & 0 deletions database/reorg_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
// Copyright (c) 2015 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package database_test

import (
"compress/bzip2"
"encoding/binary"
"io"
"os"
"path/filepath"
"strings"
"testing"

"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
)

// testReorganization performs reorganization tests for the passed DB type.
// Much of the setup is copied from the blockchain package, but the test looks
// to see if each TX in each block in the best chain can be fetched using
// FetchTxBySha. If not, then there's a bug.
func testReorganization(t *testing.T, dbType string) {
db, teardown, err := createDB(dbType, "reorganization", true)
if err != nil {
t.Fatalf("Failed to create test database (%s) %v", dbType, err)
}
defer teardown()

blocks, err := loadReorgBlocks("reorgblocks.bz2")
if err != nil {
t.Fatalf("Error loading file: %v", err)
}

for i := int64(0); i <= 2; i++ {
blkHash, err := blocks[i].Sha()
if err != nil {
t.Fatalf("Error getting SHA for block %d: %v", i, err)
}
_, err = db.InsertBlock(blocks[i])
if err != nil {
t.Fatalf("Error inserting block %d (%v): %v", i, blkHash, err)
}
var txIDs []string
for _, tx := range blocks[i].Transactions() {
txIDs = append(txIDs, tx.Sha().String())
}
}

for i := int64(1); i >= 0; i-- {
blkHash, err := blocks[i].Sha()
if err != nil {
t.Fatalf("Error getting SHA for block %d: %v", i, err)
}
err = db.DropAfterBlockBySha(blkHash)
if err != nil {
t.Fatalf("Error removing block %d for reorganization: %v", i, err)
}
// Exercise NewestSha() to make sure DropAfterBlockBySha() updates the
// info correctly
maxHash, blkHeight, err := db.NewestSha()
if err != nil {
t.Fatalf("Error getting newest block info")
}
if !maxHash.IsEqual(blkHash) || blkHeight != i {
t.Fatalf("NewestSha returned %v (%v), expected %v (%v)", blkHeight,
maxHash, i, blkHash)
}
}

for i := int64(3); i < int64(len(blocks)); i++ {
blkHash, err := blocks[i].Sha()
if err != nil {
t.Fatalf("Error getting SHA for block %dA: %v", i-2, err)
}
_, err = db.InsertBlock(blocks[i])
if err != nil {
t.Fatalf("Error inserting block %dA (%v): %v", i-2, blkHash, err)
}
}

_, maxHeight, err := db.NewestSha()
if err != nil {
t.Fatalf("Error getting newest block info")
}

for i := int64(0); i <= maxHeight; i++ {
blkHash, err := db.FetchBlockShaByHeight(i)
if err != nil {
t.Fatalf("Error fetching SHA for block %d: %v", i, err)
}
block, err := db.FetchBlockBySha(blkHash)
if err != nil {
t.Fatalf("Error fetching block %d (%v): %v", i, blkHash, err)
}
for _, tx := range block.Transactions() {
_, err := db.FetchTxBySha(tx.Sha())
if err != nil {
t.Fatalf("Error fetching transaction %v: %v", tx.Sha(), err)
}
}
}
}

// loadReorgBlocks reads files containing bitcoin block data (bzipped but
// otherwise in the format bitcoind writes) from disk and returns them as an
// array of btcutil.Block. This is copied from the blockchain package, which
// itself largely borrowed it from the test code in this package.
func loadReorgBlocks(filename string) ([]*btcutil.Block, error) {
filename = filepath.Join("testdata/", filename)

var blocks []*btcutil.Block
var err error

var network = wire.SimNet
var dr io.Reader
var fi io.ReadCloser

fi, err = os.Open(filename)
if err != nil {
return blocks, err
}

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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This error isn't checked

if err != nil {
return blocks, err
}
blocklen := rintbuf

rbytes := make([]byte, blocklen)

// read block
numbytes, err := dr.Read(rbytes)
if err != nil {
return blocks, err
}
if uint32(numbytes) != blocklen {
return blocks, io.ErrUnexpectedEOF
}

block, err = btcutil.NewBlockFromBytes(rbytes)
if err != nil {
return blocks, err
}
blocks = append(blocks, block)
}

return blocks, nil
}
Binary file added database/testdata/reorgblocks.bz2
Binary file not shown.