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("testdata", "blocks1-256.bz2") blocks, err := loadBlocks(t, testdatafile) if err != nil { t.Errorf("Unable to load blocks from test data for: %v", err) return } var lastSha *btcwire.ShaHash // 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] // except for NoVerify which does not allow lookups check inputs mblock := block.MsgBlock() var txneededList []*btcwire.ShaHash for _, tx := range mblock.Transactions { for _, txin := range tx.TxIn { if txin.PreviousOutPoint.Index == uint32(4294967295) { continue } 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 btcwire.BlockHeader bh.Version = 2 bh.PrevBlock = *lastSha // Bits, Nonce are not filled in mblk := btcwire.NewMsgBlock(&bh) hash, _ := btcwire.NewShaHashFromStr("df2b060fa2e5e9c8ed5eaf6a45c13753ec8c63282b2688322eba40cd98ea067a") po := btcwire.NewOutPoint(hash, 0) txI := btcwire.NewTxIn(po, []byte("garbage")) txO := btcwire.NewTxOut(50000000, []byte("garbageout")) var tx btcwire.MsgTx tx.AddTxIn(txI) tx.AddTxOut(txO) mblk.AddTransaction(&tx) blk := btcutil.NewBlock(mblk) fetchList := []*btcwire.ShaHash{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 != 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 { fmt.Errorf("stx %v spent %v err %v\n", lr.Sha, lr.TxSpent, lr.Err) } } } } t.Logf("Dropping block") err = db.DropAfterBlockBySha(lastSha) if err != nil { t.Errorf("failed to drop spending block %v", err) } }
// newShaHashFromStr converts the passed big-endian hex string into a // btcwire.ShaHash. It only differs from the one available in btcwire in that // it ignores the error since it will only (and must only) be called with // hard-coded, and therefore known good, hashes. func newShaHashFromStr(hexStr string) *btcwire.ShaHash { sha, _ := btcwire.NewShaHashFromStr(hexStr) return sha }
// TestHaveBlock tests the HaveBlock API to ensure proper functionality. func TestHaveBlock(t *testing.T) { // Load up blocks such that there is a side chain. // (genesis block) -> 1 -> 2 -> 3 -> 4 // \-> 3a testFiles := []string{ "blk_0_to_4.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) return } for _, block := range blockTmp { blocks = append(blocks, block) } } // Create a new database and chain instance to run tests against. chain, teardownFunc, err := chainSetup("haveblock") 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) blockchain.TstSetCoinbaseMaturity(1) timeSource := blockchain.NewMedianTime() for i := 1; i < len(blocks); i++ { isOrphan, err := chain.ProcessBlock(blocks[i], timeSource, blockchain.BFNone) if err != nil { t.Errorf("ProcessBlock fail on block %v: %v\n", i, err) return } if isOrphan { t.Errorf("ProcessBlock incorrectly returned block %v "+ "is an orphan\n", i) return } } // Insert an orphan block. isOrphan, err := chain.ProcessBlock(btcutil.NewBlock(&Block100000), timeSource, blockchain.BFNone) if err != nil { t.Errorf("Unable to process block: %v", err) return } if !isOrphan { t.Errorf("ProcessBlock indicated block is an not orphan when " + "it should be\n") return } tests := []struct { hash string want bool }{ // Genesis block should be present (in the main chain). {hash: btcnet.MainNetParams.GenesisHash.String(), want: true}, // Block 3a should be present (on a side chain). {hash: "00000000474284d20067a4d33f6a02284e6ef70764a3a26d6a5b9df52ef663dd", want: true}, // Block 100000 should be present (as an orphan). {hash: "000000000003ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506", want: true}, // Random hashes should not be availble. {hash: "123", want: false}, } for i, test := range tests { hash, err := btcwire.NewShaHashFromStr(test.hash) if err != nil { t.Errorf("NewShaHashFromStr: %v", err) continue } result, err := chain.HaveBlock(hash) if err != nil { t.Errorf("HaveBlock #%d unexpected error: %v", i, err) return } if result != test.want { t.Errorf("HaveBlock #%d got %v want %v", i, result, test.want) continue } } }
// InsertBlock inserts raw block and transaction data from a block into the // database. The first block inserted into the database will be treated as the // genesis block. Every subsequent block insert requires the referenced parent // block to already exist. func (db *LevelDb) InsertBlock(block *btcutil.Block) (height int64, rerr error) { db.dbLock.Lock() defer db.dbLock.Unlock() defer func() { if rerr == nil { rerr = db.processBatches() } else { db.lBatch().Reset() } }() blocksha, err := block.Sha() if err != nil { log.Warnf("Failed to compute block sha %v", blocksha) return 0, err } mblock := block.MsgBlock() rawMsg, err := block.Bytes() if err != nil { log.Warnf("Failed to obtain raw block sha %v", blocksha) return 0, err } txloc, err := block.TxLoc() if err != nil { log.Warnf("Failed to obtain raw block sha %v", blocksha) return 0, err } // Insert block into database newheight, err := db.insertBlockData(blocksha, &mblock.Header.PrevBlock, rawMsg) if err != nil { log.Warnf("Failed to insert block %v %v %v", blocksha, &mblock.Header.PrevBlock, err) return 0, err } // At least two blocks in the long past were generated by faulty // miners, the sha of the transaction exists in a previous block, // detect this condition and 'accept' the block. for txidx, tx := range mblock.Transactions { txsha, err := block.TxSha(txidx) if err != nil { log.Warnf("failed to compute tx name block %v idx %v err %v", blocksha, txidx, err) return 0, err } spentbuflen := (len(tx.TxOut) + 7) / 8 spentbuf := make([]byte, spentbuflen, spentbuflen) if len(tx.TxOut)%8 != 0 { for i := uint(len(tx.TxOut) % 8); i < 8; i++ { spentbuf[spentbuflen-1] |= (byte(1) << i) } } err = db.insertTx(txsha, newheight, txloc[txidx].TxStart, txloc[txidx].TxLen, spentbuf) if err != nil { log.Warnf("block %v idx %v failed to insert tx %v %v err %v", blocksha, newheight, &txsha, txidx, err) return 0, err } // Some old blocks contain duplicate transactions // Attempt to cleanly bypass this problem by marking the // first as fully spent. // http://blockexplorer.com/b/91812 dup in 91842 // http://blockexplorer.com/b/91722 dup in 91880 if newheight == 91812 { dupsha, err := btcwire.NewShaHashFromStr("d5d27987d2a3dfc724e359870c6644b40e497bdc0589a033220fe15429d88599") if err != nil { panic("invalid sha string in source") } if txsha.IsEqual(dupsha) { // marking TxOut[0] as spent po := btcwire.NewOutPoint(dupsha, 0) txI := btcwire.NewTxIn(po, []byte("garbage")) var spendtx btcwire.MsgTx spendtx.AddTxIn(txI) err = db.doSpend(&spendtx) if err != nil { log.Warnf("block %v idx %v failed to spend tx %v %v err %v", blocksha, newheight, &txsha, txidx, err) } } } if newheight == 91722 { dupsha, err := btcwire.NewShaHashFromStr("e3bf3d07d4b0375638d5f1db5255fe07ba2c4cb067cd81b84ee974b6585fb468") if err != nil { panic("invalid sha string in source") } if txsha.IsEqual(dupsha) { // marking TxOut[0] as spent po := btcwire.NewOutPoint(dupsha, 0) txI := btcwire.NewTxIn(po, []byte("garbage")) var spendtx btcwire.MsgTx spendtx.AddTxIn(txI) err = db.doSpend(&spendtx) if err != nil { log.Warnf("block %v idx %v failed to spend tx %v %v err %v", blocksha, newheight, &txsha, txidx, err) } } } err = db.doSpend(tx) if err != nil { log.Warnf("block %v idx %v failed to spend tx %v %v err %v", blocksha, newheight, txsha, txidx, err) return 0, err } } return newheight, nil }