From 7c46f213e16bd8185cf51a795116a579997c661f Mon Sep 17 00:00:00 2001 From: Alex Akselrod Date: Wed, 25 Mar 2015 18:11:50 -0400 Subject: [PATCH] Fix #340 and DropAfterBlockBySha/NewestSha bug. - Delete spent TX in setclearSpentData when unspent by block disconnect on reorg; return an error when there's more than one record to delete in the spent TX as that should never happen. - Test spent TX deletion when reorg causes block disconnect. - Test for correct NewestSha results after DropAfterBlockBySha. - Fix DropAfterBlockBySha to update info for NewestSha. - Updated copyright statements in modified files --- database/db_test.go | 11 +- database/ldb/leveldb.go | 13 ++- database/reorg_test.go | 175 ++++++++++++++++++++++++++++++ database/testdata/reorgblocks.bz2 | Bin 0 -> 1195 bytes 4 files changed, 194 insertions(+), 5 deletions(-) create mode 100644 database/reorg_test.go create mode 100644 database/testdata/reorgblocks.bz2 diff --git a/database/db_test.go b/database/db_test.go index 801736d8ce..0f5e4a232f 100644 --- a/database/db_test.go +++ b/database/db_test.go @@ -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. @@ -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) + } + } +} diff --git a/database/ldb/leveldb.go b/database/ldb/leveldb.go index 6440b5b6e9..64af166323 100644 --- a/database/ldb/leveldb.go +++ b/database/ldb/leveldb.go @@ -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. @@ -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 @@ -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. diff --git a/database/reorg_test.go b/database/reorg_test.go new file mode 100644 index 0000000000..344a358c0f --- /dev/null +++ b/database/reorg_test.go @@ -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) + 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 +} diff --git a/database/testdata/reorgblocks.bz2 b/database/testdata/reorgblocks.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..1e0285e55582e68f6f0b5a4c2256ae0593d9e849 GIT binary patch literal 1195 zcmV;c1XTM%T4*^jL0KkKS>qBvMF0Td|Nr~{zxV(D{{Qd$|KI=r`@jEp@Be@QfB*kH z_pkr|-_y_oJa_tV0ismMYBa)VF$PRc8Zux4X{HI25X1(Ih9(mMG{|BM z6B9;EOqv5s38sS+37`gzFq&vFGGxLuU=o!zDdL`(lM%Hxr{ztgdTM!3Q_Uu)srpgr zrc*;So6??YhpDvG{VD2tk0jcjDe0;Dnw}};4^wGBN_v={)jc&dev?tXQ1M5i9;4L# zO{waddS+fP^eELLy~hGe7~9 z%aSz1dM`Er{)8f_RNVC@>Jh z+67YVPYgn4m_eC_ZUKoc5of)d?{j$>?w>C7#8#@Ypw(T_$DqfM$Pe9pudd04`{d-6QAJi!RF zDB=xzGZkh=`{>D0SD!>{IKmxvRw?^K(_Mbn{qruO(>!Z4oX0BCJjCKhR7sB2(16H&loX{Gm#?xGdQC= zP`;6XeMe^`X%xwxOG#E4(T^PnL>*0kGLGDB`Xvs8neyv1?g--RIXi^bg~RMOOywLZI)3fUth|CD_>x_#;b`lYA^SXN^tZ^nMBM zIlli|HT5QwB%e5>4Bp=gxEONc2=ge+4)S0l%&j9CZpgqCYiJ=_>_arS?MOw4da0b2 zQbcwRw9mjGtjH&3slwnL4zND#zn2XYmH_$n0zU~mswe@?1YiKJI0g*ChJab(?ntK! J5*%Vj=%9X{{*eFx literal 0 HcmV?d00001