// createDB creates a new db instance and returns a teardown function the caller // should invoke when done testing to clean up. The close flag indicates // whether or not the teardown function should sync and close the database // during teardown. func createDB(dbType, dbName string, close bool) (database.Db, func(), error) { // Handle memory database specially since it doesn't need the disk // specific handling. if dbType == "memdb" { db, err := database.CreateDB(dbType) if err != nil { return nil, nil, fmt.Errorf("error creating db: %v", err) } // Setup a teardown function for cleaning up. This function is // returned to the caller to be invoked when it is done testing. teardown := func() { if close { db.Close() } } return db, teardown, nil } // 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) db, err := database.CreateDB(dbType, dbPath) if err != nil { return nil, nil, fmt.Errorf("error creating db: %v", err) } // 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") if close { db.Sync() db.Close() } os.RemoveAll(dbPath) os.Remove(dbVersionPath) os.RemoveAll(testDbRoot) } return db, teardown, nil }
// This example demonstrates creating a new database and inserting the genesis // block into it. func ExampleCreateDB() { // Notice in these example imports that the memdb driver is loaded. // Ordinarily this would be whatever driver(s) your application // requires. // import ( // "github.com/decred/dcrd/database" // _ "github.com/decred/dcrd/database/memdb" // ) // Create a database and schedule it to be closed on exit. This example // uses a memory-only database to avoid needing to write anything to // the disk. Typically, you would specify a persistent database driver // such as "leveldb" and give it a database name as the second // parameter. db, err := database.CreateDB("memdb") if err != nil { fmt.Println(err) return } defer db.Close() // Insert the main network genesis block. genesis := dcrutil.NewBlock(chaincfg.TestNetParams.GenesisBlock) genesis.SetHeight(0) newHeight, err := db.InsertBlock(genesis) if err != nil { fmt.Println(err) return } fmt.Println("New height:", newHeight) // Output: // New height: 0 }
func setUpTestDb(t *testing.T, dbname string) (*testDb, error) { // Ignore db remove errors since it means we didn't have an old one. dbnamever := dbname + ".ver" _ = os.RemoveAll(dbname) _ = os.RemoveAll(dbnamever) db, err := database.CreateDB("leveldb", dbname) if err != nil { return nil, err } testdatafile := filepath.Join("..", "/../blockchain/testdata", "blocks0to168.bz2") blocks, err := loadBlocks(t, testdatafile) if err != nil { return nil, err } cleanUp := func() { db.Close() os.RemoveAll(dbname) os.RemoveAll(dbnamever) } return &testDb{ db: db, blocks: blocks, dbName: dbname, dbNameVer: dbnamever, cleanUpFunc: cleanUp, }, nil }
// TODO Make more elaborate tests for difficulty. The difficulty algorithms // have already been tested to death in simnet/testnet/mainnet simulations, // but we should really have a unit test for them that includes tests for // edge cases. func TestDiff(t *testing.T) { db, err := database.CreateDB("memdb") if err != nil { t.Errorf("Failed to create database: %v\n", err) return } defer db.Close() var tmdb *stake.TicketDB genesisBlock := dcrutil.NewBlock(chaincfg.MainNetParams.GenesisBlock) _, err = db.InsertBlock(genesisBlock) if err != nil { t.Errorf("Failed to insert genesis block: %v\n", err) return } chain := blockchain.New(db, tmdb, &chaincfg.MainNetParams, nil, nil) //timeSource := blockchain.NewMedianTime() // Grab some blocks // Build fake blockchain // Calc new difficulty ts := time.Now() d, err := chain.CalcNextRequiredDifficulty(ts) if err != nil { t.Errorf("Failed to get difficulty: %v\n", err) return } if d != 486604799 { // This is hardcoded in genesis block but not exported anywhere. t.Error("Failed to get initial difficulty.") } sd, err := chain.CalcNextRequiredStakeDifficulty() if err != nil { t.Errorf("Failed to get stake difficulty: %v\n", err) return } if sd != chaincfg.MainNetParams.MinimumStakeDiff { t.Error("Incorrect initial stake difficulty.") } // Compare // Repeat for a few more }
// This example demonstrates how to create a new chain instance and use // ProcessBlock to attempt to attempt add a block to the chain. As the package // overview documentation describes, this includes all of the Decred consensus // rules. This example intentionally attempts to insert a duplicate genesis // block to illustrate how an invalid block is handled. func ExampleBlockChain_ProcessBlock() { // Create a new database to store the accepted blocks into. Typically // this would be opening an existing database and would not use memdb // which is a memory-only database backend, but we create a new db // here so this is a complete working example. db, err := database.CreateDB("memdb") if err != nil { fmt.Printf("Failed to create database: %v\n", err) return } defer db.Close() var tmdb *stake.TicketDB // Insert the main network genesis block. This is part of the initial // database setup. Like above, this typically would not be needed when // opening an existing database. genesisBlock := dcrutil.NewBlock(chaincfg.MainNetParams.GenesisBlock) _, err = db.InsertBlock(genesisBlock) if err != nil { fmt.Printf("Failed to insert genesis block: %v\n", err) return } // Create a new BlockChain instance without an initialized signature // verification cache, using the underlying database for the main // bitcoin network and ignore notifications. chain := blockchain.New(db, tmdb, &chaincfg.MainNetParams, nil, nil) // Create a new median time source that is required by the upcoming // call to ProcessBlock. Ordinarily this would also add time values // obtained from other peers on the network so the local time is // adjusted to be in agreement with other peers. timeSource := blockchain.NewMedianTime() // Process a block. For this example, we are going to intentionally // cause an error by trying to process the genesis block which already // exists. isOrphan, _, err := chain.ProcessBlock(genesisBlock, timeSource, blockchain.BFNone) if err != nil { fmt.Printf("Failed to process block: %v\n", err) return } fmt.Printf("Block accepted. Is it an orphan?: %v", isOrphan) // This output is dependent on the genesis block, and needs to be // updated if the mainnet genesis block is updated. // Output: // Failed to process block: already have block 267a53b5ee86c24a48ec37aee4f4e7c0c4004892b7259e695e9f5b321f1ab9d2 }
// exampleLoadDB is used in the example to elide the setup code. func exampleLoadDB() (database.Db, error) { db, err := database.CreateDB("memdb") if err != nil { return nil, err } // Insert the main network genesis block. genesis := dcrutil.NewBlock(chaincfg.TestNetParams.GenesisBlock) genesis.SetHeight(0) _, err = db.InsertBlock(genesis) if err != nil { return nil, err } return db, err }
func TestEmptyDB(t *testing.T) { dbname := "tstdbempty" dbnamever := dbname + ".ver" _ = os.RemoveAll(dbname) _ = os.RemoveAll(dbnamever) db, err := database.CreateDB("leveldb", dbname) if err != nil { t.Errorf("Failed to open test database %v", err) return } defer os.RemoveAll(dbname) defer os.RemoveAll(dbnamever) sha, height, err := db.NewestSha() if !sha.IsEqual(&chainhash.Hash{}) { t.Errorf("sha not zero hash") } if height != -1 { t.Errorf("height not -1 %v", height) } // This is a reopen test if err := db.Close(); err != nil { t.Errorf("Close: unexpected error: %v", err) } db, err = database.OpenDB("leveldb", dbname) if err != nil { t.Errorf("Failed to open test database %v", err) return } defer func() { if err := db.Close(); err != nil { t.Errorf("Close: unexpected error: %v", err) } }() sha, height, err = db.NewestSha() if !sha.IsEqual(&chainhash.Hash{}) { t.Errorf("sha not zero hash") } if height != -1 { t.Errorf("height not -1 %v", height) } }
// TestCreateOpenUnsupported ensures that attempting to create or open an // unsupported database type is handled properly. func TestCreateOpenUnsupported(t *testing.T) { // Ensure creating a database with an unsupported type fails with the // expected error. dbType := "unsupported" _, err := database.CreateDB(dbType, "unsupportedcreatetest") if err != database.ErrDbUnknownType { t.Errorf("TestCreateOpenUnsupported: expected error not "+ "received - got: %v, want %v", err, database.ErrDbUnknownType) return } // Ensure opening a database with the new type fails with the expected // error. _, err = database.OpenDB(dbType, "unsupportedopentest") if err != database.ErrDbUnknownType { t.Errorf("TestCreateOpenUnsupported: expected error not "+ "received - got: %v, want %v", err, database.ErrDbUnknownType) return } }
// TestCreateOpenFail ensures that errors which occur while opening or closing // a database are handled properly. func TestCreateOpenFail(t *testing.T) { // bogusCreateDB is a function which acts as a bogus create and open // driver function that intentionally returns a failure which can be // detected. dbType := "createopenfail" openError := fmt.Errorf("failed to create or open database for "+ "database type [%v]", dbType) bogusCreateDB := func(args ...interface{}) (database.Db, error) { return nil, openError } // Create and add driver that intentionally fails when created or opened // to ensure errors on database open and create are handled properly. driver := database.DriverDB{ DbType: dbType, CreateDB: bogusCreateDB, OpenDB: bogusCreateDB, } database.AddDBDriver(driver) // Ensure creating a database with the new type fails with the expected // error. _, err := database.CreateDB(dbType, "createfailtest") if err != openError { t.Errorf("TestCreateOpenFail: expected error not received - "+ "got: %v, want %v", err, openError) return } // Ensure opening a database with the new type fails with the expected // error. _, err = database.OpenDB(dbType, "openfailtest") if err != openError { t.Errorf("TestCreateOpenFail: expected error not received - "+ "got: %v, want %v", err, openError) return } }
// loadBlockDB opens the block database and returns a handle to it. func loadBlockDB() (database.Db, error) { // The database name is based on the database type. dbName := blockDbNamePrefix + "_" + cfg.DbType if cfg.DbType == "sqlite" { dbName = dbName + ".db" } dbPath := filepath.Join(cfg.DataDir, dbName) log.Infof("Loading block database from '%s'", dbPath) db, err := database.OpenDB(cfg.DbType, dbPath) if err != nil { // Return the error if it's not because the database doesn't // exist. if err != database.ErrDbDoesNotExist { return nil, err } // Create the db if it does not exist. err = os.MkdirAll(cfg.DataDir, 0700) if err != nil { return nil, err } db, err = database.CreateDB(cfg.DbType, dbPath) if err != nil { return nil, err } } // Get the latest block height from the database. _, height, err := db.NewestSha() if err != nil { db.Close() return nil, err } log.Infof("Block database loaded with block height %d", height) return db, nil }
func TestTicketDB(t *testing.T) { // Declare some useful variables testBCHeight := int64(168) // Set up a DB database, err := database.CreateDB("leveldb", "ticketdb_test") if err != nil { t.Errorf("Db create error: %v", err.Error()) } // Make a new tmdb to fill with dummy live and used tickets var tmdb stake.TicketDB tmdb.Initialize(simNetParams, database) filename := filepath.Join("..", "/../blockchain/testdata", "blocks0to168.bz2") fi, err := os.Open(filename) bcStream := bzip2.NewReader(fi) defer fi.Close() // Create a buffer of the read file bcBuf := new(bytes.Buffer) bcBuf.ReadFrom(bcStream) // Create decoder from the buffer and a map to store the data bcDecoder := gob.NewDecoder(bcBuf) blockchain := make(map[int64][]byte) // Decode the blockchain into the map if err := bcDecoder.Decode(&blockchain); err != nil { t.Errorf("error decoding test blockchain") } var CopyOfMapsAtBlock50, CopyOfMapsAtBlock168 stake.TicketMaps var ticketsToSpendIn167 []chainhash.Hash var sortedTickets167 []*stake.TicketData for i := int64(0); i <= testBCHeight; i++ { block, err := dcrutil.NewBlockFromBytes(blockchain[i]) if err != nil { t.Errorf("block deserialization error on block %v", i) } block.SetHeight(i) database.InsertBlock(block) tmdb.InsertBlock(block) if i == 50 { // Create snapshot of tmdb at block 50 CopyOfMapsAtBlock50, err = cloneTicketDB(&tmdb) if err != nil { t.Errorf("db cloning at block 50 failure! %v", err) } } // Test to make sure that ticket selection is working correctly. if i == 167 { // Sort the entire list of tickets lexicographically by sorting // each bucket and then appending it to the list. Then store it // to use in the next block. totalTickets := 0 sortedSlice := make([]*stake.TicketData, 0) for i := 0; i < stake.BucketsSize; i++ { tix, err := tmdb.DumpLiveTickets(uint8(i)) if err != nil { t.Errorf("error dumping live tickets") } mapLen := len(tix) totalTickets += mapLen tempTdSlice := stake.NewTicketDataSlice(mapLen) itr := 0 // Iterator for _, td := range tix { tempTdSlice[itr] = td itr++ } sort.Sort(tempTdSlice) sortedSlice = append(sortedSlice, tempTdSlice...) } sortedTickets167 = sortedSlice } if i == 168 { parentBlock, err := dcrutil.NewBlockFromBytes(blockchain[i-1]) if err != nil { t.Errorf("block deserialization error on block %v", i-1) } pbhB, err := parentBlock.MsgBlock().Header.Bytes() if err != nil { t.Errorf("block header serialization error") } prng := stake.NewHash256PRNG(pbhB) ts, err := stake.FindTicketIdxs(int64(len(sortedTickets167)), int(simNetParams.TicketsPerBlock), prng) if err != nil { t.Errorf("failure on FindTicketIdxs") } for _, idx := range ts { ticketsToSpendIn167 = append(ticketsToSpendIn167, sortedTickets167[idx].SStxHash) } // Make sure that the tickets that were supposed to be spent or // missed were. spentTix, err := tmdb.DumpSpentTickets(i) if err != nil { t.Errorf("DumpSpentTickets failure") } for _, h := range ticketsToSpendIn167 { if _, ok := spentTix[h]; !ok { t.Errorf("missing ticket %v that should have been missed "+ "or spent in block %v", h, i) } } // Create snapshot of tmdb at block 168 CopyOfMapsAtBlock168, err = cloneTicketDB(&tmdb) if err != nil { t.Errorf("db cloning at block 168 failure! %v", err) } } } // Remove five blocks from HEAD~1 _, _, _, err = tmdb.RemoveBlockToHeight(50) if err != nil { t.Errorf("error: %v", err) } // Test if the roll back was symmetric to the earlier snapshot if !reflect.DeepEqual(tmdb.DumpMapsPointer(), CopyOfMapsAtBlock50) { t.Errorf("The td did not restore to a previous block height correctly!") } // Test rescanning a ticket db err = tmdb.RescanTicketDB() if err != nil { t.Errorf("rescanticketdb err: %v", err.Error()) } // Test if the db file storage was symmetric to the earlier snapshot if !reflect.DeepEqual(tmdb.DumpMapsPointer(), CopyOfMapsAtBlock168) { t.Errorf("The td did not rescan to HEAD correctly!") } err = os.Mkdir("testdata/", os.FileMode(0700)) if err != nil { t.Error(err) } // Store the ticket db to disk err = tmdb.Store("testdata/", "testtmdb") if err != nil { t.Errorf("error: %v", err) } var tmdb2 stake.TicketDB err = tmdb2.LoadTicketDBs("testdata/", "testtmdb", simNetParams, database) if err != nil { t.Errorf("error: %v", err) } // Test if the db file storage was symmetric to previously rescanned one if !reflect.DeepEqual(tmdb.DumpMapsPointer(), tmdb2.DumpMapsPointer()) { t.Errorf("The td did not rescan to a previous block height correctly!") } tmdb2.Close() // Test dumping missing tickets from block 152 missedIn152, _ := chainhash.NewHashFromStr( "84f7f866b0af1cc278cb8e0b2b76024a07542512c76487c83628c14c650de4fa") tmdb.RemoveBlockToHeight(152) missedTix, err := tmdb.DumpMissedTickets() if err != nil { t.Errorf("err dumping missed tix: %v", err.Error()) } if _, exists := missedTix[*missedIn152]; !exists { t.Errorf("couldn't finding missed tx 1 %v in tmdb @ block 152!", missedIn152) } tmdb.RescanTicketDB() // Make sure that the revoked map contains the revoked tx revokedSlice := []*chainhash.Hash{missedIn152} revokedTix, err := tmdb.DumpRevokedTickets() if err != nil { t.Errorf("err dumping missed tix: %v", err.Error()) } if len(revokedTix) != 1 { t.Errorf("revoked ticket map is wrong len, got %v, want %v", len(revokedTix), 1) } _, wasMissedIn152 := revokedTix[*revokedSlice[0]] ticketsRevoked := wasMissedIn152 if !ticketsRevoked { t.Errorf("revoked ticket map did not include tickets missed in " + "block 152 and later revoked") } database.Close() tmdb.Close() os.RemoveAll("ticketdb_test") os.Remove("./ticketdb_test.ver") os.Remove("testdata/testtmdb") os.Remove("testdata") }
func Test_dupTx(t *testing.T) { // Ignore db remove errors since it means we didn't have an old one. dbname := fmt.Sprintf("tstdbdup0") dbnamever := dbname + ".ver" _ = os.RemoveAll(dbname) _ = os.RemoveAll(dbnamever) db, err := database.CreateDB("leveldb", dbname) if err != nil { t.Errorf("Failed to open test database %v", err) return } defer os.RemoveAll(dbname) defer os.RemoveAll(dbnamever) defer func() { if err := db.Close(); err != nil { t.Errorf("Close: unexpected error: %v", err) } }() testdatafile := filepath.Join("../", "../blockchain/testdata", "blocks0to168.bz2") blocks, err := loadBlocks(t, testdatafile) if err != nil { t.Errorf("Unable to load blocks from test data for: %v", err) return } var lastSha *chainhash.Hash // Populate with the fisrt 256 blocks, so we have blocks to 'mess with' err = nil out: for height := int64(0); height < int64(len(blocks)); height++ { block := blocks[height] if height != 0 { // except for NoVerify which does not allow lookups check inputs mblock := block.MsgBlock() //t.Errorf("%v", blockchain.DebugBlockString(block)) parentBlock := blocks[height-1] mParentBlock := parentBlock.MsgBlock() var txneededList []*chainhash.Hash opSpentInBlock := make(map[wire.OutPoint]struct{}) if dcrutil.IsFlagSet16(dcrutil.BlockValid, mParentBlock.Header.VoteBits) { for _, tx := range mParentBlock.Transactions { for _, txin := range tx.TxIn { if txin.PreviousOutPoint.Index == uint32(4294967295) { continue } if existsInOwnBlockRegTree(mParentBlock, txin.PreviousOutPoint.Hash) { _, used := opSpentInBlock[txin.PreviousOutPoint] if !used { // Origin tx is in the block and so hasn't been // added yet, continue opSpentInBlock[txin.PreviousOutPoint] = struct{}{} continue } else { t.Errorf("output ref %v attempted double spend of previously spend output", txin.PreviousOutPoint) } } origintxsha := &txin.PreviousOutPoint.Hash txneededList = append(txneededList, origintxsha) exists, err := db.ExistsTxSha(origintxsha) if err != nil { t.Errorf("ExistsTxSha: unexpected error %v ", err) } if !exists { t.Errorf("referenced tx not found %v (height %v)", origintxsha, height) } _, err = db.FetchTxBySha(origintxsha) if err != nil { t.Errorf("referenced tx not found %v err %v ", origintxsha, err) } } } } for _, stx := range mblock.STransactions { for _, txin := range stx.TxIn { if txin.PreviousOutPoint.Index == uint32(4294967295) { continue } if existsInOwnBlockRegTree(mParentBlock, txin.PreviousOutPoint.Hash) { _, used := opSpentInBlock[txin.PreviousOutPoint] if !used { // Origin tx is in the block and so hasn't been // added yet, continue opSpentInBlock[txin.PreviousOutPoint] = struct{}{} continue } else { t.Errorf("output ref %v attempted double spend of previously spend output", txin.PreviousOutPoint) } } origintxsha := &txin.PreviousOutPoint.Hash txneededList = append(txneededList, origintxsha) exists, err := db.ExistsTxSha(origintxsha) if err != nil { t.Errorf("ExistsTxSha: unexpected error %v ", err) } if !exists { t.Errorf("referenced tx not found %v", origintxsha) } _, err = db.FetchTxBySha(origintxsha) if err != nil { t.Errorf("referenced tx not found %v err %v ", origintxsha, err) } } } txlist := db.FetchUnSpentTxByShaList(txneededList) for _, txe := range txlist { if txe.Err != nil { t.Errorf("tx list fetch failed %v err %v ", txe.Sha, txe.Err) break out } } } newheight, err := db.InsertBlock(block) if err != nil { t.Errorf("failed to insert block %v err %v", height, err) break out } if newheight != height { t.Errorf("height mismatch expect %v returned %v", height, newheight) break out } newSha, blkid, err := db.NewestSha() if err != nil { t.Errorf("failed to obtain latest sha %v %v", height, err) } if blkid != height { t.Errorf("height doe not match latest block height %v %v %v", blkid, height, err) } blkSha := block.Sha() if *newSha != *blkSha { t.Errorf("Newest block sha does not match freshly inserted one %v %v %v ", newSha, blkSha, err) } lastSha = blkSha } // generate a new block based on the last sha // these block are not verified, so there are a bunch of garbage fields // in the 'generated' block. var bh wire.BlockHeader bh.Version = 0 bh.PrevBlock = *lastSha // Bits, Nonce are not filled in mblk := wire.NewMsgBlock(&bh) hash, _ := chainhash.NewHashFromStr("c23953c56cb2ef8e4698e3ed3b0fc4c837754d3cd16485192d893e35f32626b4") po := wire.NewOutPoint(hash, 0, dcrutil.TxTreeRegular) txI := wire.NewTxIn(po, []byte("garbage")) txO := wire.NewTxOut(50000000, []byte("garbageout")) var tx wire.MsgTx tx.AddTxIn(txI) tx.AddTxOut(txO) mblk.AddTransaction(&tx) blk := dcrutil.NewBlock(mblk) fetchList := []*chainhash.Hash{hash} listReply := db.FetchUnSpentTxByShaList(fetchList) for _, lr := range listReply { if lr.Err != nil { t.Errorf("sha %v spent %v err %v\n", lr.Sha, lr.TxSpent, lr.Err) } } _, err = db.InsertBlock(blk) if err != nil { t.Errorf("failed to insert phony block %v", err) } // ok, did it 'spend' the tx ? listReply = db.FetchUnSpentTxByShaList(fetchList) for _, lr := range listReply { if lr.Err != nil && lr.Err != database.ErrTxShaMissing { t.Errorf("sha %v spent %v err %v\n", lr.Sha, lr.TxSpent, lr.Err) } } txlist := blk.Transactions() for _, tx := range txlist { txsha := tx.Sha() txReply, err := db.FetchTxBySha(txsha) if err != nil { t.Errorf("fully spent lookup %v err %v\n", hash, err) } else { for _, lr := range txReply { if lr.Err != nil { t.Errorf("stx %v spent %v err %v\n", lr.Sha, lr.TxSpent, lr.Err) } } } } err = db.DropAfterBlockBySha(lastSha) if err != nil { t.Errorf("failed to drop spending block %v", err) } }
// TestClosed ensure calling the interface functions on a closed database // returns appropriate errors for the interface functions that return errors // and does not panic or otherwise misbehave for functions which do not return // errors. func TestClosed(t *testing.T) { db, err := database.CreateDB("memdb") if err != nil { t.Errorf("Failed to open test database %v", err) return } _, err = db.InsertBlock(dcrutil.NewBlock(chaincfg.MainNetParams.GenesisBlock)) if err != nil { t.Errorf("InsertBlock: %v", err) } if err := db.Close(); err != nil { t.Errorf("Close: unexpected error %v", err) } genesisHash := chaincfg.MainNetParams.GenesisHash if err := db.DropAfterBlockBySha(genesisHash); err != memdb.ErrDbClosed { t.Errorf("DropAfterBlockBySha: unexpected error %v", err) } if _, err := db.ExistsSha(genesisHash); err != memdb.ErrDbClosed { t.Errorf("ExistsSha: Unexpected error: %v", err) } if _, err := db.FetchBlockBySha(genesisHash); err != memdb.ErrDbClosed { t.Errorf("FetchBlockBySha: unexpected error %v", err) } if _, err := db.FetchBlockShaByHeight(0); err != memdb.ErrDbClosed { t.Errorf("FetchBlockShaByHeight: unexpected error %v", err) } if _, err := db.FetchHeightRange(0, 1); err != memdb.ErrDbClosed { t.Errorf("FetchHeightRange: unexpected error %v", err) } genesisCoinbaseTx := chaincfg.MainNetParams.GenesisBlock.Transactions[0] coinbaseHash := genesisCoinbaseTx.TxSha() if _, err := db.ExistsTxSha(&coinbaseHash); err != memdb.ErrDbClosed { t.Errorf("ExistsTxSha: unexpected error %v", err) } if _, err := db.FetchTxBySha(genesisHash); err != memdb.ErrDbClosed { t.Errorf("FetchTxBySha: unexpected error %v", err) } requestHashes := []*chainhash.Hash{genesisHash} reply := db.FetchTxByShaList(requestHashes) if len(reply) != len(requestHashes) { t.Errorf("FetchUnSpentTxByShaList unexpected number of replies "+ "got: %d, want: %d", len(reply), len(requestHashes)) } for i, txLR := range reply { wantReply := &database.TxListReply{ Sha: requestHashes[i], Err: memdb.ErrDbClosed, } if !reflect.DeepEqual(wantReply, txLR) { t.Errorf("FetchTxByShaList unexpected reply\ngot: %v\n"+ "want: %v", txLR, wantReply) } } reply = db.FetchUnSpentTxByShaList(requestHashes) if len(reply) != len(requestHashes) { t.Errorf("FetchUnSpentTxByShaList unexpected number of replies "+ "got: %d, want: %d", len(reply), len(requestHashes)) } for i, txLR := range reply { wantReply := &database.TxListReply{ Sha: requestHashes[i], Err: memdb.ErrDbClosed, } if !reflect.DeepEqual(wantReply, txLR) { t.Errorf("FetchUnSpentTxByShaList unexpected reply\n"+ "got: %v\nwant: %v", txLR, wantReply) } } if _, _, err := db.NewestSha(); err != memdb.ErrDbClosed { t.Errorf("NewestSha: unexpected error %v", err) } if err := db.Sync(); err != memdb.ErrDbClosed { t.Errorf("Sync: unexpected error %v", err) } if err := db.RollbackClose(); err != memdb.ErrDbClosed { t.Errorf("RollbackClose: unexpected error %v", err) } if err := db.Close(); err != memdb.ErrDbClosed { t.Errorf("Close: unexpected error %v", err) } }
// 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, params *chaincfg.Params) (*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 tmdb := new(stake.TicketDB) 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() { tmdb.Close() 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") tmdb.Close() 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 := dcrutil.NewBlock(params.GenesisBlock) genesisBlock.SetHeight(int64(0)) _, err := db.InsertBlock(genesisBlock) if err != nil { teardown() err := fmt.Errorf("failed to insert genesis block: %v", err) return nil, nil, err } // Start the ticket database. tmdb.Initialize(params, db) tmdb.RescanTicketDB() chain := blockchain.New(db, tmdb, params, nil) return chain, teardown, nil }
// insert every block in the test chain // after each insert, fetch all the tx affected by the latest // block and verify that the the tx is spent/unspent // new tx should be fully unspent, referenced tx should have // the associated txout set to spent. // checks tx tree stake only func testUnspentInsertStakeTree(t *testing.T) { // Ignore db remove errors since it means we didn't have an old one. dbname := fmt.Sprintf("tstdbuspnt1") dbnamever := dbname + ".ver" _ = os.RemoveAll(dbname) _ = os.RemoveAll(dbnamever) db, err := database.CreateDB("leveldb", dbname) if err != nil { t.Errorf("Failed to open test database %v", err) return } defer os.RemoveAll(dbname) defer os.RemoveAll(dbnamever) defer func() { if err := db.Close(); err != nil { t.Errorf("Close: unexpected error: %v", err) } }() blocks := loadblocks(t) endtest: for height := int64(0); height < int64(len(blocks))-1; height++ { block := blocks[height] var txneededList []*chainhash.Hash var txlookupList []*chainhash.Hash var txOutList []*chainhash.Hash var txInList []*wire.OutPoint spentFromParent := make(map[wire.OutPoint]struct{}) for _, tx := range block.MsgBlock().STransactions { for _, txin := range tx.TxIn { if txin.PreviousOutPoint.Index == uint32(4294967295) { continue } origintxsha := &txin.PreviousOutPoint.Hash exists, err := db.ExistsTxSha(origintxsha) if err != nil { t.Errorf("ExistsTxSha: unexpected error %v ", err) } if !exists { // Check and see if the outpoint references txtreeregular of // the previous block. If it does, make sure nothing in tx // treeregular spends it in flight. Then check make sure it's // not currently spent for this block. If it isn't, mark it // spent and skip lookup in the db below, since the db won't // yet be able to add it as it's still to be inserted. spentFromParentReg := false parent := blocks[height-1] parentValid := dcrutil.IsFlagSet16(dcrutil.BlockValid, block.MsgBlock().Header.VoteBits) if parentValid { for _, prtx := range parent.Transactions() { // Check and make sure it's not being spent in this tx // tree first by an in flight tx. Mark it spent if it // is so it fails the check below. for _, prtxCheck := range parent.Transactions() { for _, prTxIn := range prtxCheck.MsgTx().TxIn { if prTxIn.PreviousOutPoint == txin.PreviousOutPoint { spentFromParent[txin.PreviousOutPoint] = struct{}{} } } } // If it is in the tree, make sure it's not already spent // somewhere else and mark it spent. Set the flag below // so we skip lookup. if prtx.Sha().IsEqual(origintxsha) { if _, spent := spentFromParent[txin.PreviousOutPoint]; !spent { spentFromParent[txin.PreviousOutPoint] = struct{}{} spentFromParentReg = true } } } } if !spentFromParentReg { t.Errorf("referenced tx not found %v %v", origintxsha, height) } else { continue } } txInList = append(txInList, &txin.PreviousOutPoint) txneededList = append(txneededList, origintxsha) txlookupList = append(txlookupList, origintxsha) } txshaname := tx.TxSha() txlookupList = append(txlookupList, &txshaname) txOutList = append(txOutList, &txshaname) } txneededmap := map[chainhash.Hash]*database.TxListReply{} txlist := db.FetchUnSpentTxByShaList(txneededList) for _, txe := range txlist { if txe.Err != nil { t.Errorf("tx list fetch failed %v err %v", txe.Sha, txe.Err) break endtest } txneededmap[*txe.Sha] = txe } for _, spend := range txInList { itxe := txneededmap[spend.Hash] if itxe.TxSpent[spend.Index] == true { t.Errorf("txin %v:%v is already spent", spend.Hash, spend.Index) } } newheight, err := db.InsertBlock(block) if err != nil { t.Errorf("failed to insert block %v err %v", height, err) break endtest } if newheight != height { t.Errorf("height mismatch expect %v returned %v", height, newheight) break endtest } // only check transactions if current block is valid txlookupmap := map[chainhash.Hash]*database.TxListReply{} txlist = db.FetchTxByShaList(txlookupList) for _, txe := range txlist { if txe.Err != nil { t.Errorf("tx list fetch failed %v err %v ", txe.Sha, txe.Err, height) break endtest } txlookupmap[*txe.Sha] = txe } for _, spend := range txInList { itxe := txlookupmap[spend.Hash] if itxe.TxSpent[spend.Index] == false { t.Errorf("txin %v:%v is unspent %v", spend.Hash, spend.Index, itxe.TxSpent) } } for _, txo := range txOutList { itxe := txlookupmap[*txo] for i, spent := range itxe.TxSpent { if spent == true { t.Errorf("height: %v freshly inserted tx %v already spent %v", height, txo, i) } } } if len(txInList) == 0 { continue } dropblock := blocks[height-1] err = db.DropAfterBlockBySha(dropblock.Sha()) if err != nil { t.Errorf("failed to drop block %v err %v", height, err) break endtest } txlookupmap = map[chainhash.Hash]*database.TxListReply{} txlist = db.FetchUnSpentTxByShaList(txlookupList) for _, txe := range txlist { if txe.Err != nil { if _, ok := txneededmap[*txe.Sha]; ok { t.Errorf("tx list fetch failed %v err %v ", txe.Sha, txe.Err) break endtest } } txlookupmap[*txe.Sha] = txe } for _, spend := range txInList { itxe := txlookupmap[spend.Hash] if itxe.TxSpent[spend.Index] == true { t.Errorf("txin %v:%v is unspent %v", spend.Hash, spend.Index, itxe.TxSpent) } } newheight, err = db.InsertBlock(block) if err != nil { t.Errorf("failed to insert block %v err %v", height, err) break endtest } txlookupmap = map[chainhash.Hash]*database.TxListReply{} txlist = db.FetchTxByShaList(txlookupList) for _, txe := range txlist { if txe.Err != nil { t.Errorf("tx list fetch failed %v err %v ", txe.Sha, txe.Err) break endtest } txlookupmap[*txe.Sha] = txe } for _, spend := range txInList { itxe := txlookupmap[spend.Hash] if itxe.TxSpent[spend.Index] == false { t.Errorf("txin %v:%v is unspent %v", spend.Hash, spend.Index, itxe.TxSpent) } } } }
// insert every block in the test chain // after each insert, fetch all the tx affected by the latest // block and verify that the the tx is spent/unspent // new tx should be fully unspent, referenced tx should have // the associated txout set to spent. // checks tx tree regular only func testUnspentInsertRegTree(t *testing.T) { // Ignore db remove errors since it means we didn't have an old one. dbname := fmt.Sprintf("tstdbuspnt1") dbnamever := dbname + ".ver" _ = os.RemoveAll(dbname) _ = os.RemoveAll(dbnamever) db, err := database.CreateDB("leveldb", dbname) if err != nil { t.Errorf("Failed to open test database %v", err) return } defer os.RemoveAll(dbname) defer os.RemoveAll(dbnamever) defer func() { if err := db.Close(); err != nil { t.Errorf("Close: unexpected error: %v", err) } }() blocks := loadblocks(t) endtest: for height := int64(0); height < int64(len(blocks))-1; height++ { block := blocks[height] // jam in genesis block if height == 0 { _, err := db.InsertBlock(block) if err != nil { t.Errorf("failed to insert block %v err %v", height, err) break endtest } continue } var txneededList []*chainhash.Hash var txlookupList []*chainhash.Hash var txOutList []*chainhash.Hash var txInList []*wire.OutPoint parent := blocks[height-1] unspentStakeOps := unspendStakeTxTree(block) // Check regular tree of parent and make sure it's ok for txIdx, tx := range parent.MsgBlock().Transactions { for txinIdx, txin := range tx.TxIn { if txin.PreviousOutPoint.Index == uint32(4294967295) { continue } origintxsha := &txin.PreviousOutPoint.Hash exists, err := db.ExistsTxSha(origintxsha) if err != nil { t.Errorf("ExistsTxSha: unexpected error %v ", err) } if !exists { // Check and see if something in flight spends it from this // tx tree. We can skip looking for this transaction OP // if that's the case. spentFromParentReg := false alreadySpentOps := getRegTreeOpsSpentBeforeThisOp(parent, txIdx, txinIdx) _, alreadySpent := alreadySpentOps[txin.PreviousOutPoint] if !alreadySpent { spentFromParentReg = true } if !spentFromParentReg { t.Errorf("referenced tx not found %v %v", origintxsha, height) } else { continue } } txInList = append(txInList, &txin.PreviousOutPoint) txneededList = append(txneededList, origintxsha) txlookupList = append(txlookupList, origintxsha) } txshaname := tx.TxSha() txlookupList = append(txlookupList, &txshaname) txOutList = append(txOutList, &txshaname) } txneededmap := map[chainhash.Hash]*database.TxListReply{} txlist := db.FetchUnSpentTxByShaList(txneededList) for _, txe := range txlist { if txe.Err != nil { t.Errorf("tx list fetch failed %v err %v", txe.Sha, txe.Err) break endtest } txneededmap[*txe.Sha] = txe } for _, spend := range txInList { itxe := txneededmap[spend.Hash] if itxe.TxSpent[spend.Index] == true { t.Errorf("txin %v:%v is already spent", spend.Hash, spend.Index) } } newheight, err := db.InsertBlock(block) if err != nil { t.Errorf("failed to insert block %v err %v", height, err) break endtest } if newheight != height { t.Errorf("height mismatch expect %v returned %v", height, newheight) break endtest } // only check transactions if current block validates parent block if !dcrutil.IsFlagSet16(block.MsgBlock().Header.VoteBits, dcrutil.BlockValid) { continue } txlookupmap := map[chainhash.Hash]*database.TxListReply{} txlist = db.FetchTxByShaList(txlookupList) for _, txe := range txlist { if txe.Err != nil { t.Errorf("tx list fetch failed %v err %v (height %v)", txe.Sha, txe.Err, height) break endtest } txlookupmap[*txe.Sha] = txe } for _, spend := range txInList { itxe := txlookupmap[spend.Hash] if itxe.TxSpent[spend.Index] == false { t.Errorf("txin %v:%v is unspent %v", spend.Hash, spend.Index, itxe.TxSpent) } } alreadySpentOps := unspendInflightTxTree(parent) for _, txo := range txOutList { itxe := txlookupmap[*txo] for i, spent := range itxe.TxSpent { if spent == true { // If this was spent in flight, skip thisOP := wire.OutPoint{*txo, uint32(i), dcrutil.TxTreeRegular} _, alreadySpent := alreadySpentOps[thisOP] if alreadySpent { continue } // If it was spent in the stake tree it's actually unspent too _, wasSpentInStakeTree := unspentStakeOps[thisOP] if wasSpentInStakeTree { continue } t.Errorf("height: %v freshly inserted tx %v already spent %v", height, txo, i) } } } if len(txInList) == 0 { continue } dropblock := blocks[height-1] err = db.DropAfterBlockBySha(dropblock.Sha()) if err != nil { t.Errorf("failed to drop block %v err %v", height, err) break endtest } txlookupmap = map[chainhash.Hash]*database.TxListReply{} txlist = db.FetchUnSpentTxByShaList(txlookupList) for _, txe := range txlist { if txe.Err != nil { if _, ok := txneededmap[*txe.Sha]; ok { t.Errorf("tx list fetch failed %v err %v ", txe.Sha, txe.Err) break endtest } } txlookupmap[*txe.Sha] = txe } for _, spend := range txInList { itxe := txlookupmap[spend.Hash] if itxe.TxSpent[spend.Index] == true { t.Errorf("txin %v:%v is unspent %v", spend.Hash, spend.Index, itxe.TxSpent) } } newheight, err = db.InsertBlock(block) if err != nil { t.Errorf("failed to insert block %v err %v", height, err) break endtest } txlookupmap = map[chainhash.Hash]*database.TxListReply{} txlist = db.FetchTxByShaList(txlookupList) for _, txe := range txlist { if txe.Err != nil { t.Errorf("tx list fetch failed %v err %v ", txe.Sha, txe.Err) break endtest } txlookupmap[*txe.Sha] = txe } for _, spend := range txInList { itxe := txlookupmap[spend.Hash] if itxe.TxSpent[spend.Index] == false { t.Errorf("txin %v:%v is unspent %v", spend.Hash, spend.Index, itxe.TxSpent) } } } }