// submitBlock submits the passed block to network after ensuring it passes all // of the consensus validation rules. func (m *CPUMiner) submitBlock(block *dcrutil.Block) bool { m.submitBlockLock.Lock() defer m.submitBlockLock.Unlock() _, latestHeight := m.server.blockManager.chainState.Best() // Be sure to set this so ProcessBlock doesn't fail! - Decred block.SetHeight(latestHeight + 1) // Process this block using the same rules as blocks coming from other // nodes. This will in turn relay it to the network like normal. isOrphan, err := m.server.blockManager.ProcessBlock(block, blockchain.BFNone) if err != nil { // Anything other than a rule violation is an unexpected error, // so log that error as an internal error. if rErr, ok := err.(blockchain.RuleError); !ok { minrLog.Errorf("Unexpected error while processing "+ "block submitted via CPU miner: %v", err) return false } else { // Occasionally errors are given out for timing errors with // ResetMinDifficulty and high block works that is above // the target. Feed these to debug. if m.server.chainParams.ResetMinDifficulty && rErr.ErrorCode == blockchain.ErrHighHash { minrLog.Debugf("Block submitted via CPU miner rejected "+ "because of ResetMinDifficulty time sync failure: %v", err) return false } else { // Other rule errors should be reported. minrLog.Errorf("Block submitted via CPU miner rejected: %v", err) return false } } } if isOrphan { minrLog.Errorf("Block submitted via CPU miner is an orphan building "+ "on parent %v", block.MsgBlock().Header.PrevBlock) return false } // The block was accepted. coinbaseTxOuts := block.MsgBlock().Transactions[0].TxOut coinbaseTxGenerated := int64(0) for _, out := range coinbaseTxOuts { coinbaseTxGenerated += out.Value } minrLog.Infof("Block submitted via CPU miner accepted (hash %s, "+ "height %v, amount %v)", block.Sha(), block.Height(), dcrutil.Amount(coinbaseTxGenerated)) return true }
// ticketsRevokedInBlock fetches a list of tickets that were revoked in the // block. func ticketsRevokedInBlock(bl *dcrutil.Block) []chainhash.Hash { var tickets []chainhash.Hash for _, stx := range bl.MsgBlock().STransactions { if stake.DetermineTxType(stx) == stake.TxTypeSSRtx { tickets = append(tickets, stx.TxIn[0].PreviousOutPoint.Hash) } } return tickets }
// ticketsSpentInBlock finds all the tickets spent in the block. func ticketsSpentInBlock(bl *dcrutil.Block) []chainhash.Hash { tickets := make([]chainhash.Hash, 0) for _, stx := range bl.STransactions() { if DetermineTxType(stx.MsgTx()) == TxTypeSSGen { tickets = append(tickets, stx.MsgTx().TxIn[1].PreviousOutPoint.Hash) } } return tickets }
// voteVersionsInBlock returns all versions in a block. func voteVersionsInBlock(bl *dcrutil.Block, params *chaincfg.Params) []uint32 { versions := make([]uint32, 0, params.TicketsPerBlock) for _, stx := range bl.MsgBlock().STransactions { if is, _ := stake.IsSSGen(stx); !is { continue } versions = append(versions, stake.SSGenVersion(stx)) } return versions }
// ticketsInBlock finds all the new tickets in the block. func ticketsInBlock(bl *dcrutil.Block) []chainhash.Hash { tickets := make([]chainhash.Hash, 0) for _, stx := range bl.STransactions() { if DetermineTxType(stx.MsgTx()) == TxTypeSStx { h := stx.Sha() tickets = append(tickets, *h) } } return tickets }
// unspendStakeTxTree returns all outpoints spent before this one // in the block's tx tree stake. used for unspending the stake tx // tree to evaluate tx tree regular of prev block. func unspendStakeTxTree(block *dcrutil.Block) map[wire.OutPoint]struct{} { unspentOps := make(map[wire.OutPoint]struct{}) for _, tx := range block.STransactions() { for _, txIn := range tx.MsgTx().TxIn { unspentOps[txIn.PreviousOutPoint] = struct{}{} } } return unspentOps }
// votesInBlock finds all the votes in the block. func votesInBlock(bl *dcrutil.Block) []chainhash.Hash { votes := make([]chainhash.Hash, 0) for _, stx := range bl.STransactions() { if DetermineTxType(stx.MsgTx()) == TxTypeSSGen { h := stx.Sha() votes = append(votes, *h) } } return votes }
// dbIndexDisconnectBlock removes all of the index entries associated with the // given block using the provided indexer and updates the tip of the indexer // accordingly. An error will be returned if the current tip for the indexer is // not the passed block. func dbIndexDisconnectBlock(dbTx database.Tx, indexer Indexer, block, parent *dcrutil.Block, view *blockchain.UtxoViewpoint) error { // Assert that the block being disconnected is the current tip of the // index. idxKey := indexer.Key() curTipHash, _, err := dbFetchIndexerTip(dbTx, idxKey) if err != nil { return err } if !curTipHash.IsEqual(block.Sha()) { return AssertError(fmt.Sprintf("dbIndexDisconnectBlock must "+ "be called with the block at the current index tip "+ "(%s, tip %s, block %s)", indexer.Name(), curTipHash, block.Sha())) } // Notify the indexer with the disconnected block so it can remove all // of the appropriate entries. if err := indexer.DisconnectBlock(dbTx, block, parent, view); err != nil { return err } // Update the current index tip. prevHash := &block.MsgBlock().Header.PrevBlock return dbPutIndexerTip(dbTx, idxKey, prevHash, uint32(block.Height())-1) }
// checkBlockContext peforms several validation checks on the block which depend // on its position within the block chain. // // The flags modify the behavior of this function as follows: // - BFFastAdd: The transaction are not checked to see if they are finalized // and the somewhat expensive duplication transaction check is not performed. // // The flags are also passed to checkBlockHeaderContext. See its documentation // for how the flags modify its behavior. func (b *BlockChain) checkBlockContext(block *dcrutil.Block, prevNode *blockNode, flags BehaviorFlags) error { // The genesis block is valid by definition. if prevNode == nil { return nil } // Perform all block header related validation checks. header := &block.MsgBlock().Header err := b.checkBlockHeaderContext(header, prevNode, flags) if err != nil { return err } fastAdd := flags&BFFastAdd == BFFastAdd if !fastAdd { // The height of this block is one more than the referenced // previous block. blockHeight := prevNode.height + 1 // Ensure all transactions in the block are finalized. for _, tx := range block.Transactions() { if !IsFinalizedTransaction(tx, blockHeight, header.Timestamp) { str := fmt.Sprintf("block contains unfinalized regular "+ "transaction %v", tx.Sha()) return ruleError(ErrUnfinalizedTx, str) } } for _, stx := range block.STransactions() { if !IsFinalizedTransaction(stx, blockHeight, header.Timestamp) { str := fmt.Sprintf("block contains unfinalized stake "+ "transaction %v", stx.Sha()) return ruleError(ErrUnfinalizedTx, str) } } // Check that the node is at the correct height in the blockchain, // as specified in the block header. if blockHeight != int64(block.MsgBlock().Header.Height) { errStr := fmt.Sprintf("Block header height invalid; expected %v"+ " but %v was found", blockHeight, header.Height) return ruleError(ErrBadBlockHeight, errStr) } // Check that the coinbase contains at minimum the block // height in output 1. if blockHeight > 1 { err := checkCoinbaseUniqueHeight(blockHeight, block) if err != nil { return err } } } return nil }
// SubmitBlockAsync returns an instance of a type that can be used to get the // result of the RPC at some future time by invoking the Receive function on the // returned instance. // // See SubmitBlock for the blocking version and more details. func (c *Client) SubmitBlockAsync(block *dcrutil.Block, options *dcrjson.SubmitBlockOptions) FutureSubmitBlockResult { blockHex := "" if block != nil { blockBytes, err := block.Bytes() if err != nil { return newFutureError(err) } blockHex = hex.EncodeToString(blockBytes) } cmd := dcrjson.NewSubmitBlockCmd(blockHex, options) return c.sendCmd(cmd) }
// InsertBlock synchronously queues a newly solved block to have its // transactions indexed by address. func (a *addrIndexer) InsertBlock(block *dcrutil.Block, parent *dcrutil.Block) error { addrIndex, err := a.indexBlockAddrs(block, parent) if err != nil { return fmt.Errorf("Unable to index transactions of"+ " block: %v", err) } err = a.server.db.UpdateAddrIndexForBlock(block.Sha(), block.Height(), addrIndex) if err != nil { return fmt.Errorf("Unable to insert block: %v", err.Error()) } return nil }
// DisconnectBlock is invoked by the index manager when a block has been // disconnected from the main chain. This indexer removes the // hash-to-transaction mapping for every transaction in the block. // // This is part of the Indexer interface. func (idx *TxIndex) DisconnectBlock(dbTx database.Tx, block, parent *dcrutil.Block, view *blockchain.UtxoViewpoint) error { // Remove all of the transactions in the block from the index. if err := dbRemoveTxIndexEntries(dbTx, block, parent); err != nil { return err } // Remove the block ID index entry for the block being disconnected and // decrement the current internal block ID to account for it. blockSha := block.Sha() if err := dbRemoveBlockIDIndexEntry(dbTx, *blockSha); err != nil { return err } idx.curBlockID-- return nil }
// unspendInflightTxTree returns all outpoints spent that reference internal // transactions in a TxTreeRegular. func unspendInflightTxTree(block *dcrutil.Block) map[wire.OutPoint]struct{} { unspentOps := make(map[wire.OutPoint]struct{}) allTxHashes := make(map[chainhash.Hash]struct{}) for _, tx := range block.Transactions() { h := tx.Sha() allTxHashes[*h] = struct{}{} } for _, tx := range block.Transactions() { for _, txIn := range tx.MsgTx().TxIn { if _, isLocal := allTxHashes[txIn.PreviousOutPoint.Hash]; isLocal { unspentOps[txIn.PreviousOutPoint] = struct{}{} } } } return unspentOps }
// ConnectBlock is invoked by the index manager when a new block has been // connected to the main chain. This indexer adds a hash-to-transaction mapping // for every transaction in the passed block. // // This is part of the Indexer interface. func (idx *TxIndex) ConnectBlock(dbTx database.Tx, block, parent *dcrutil.Block, view *blockchain.UtxoViewpoint) error { // Increment the internal block ID to use for the block being connected // and add all of the transactions in the block to the index. newBlockID := idx.curBlockID + 1 if err := dbAddTxIndexEntries(dbTx, block, parent, newBlockID); err != nil { return err } // Add the new block ID index entry for the block being connected and // update the current internal block ID accordingly. blockSha := block.Sha() err := dbPutBlockIDIndexEntry(dbTx, *blockSha, newBlockID) if err != nil { return err } idx.curBlockID = newBlockID return nil }
// checkBlockScripts executes and validates the scripts for all transactions in // the passed block using multiple goroutines. // txTree = true is TxTreeRegular, txTree = false is TxTreeStake. func checkBlockScripts(block *dcrutil.Block, utxoView *UtxoViewpoint, txTree bool, scriptFlags txscript.ScriptFlags, sigCache *txscript.SigCache) error { // Collect all of the transaction inputs and required information for // validation for all transactions in the block into a single slice. numInputs := 0 var txs []*dcrutil.Tx // TxTreeRegular handling. if txTree { txs = block.Transactions() } else { // TxTreeStake txs = block.STransactions() } for _, tx := range txs { numInputs += len(tx.MsgTx().TxIn) } txValItems := make([]*txValidateItem, 0, numInputs) for _, tx := range txs { for txInIdx, txIn := range tx.MsgTx().TxIn { // Skip coinbases. if txIn.PreviousOutPoint.Index == math.MaxUint32 { continue } txVI := &txValidateItem{ txInIndex: txInIdx, txIn: txIn, tx: tx, } txValItems = append(txValItems, txVI) } } // Validate all of the inputs. validator := newTxValidator(utxoView, scriptFlags, sigCache) if err := validator.Validate(txValItems); err != nil { return err } return nil }
// getRegTreeOpsSpentBeforeThisOp returns all outpoints spent before this one // in the block's tx tree regular. used for checking vs in flight tx. func getRegTreeOpsSpentBeforeThisOp(block *dcrutil.Block, idx int, txinIdx int) map[wire.OutPoint]struct{} { spentOps := make(map[wire.OutPoint]struct{}) thisTx := block.Transactions()[idx] for i, txIn := range thisTx.MsgTx().TxIn { if i < txinIdx { spentOps[txIn.PreviousOutPoint] = struct{}{} } } for i, tx := range block.Transactions() { if i < idx { for _, txIn := range tx.MsgTx().TxIn { spentOps[txIn.PreviousOutPoint] = struct{}{} } } } return spentOps }
// testExistsTxSha ensures ExistsTxSha conforms to the interface contract. func testExistsTxSha(tc *testContext) bool { var blockPrev *dcrutil.Block = nil // Decred: WARNING. This function assumes that all block insertion calls have // dcrutil.blocks passed to them with block.blockHeight set correctly. However, // loading the genesis block in dcrd didn't do this (via block manager); pre- // production it should be established that all calls to this function pass // blocks with block.blockHeight set correctly. if tc.block.Height() != 0 { var errBlockPrev error blockPrev, errBlockPrev = tc.db.FetchBlockBySha(&tc.block.MsgBlock().Header.PrevBlock) if errBlockPrev != nil { blockSha := tc.block.Sha() tc.t.Errorf("Failed to fetch parent block of block %v", blockSha) } } votebits := tc.block.MsgBlock().Header.VoteBits if dcrutil.IsFlagSet16(votebits, dcrutil.BlockValid) && blockPrev != nil { for i, tx := range blockPrev.Transactions() { // The transaction must exist in the database. txHash := tx.Sha() exists, err := tc.db.ExistsTxSha(txHash) if err != nil { tc.t.Errorf("ExistsTxSha (%s): block #%d (%s) tx #%d "+ "(%s) unexpected error: %v", tc.dbType, tc.blockHeight, tc.blockHash, i, txHash, err) return false } if !exists { _, err := tc.db.FetchTxBySha(txHash) if err != nil { tc.t.Errorf("ExistsTxSha (%s): block #%d (%s) "+ "tx #%d (%s) does not exist", tc.dbType, tc.blockHeight, tc.blockHash, i, txHash) } return false } } } return true }
// LogBlockHeight logs a new block height as an information message to show // progress to the user. In order to prevent spam, it limits logging to one // message every 10 seconds with duration and totals included. func (b *blockProgressLogger) LogBlockHeight(block *dcrutil.Block) { b.Lock() defer b.Unlock() b.receivedLogBlocks++ b.receivedLogTx += int64(len(block.MsgBlock().Transactions)) now := time.Now() duration := now.Sub(b.lastBlockLogTime) if duration < time.Second*10 { return } // Truncate the duration to 10s of milliseconds. durationMillis := int64(duration / time.Millisecond) tDuration := 10 * time.Millisecond * time.Duration(durationMillis/10) // Log information about new block height. blockStr := "blocks" if b.receivedLogBlocks == 1 { blockStr = "block" } txStr := "transactions" if b.receivedLogTx == 1 { txStr = "transaction" } b.subsystemLogger.Infof("%s %d %s in the last %s (%d %s, height %d, %s)", b.progressAction, b.receivedLogBlocks, blockStr, tDuration, b.receivedLogTx, txStr, block.Height(), block.MsgBlock().Header.Timestamp) b.receivedLogBlocks = 0 b.receivedLogTx = 0 b.lastBlockLogTime = now }
// loadReorgBlocks reads files containing decred block data (bzipped but // otherwise in the format bitcoind writes) from disk and returns them as an // array of dcrutil.Block. This is copied from the blockchain package, which // itself largely borrowed it from the test code in this package. func loadReorgBlocks(filename string) ([]*dcrutil.Block, error) { filename = filepath.Join("../blockchain/testdata/", filename) fi, err := os.Open(filename) if err != nil { return nil, err } 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 { return nil, err } var block *dcrutil.Block blocks := make([]*dcrutil.Block, 0, len(blockchain)) for height := int64(0); height < int64(len(blockchain)); height++ { block, err = dcrutil.NewBlockFromBytes(blockchain[height]) if err != nil { return blocks, err } block.SetHeight(height) blocks = append(blocks, block) } return blocks, nil }
// dbIndexConnectBlock adds all of the index entries associated with the // given block using the provided indexer and updates the tip of the indexer // accordingly. An error will be returned if the current tip for the indexer is // not the previous block for the passed block. func dbIndexConnectBlock(dbTx database.Tx, indexer Indexer, block, parent *dcrutil.Block, view *blockchain.UtxoViewpoint) error { // Assert that the block being connected properly connects to the // current tip of the index. idxKey := indexer.Key() curTipHash, _, err := dbFetchIndexerTip(dbTx, idxKey) if err != nil { return err } if !curTipHash.IsEqual(&block.MsgBlock().Header.PrevBlock) { return AssertError(fmt.Sprintf("dbIndexConnectBlock must be "+ "called with a block that extends the current index "+ "tip (%s, tip %s, block %s)", indexer.Name(), curTipHash, block.Sha())) } // Notify the indexer with the connected block so it can index it. if err := indexer.ConnectBlock(dbTx, block, parent, view); err != nil { return err } // Update the current index tip. return dbPutIndexerTip(dbTx, idxKey, block.Sha(), uint32(block.Height())) }
// CalculateAddedSubsidy calculates the amount of subsidy added by a block // and its parent. The blocks passed to this function MUST be valid blocks // that have already been confirmed to abide by the consensus rules of the // network, or the function might panic. func CalculateAddedSubsidy(block, parent *dcrutil.Block) int64 { var subsidy int64 regularTxTreeValid := dcrutil.IsFlagSet16(block.MsgBlock().Header.VoteBits, dcrutil.BlockValid) if regularTxTreeValid { subsidy += parent.MsgBlock().Transactions[0].TxIn[0].ValueIn } for _, stx := range block.MsgBlock().STransactions { if isSSGen, _ := stake.IsSSGen(stx); isSSGen { subsidy += stx.TxIn[0].ValueIn } } return subsidy }
// NewMerkleBlock returns a new *wire.MsgMerkleBlock and an array of the matched // transaction hashes based on the passed block and filter. func NewMerkleBlock(block *dcrutil.Block, filter *Filter) (*wire.MsgMerkleBlock, []*chainhash.Hash) { numTx := uint32(len(block.Transactions())) mBlock := merkleBlock{ numTx: numTx, allHashes: make([]*chainhash.Hash, 0, numTx), matchedBits: make([]byte, 0, numTx), } // Find and keep track of any transactions that match the filter. var matchedHashes []*chainhash.Hash for _, tx := range block.Transactions() { if filter.MatchTxAndUpdate(tx) { mBlock.matchedBits = append(mBlock.matchedBits, 0x01) matchedHashes = append(matchedHashes, tx.Sha()) } else { mBlock.matchedBits = append(mBlock.matchedBits, 0x00) } mBlock.allHashes = append(mBlock.allHashes, tx.Sha()) } // Calculate the number of merkle branches (height) in the tree. height := uint32(0) for mBlock.calcTreeWidth(height) > 1 { height++ } // Build the depth-first partial merkle tree. mBlock.traverseAndBuild(height, 0) // Create and return the merkle block. msgMerkleBlock := wire.MsgMerkleBlock{ Header: block.MsgBlock().Header, Transactions: uint32(mBlock.numTx), Hashes: make([]*chainhash.Hash, 0, len(mBlock.finalHashes)), Flags: make([]byte, (len(mBlock.bits)+7)/8), } for _, sha := range mBlock.finalHashes { msgMerkleBlock.AddTxHash(sha) } for i := uint32(0); i < uint32(len(mBlock.bits)); i++ { msgMerkleBlock.Flags[i/8] |= mBlock.bits[i] << (i % 8) } return &msgMerkleBlock, matchedHashes }
// connectTxTree lets you connect an arbitrary TxTree to a txStore to push it // forward in history. // TxTree true == TxTreeRegular // TxTree false == TxTreeStake func connectTxTree(txStore TxStore, block *dcrutil.Block, txTree bool) { var transactions []*dcrutil.Tx if txTree { transactions = block.Transactions() } else { transactions = block.STransactions() } // Loop through all of the transactions in the block to see if any of // them are ones we need to update and spend based on the results map. for i, tx := range transactions { // Update the transaction store with the transaction information // if it's one of the requested transactions. msgTx := tx.MsgTx() if txD, exists := txStore[*tx.Sha()]; exists { txD.Tx = tx txD.BlockHeight = block.Height() txD.BlockIndex = uint32(i) txD.Spent = make([]bool, len(msgTx.TxOut)) txD.Err = nil } // Spend the origin transaction output. for _, txIn := range msgTx.TxIn { originHash := &txIn.PreviousOutPoint.Hash originIndex := txIn.PreviousOutPoint.Index if originTx, exists := txStore[*originHash]; exists { if originTx.Spent == nil { continue } if originIndex >= uint32(len(originTx.Spent)) { continue } originTx.Spent[originIndex] = true } } } return }
// dbRemoveTxIndexEntries uses an existing database transaction to remove the // latest transaction entry for every transaction in the passed block. func dbRemoveTxIndexEntries(dbTx database.Tx, block, parent *dcrutil.Block) error { regularTxTreeValid := dcrutil.IsFlagSet16(block.MsgBlock().Header.VoteBits, dcrutil.BlockValid) if regularTxTreeValid { for _, tx := range parent.Transactions() { txSha := tx.Sha() err := dbRemoveTxIndexEntry(dbTx, *txSha) if err != nil { return err } } } for _, tx := range block.STransactions() { txSha := tx.Sha() err := dbRemoveTxIndexEntry(dbTx, *txSha) if err != nil { return err } } return nil }
// 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. This is part of the database.Db interface // implementation. func (db *MemDb) InsertBlock(block *dcrutil.Block) (int64, error) { db.Lock() defer db.Unlock() if db.closed { return 0, ErrDbClosed } // Reject the insert if the previously reference block does not exist // except in the case there are no blocks inserted yet where the first // inserted block is assumed to be a genesis block. msgBlock := block.MsgBlock() if _, exists := db.blocksBySha[msgBlock.Header.PrevBlock]; !exists { if len(db.blocks) > 0 { return 0, database.ErrPrevShaMissing } } var blockPrev *dcrutil.Block = nil // Decred: WARNING. This function assumes that all block insertion calls have // dcrutil.blocks passed to them with block.blockHeight set correctly. However, // loading the genesis block in dcrd didn't do this (via block manager); pre- // production it should be established that all calls to this function pass // blocks with block.blockHeight set correctly. if len(db.blocks) > 0 { var errBlockPrev error blockPrev, errBlockPrev = db.fetchBlockBySha(&msgBlock.Header.PrevBlock) if errBlockPrev != nil { blockSha := block.Sha() log.Warnf("Failed to fetch parent block of block %v", blockSha) return 0, errBlockPrev } } // Build a map of in-flight transactions because some of the inputs in // this block could be referencing other transactions earlier in this // block which are not yet in the chain. newHeight := int64(len(db.blocks)) txInFlight := map[chainhash.Hash]int{} // Loop through all transactions and inputs to ensure there are no error // conditions that would prevent them from be inserted into the db. // Although these checks could could be done in the loop below, checking // for error conditions up front means the code below doesn't have to // deal with rollback on errors. votebits := block.MsgBlock().Header.VoteBits if dcrutil.IsFlagSet16(votebits, dcrutil.BlockValid) && blockPrev != nil { transactions := blockPrev.Transactions() for i, tx := range transactions { txInFlight[*tx.Sha()] = i } for i, tx := range transactions { for _, txIn := range tx.MsgTx().TxIn { if isCoinbaseInput(txIn) { continue } // It is acceptable for a transaction input to reference // the output of another transaction in this block only // if the referenced transaction comes before the // current one in this block. prevOut := &txIn.PreviousOutPoint if inFlightIndex, ok := txInFlight[prevOut.Hash]; ok { if i <= inFlightIndex { log.Warnf("InsertBlock: requested hash "+ " of %s does not exist in-flight", tx.Sha()) return 0, database.ErrTxShaMissing } } else { originTxns, exists := db.txns[prevOut.Hash] if !exists { log.Warnf("InsertBlock: requested hash "+ "of %s by %s does not exist", prevOut.Hash, tx.Sha()) return 0, database.ErrTxShaMissing } originTxD := originTxns[len(originTxns)-1] if prevOut.Index > uint32(len(originTxD.spentBuf)) { log.Warnf("InsertBlock: requested hash "+ "of %s with index %d does not "+ "exist", tx.Sha(), prevOut.Index) return 0, database.ErrTxShaMissing } } } // Prevent duplicate transactions in the same block. if inFlightIndex, exists := txInFlight[*tx.Sha()]; exists && inFlightIndex < i { log.Warnf("Block contains duplicate transaction %s", tx.Sha()) return 0, database.ErrDuplicateSha } // Prevent duplicate transactions unless the old one is fully // spent. if txns, exists := db.txns[*tx.Sha()]; exists { txD := txns[len(txns)-1] if !isFullySpent(txD) { log.Warnf("Attempt to insert duplicate "+ "transaction %s", tx.Sha()) return 0, database.ErrDuplicateSha } } } } db.blocks = append(db.blocks, msgBlock) db.blocksBySha[msgBlock.Header.BlockSha()] = newHeight if dcrutil.IsFlagSet16(votebits, dcrutil.BlockValid) && blockPrev != nil { // Insert information about eacj transaction and spend all of the // outputs referenced by the inputs to the transactions. for i, tx := range blockPrev.Transactions() { // Insert the transaction data. txD := tTxInsertData{ tree: dcrutil.TxTreeRegular, blockHeight: newHeight - 1, offset: i, spentBuf: make([]bool, len(tx.MsgTx().TxOut)), } db.txns[*tx.Sha()] = append(db.txns[*tx.Sha()], &txD) // Spend all of the inputs. for _, txIn := range tx.MsgTx().TxIn { // Coinbase transaction has no inputs. if isCoinbaseInput(txIn) { continue } // Already checked for existing and valid ranges above. prevOut := &txIn.PreviousOutPoint originTxns := db.txns[prevOut.Hash] originTxD := originTxns[len(originTxns)-1] originTxD.spentBuf[prevOut.Index] = true } } } for i, tx := range block.STransactions() { // Insert the transaction data. txD := tTxInsertData{ tree: dcrutil.TxTreeStake, blockHeight: newHeight, offset: i, spentBuf: make([]bool, len(tx.MsgTx().TxOut)), } db.txns[*tx.Sha()] = append(db.txns[*tx.Sha()], &txD) // Spend all of the inputs. for _, txIn := range tx.MsgTx().TxIn { // Coinbase transaction has no inputs. if isCoinbaseInput(txIn) { continue } // Already checked for existing and valid ranges above. prevOut := &txIn.PreviousOutPoint originTxns := db.txns[prevOut.Hash] originTxD := originTxns[len(originTxns)-1] originTxD.spentBuf[prevOut.Index] = true } } return newHeight, nil }
// spendTickets transfers tickets from the ticketMap to the spentTicketMap. Useful // when connecting blocks. Also pushes missed tickets to the missed ticket map. // usedtickets is a map that contains all tickets that were actually used in SSGen // votes; all other tickets are considered missed. // // This function MUST be called with the tmdb lock held (for writes). func (tmdb *TicketDB) spendTickets(parentBlock *dcrutil.Block, usedTickets map[chainhash.Hash]struct{}, spendingHashes map[chainhash.Hash]chainhash.Hash) (SStxMemMap, error) { // If there is nothing being spent, break. if len(spendingHashes) < 1 { return nil, nil } // Make sure there's a bucket in the map for used tickets height := parentBlock.Height() + 1 tmdb.maybeInsertBlock(height) tempTickets := make(SStxMemMap) // Sort the entire list of tickets lexicographically by sorting // each bucket and then appending it to the list. totalTickets := 0 var sortedSlice []*TicketData for i := 0; i < BucketsSize; i++ { mapLen := len(tmdb.maps.ticketMap[i]) totalTickets += mapLen tempTdSlice := NewTicketDataSlice(mapLen) itr := 0 // Iterator for _, td := range tmdb.maps.ticketMap[i] { tempTdSlice[itr] = td itr++ } sort.Sort(tempTdSlice) sortedSlice = append(sortedSlice, tempTdSlice...) } // Use the parent block's header to seed a PRNG that picks the lottery winners. ticketsPerBlock := int(tmdb.chainParams.TicketsPerBlock) pbhB, err := parentBlock.MsgBlock().Header.Bytes() if err != nil { return nil, err } prng := NewHash256PRNG(pbhB) ts, err := FindTicketIdxs(int64(totalTickets), ticketsPerBlock, prng) if err != nil { return nil, err } ticketsToSpendOrMiss := make([]*TicketData, ticketsPerBlock, ticketsPerBlock) for i, idx := range ts { ticketsToSpendOrMiss[i] = sortedSlice[idx] } // Spend or miss these tickets by checking for their existence in the // passed usedtickets map. tixSpent := 0 tixMissed := 0 for _, ticket := range ticketsToSpendOrMiss { // Move the ticket from active tickets map into the used tickets map // if the ticket was spent. _, wasSpent := usedTickets[ticket.SStxHash] if wasSpent { ticket.Missed = false ticket.SpendHash = spendingHashes[ticket.SStxHash] err := tmdb.pushSpentTicket(height, ticket) if err != nil { return nil, err } err = tmdb.removeLiveTicket(ticket) if err != nil { return nil, err } tixSpent++ } else { // Ticket missed being spent and --> false or nil ticket.Missed = true // TODO fix test failure @ L150 due to this err := tmdb.pushSpentTicket(height, ticket) if err != nil { return nil, err } err = tmdb.pushMissedTicket(ticket) if err != nil { return nil, err } err = tmdb.removeLiveTicket(ticket) if err != nil { return nil, err } tixMissed++ } // Report on the spent and missed tickets for the block in debug. if ticket.Missed { log.Debugf("Ticket %v has been missed and expired from "+ "the lottery pool as a missed ticket", ticket.SStxHash) } else { log.Debugf("Ticket %v was spent and removed from "+ "the lottery pool", ticket.SStxHash) } // Add the ticket to the temporary tickets buffer for later use in // map restoration if needed. tempTickets[ticket.SStxHash] = ticket } // Some sanity checks. if tixSpent != len(usedTickets) { errStr := fmt.Sprintf("spendTickets error, an invalid number %v "+ "tickets was spent, but %v many tickets should "+ "have been spent!", tixSpent, len(usedTickets)) return nil, errors.New(errStr) } if tixMissed != (ticketsPerBlock - len(usedTickets)) { errStr := fmt.Sprintf("spendTickets error, an invalid number %v "+ "tickets was missed, but %v many tickets should "+ "have been missed!", tixMissed, ticketsPerBlock-len(usedTickets)) return nil, errors.New(errStr) } if (tixSpent + tixMissed) != ticketsPerBlock { errStr := fmt.Sprintf("spendTickets error, an invalid number %v "+ "tickets was spent and missed, but TicketsPerBlock %v many "+ "tickets should have been spent!", tixSpent, ticketsPerBlock) return nil, errors.New(errStr) } return tempTickets, nil }
// insertBlock is the internal function which implements the public // InsertBlock. See the comment for InsertBlock for more details. // // This function MUST be called with the tmdb lock held (for writes). func (tmdb *TicketDB) insertBlock(block *dcrutil.Block) (SStxMemMap, SStxMemMap, SStxMemMap, error) { height := block.Height() if height < tmdb.StakeEnabledHeight { return nil, nil, nil, nil } // Sanity check: Does the number of tickets in ticketMap equal the number // of tickets indicated in the header? poolSizeBlock := int(block.MsgBlock().Header.PoolSize) poolSize := 0 for i := 0; i < BucketsSize; i++ { poolSize += len(tmdb.maps.ticketMap[i]) } if poolSize != poolSizeBlock { return nil, nil, nil, fmt.Errorf("ticketpoolsize in block %v not "+ "equal to the calculated ticketpoolsize, indicating database "+ "corruption (got %v, want %v)", block.Sha(), poolSizeBlock, poolSize) } // Create the block in the spentTicketMap. tmdb.maybeInsertBlock(block.Height()) // Iterate through all the SSGen (vote) tx in the block and add them to // a map of tickets that were actually used. The rest of the tickets in // the buckets were then considered missed --> missedTicketMap. // Note that it doesn't really matter what value you set usedTickets to, // it's just a map of tickets that were actually used in the block. It // would probably be more efficient to use an array. usedTickets := make(map[chainhash.Hash]struct{}) spendingHashes := make(map[chainhash.Hash]chainhash.Hash) revocations := make(map[chainhash.Hash]struct{}) for _, staketx := range block.STransactions() { if is, _ := IsSSGen(staketx); is { msgTx := staketx.MsgTx() sstxIn := msgTx.TxIn[1] // sstx input sstxHash := sstxIn.PreviousOutPoint.Hash usedTickets[sstxHash] = struct{}{} spendingHashes[sstxHash] = *staketx.Sha() } if is, _ := IsSSRtx(staketx); is { msgTx := staketx.MsgTx() sstxIn := msgTx.TxIn[0] // sstx input sstxHash := sstxIn.PreviousOutPoint.Hash revocations[sstxHash] = struct{}{} } } // Spend or miss all the necessary tickets and do some sanity checks. parentBlock, err := tmdb.database.FetchBlockBySha( &block.MsgBlock().Header.PrevBlock) if err != nil { return nil, nil, nil, err } spentAndMissedTickets, err := tmdb.spendTickets(parentBlock, usedTickets, spendingHashes) if err != nil { return nil, nil, nil, err } // Expire all old tickets, and stick them into the spent and missed ticket // map too. expiredTickets, err := tmdb.expireTickets(height) if err != nil { return nil, nil, nil, err } if len(expiredTickets) > 0 && len(spentAndMissedTickets) == 0 { return nil, nil, nil, fmt.Errorf("tried to expire tickets before " + "stake validation height! TicketExpiry may be too small") } if len(expiredTickets) > 0 { for hash, ticket := range expiredTickets { spentAndMissedTickets[hash] = ticket } } revokedTickets, err := tmdb.revokeTickets(revocations) if err != nil { return nil, nil, nil, err } newTickets, err := tmdb.pushMatureTicketsAtHeight(block.Height()) if err != nil { return nil, nil, nil, err } log.Debugf("Connected block %v (height %v) to the ticket database", block.Sha(), block.Height()) return cloneSStxMemMap(spentAndMissedTickets), cloneSStxMemMap(newTickets), cloneSStxMemMap(revokedTickets), nil }
func testFetchTxByShaListCommon(tc *testContext, includeSpent bool) bool { var blockPrev *dcrutil.Block = nil if tc.block.Height() != 0 { var errBlockPrev error blockPrev, errBlockPrev = tc.db.FetchBlockBySha(&tc.block.MsgBlock().Header.PrevBlock) if errBlockPrev != nil { blockSha := tc.block.Sha() tc.t.Errorf("Failed to fetch parent block of block %v", blockSha) } } unspentFromTxTreeStake := unspendStakeTxTree(tc.block) votebits := tc.block.MsgBlock().Header.VoteBits if dcrutil.IsFlagSet16(votebits, dcrutil.BlockValid) && blockPrev != nil { fetchFunc := tc.db.FetchUnSpentTxByShaList funcName := "FetchUnSpentTxByShaList" if includeSpent { fetchFunc = tc.db.FetchTxByShaList funcName = "FetchTxByShaList" } transactions := blockPrev.Transactions() txHashes := make([]*chainhash.Hash, len(transactions)) for i, tx := range transactions { txHashes[i] = tx.Sha() } txReplyList := fetchFunc(txHashes) if len(txReplyList) != len(txHashes) { tc.t.Errorf("%s (%s): block #%d (%s) tx reply list does not "+ " match expected length - got: %v, want: %v", funcName, tc.dbType, tc.blockHeight, tc.blockHash, len(txReplyList), len(txHashes)) return false } for i, tx := range transactions { txHash := tx.Sha() txD := txReplyList[i] // The transaction hash in the reply must be the expected value. if !txD.Sha.IsEqual(txHash) { tc.t.Errorf("%s (%s): block #%d (%s) tx #%d (%s) "+ "hash does not match expected value - got %v", funcName, tc.dbType, tc.blockHeight, tc.blockHash, i, txHash, txD.Sha) return false } // The reply must not indicate any errors. if txD.Err != nil { tc.t.Errorf("%s (%s): block #%d (%s) tx #%d (%s) "+ "returned unexpected error - got %v, want nil", funcName, tc.dbType, tc.blockHeight, tc.blockHash, i, txHash, txD.Err) return false } // The transaction in the reply fetched from the database must // be the same MsgTx that was stored. if !reflect.DeepEqual(tx.MsgTx(), txD.Tx) { tc.t.Errorf("%s (%s): block #%d (%s) tx #%d (%s) does "+ "not match stored tx\ngot: %v\nwant: %v", funcName, tc.dbType, tc.blockHeight, tc.blockHash, i, txHash, spew.Sdump(txD.Tx), spew.Sdump(tx.MsgTx())) return false } // The block hash in the reply from the database must be the // expected value. if txD.BlkSha == nil { tc.t.Errorf("%s (%s): block #%d (%s) tx #%d (%s) "+ "returned nil block hash", funcName, tc.dbType, tc.blockHeight, tc.blockHash, i, txHash) return false } if !txD.BlkSha.IsEqual(&tc.block.MsgBlock().Header.PrevBlock) { tc.t.Errorf("%s (%s): block #%d (%s) tx #%d (%s)"+ "returned unexpected block hash - got %v", funcName, tc.dbType, tc.blockHeight, tc.blockHash, i, txHash, txD.BlkSha) return false } // The block height in the reply from the database must be the // expected value. if txD.Height != tc.blockHeight-1 { tc.t.Errorf("%s (%s): block #%d (%s) tx #%d (%s) "+ "returned unexpected block height - got %v", funcName, tc.dbType, tc.blockHeight, tc.blockHash, i, txHash, txD.Height) return false } // The spend data in the reply from the database must not // indicate any of the transactions that were just inserted are // spent. if txD.TxSpent == nil { tc.t.Errorf("%s (%s): block #%d (%s) tx #%d (%s) "+ "returned nil spend data", funcName, tc.dbType, tc.blockHeight, tc.blockHash, i, txHash) return false } spentBuf := expectedSpentBuf(tc, i) if !reflect.DeepEqual(txD.TxSpent, spentBuf) { stakeInChecksDontPass := false for txoIdx, _ := range spentBuf { if txD.TxSpent[txoIdx] != spentBuf[txoIdx] { op := wire.OutPoint{ *txHash, uint32(txoIdx), dcrutil.TxTreeRegular, } if _, unspent := unspentFromTxTreeStake[op]; !unspent { stakeInChecksDontPass = true } } } if stakeInChecksDontPass { tc.t.Errorf("%s (%s): block #%d (%s) tx #%d (%s) "+ "returned unexpected spend data - got %v, "+ "want %v", funcName, tc.dbType, tc.blockHeight, tc.blockHash, i, txHash, txD.TxSpent, spentBuf) return false } } } } return true }
// expectedSpentBuf returns the expected transaction spend information depending // on the block height and and transaction number. NOTE: These figures are // only valid for the specific set of test data provided at the time these tests // were written. In particular, this means the first 256 blocks of the mainnet // block chain. // // The first run through while the blocks are still being inserted, the tests // are running against the latest block and therefore none of the outputs can // be spent yet. However, on subsequent runs, all blocks have been inserted and // therefore some of the transaction outputs are spent. func expectedSpentBuf(tc *testContext, txNum int) []bool { var blah = []bool{false} var blockPrev *dcrutil.Block = nil if tc.block.Height() != 0 { var errBlockPrev error blockPrev, errBlockPrev = tc.db.FetchBlockBySha(&tc.block.MsgBlock().Header.PrevBlock) if errBlockPrev != nil { blockSha := tc.block.Sha() tc.t.Errorf("Failed to fetch parent block of block %v", blockSha) return blah } } transactions := blockPrev.Transactions() numTxOut := len(transactions[txNum].MsgTx().TxOut) spentBuf := make([]bool, numTxOut) if tc.useSpends { if tc.blockHeight >= 2 && tc.blockHeight <= 43 && txNum == 0 { spentBuf[0] = false spentBuf[1] = false spentBuf[2] = true } if tc.blockHeight == 45 && txNum == 0 { spentBuf[0] = false spentBuf[1] = false spentBuf[2] = true } if tc.blockHeight == 46 && txNum == 0 { spentBuf[0] = false spentBuf[1] = false spentBuf[2] = true } if tc.blockHeight == 48 && txNum == 0 { spentBuf[0] = false spentBuf[1] = false spentBuf[2] = true } if tc.blockHeight == 49 && txNum == 0 { spentBuf[0] = false spentBuf[1] = false spentBuf[2] = true } if tc.blockHeight == 54 && txNum == 0 { spentBuf[0] = false spentBuf[1] = false spentBuf[2] = true } if tc.blockHeight == 55 && txNum == 0 { spentBuf[0] = false spentBuf[1] = false spentBuf[2] = true } if tc.blockHeight == 57 && txNum == 0 { spentBuf[0] = false spentBuf[1] = false spentBuf[2] = true } if tc.blockHeight == 59 && txNum == 0 { spentBuf[0] = false spentBuf[1] = false spentBuf[2] = true } if tc.blockHeight == 63 && txNum == 0 { spentBuf[0] = false spentBuf[1] = false spentBuf[2] = true } if tc.blockHeight == 67 && txNum == 0 { spentBuf[0] = false spentBuf[1] = false spentBuf[2] = true } if tc.blockHeight == 68 && txNum == 0 { spentBuf[0] = false spentBuf[1] = false spentBuf[2] = true } if tc.blockHeight == 69 && txNum == 0 { spentBuf[0] = false spentBuf[1] = false spentBuf[2] = true } if tc.blockHeight == 70 && txNum == 0 { spentBuf[0] = false spentBuf[1] = false spentBuf[2] = true } if tc.blockHeight == 73 && txNum == 0 { spentBuf[0] = false spentBuf[1] = false spentBuf[2] = true } if tc.blockHeight == 74 && txNum == 0 { spentBuf[0] = false spentBuf[1] = false spentBuf[2] = true } if tc.blockHeight == 76 && txNum == 0 { spentBuf[0] = false spentBuf[1] = false spentBuf[2] = true } if tc.blockHeight == 77 && txNum == 0 { spentBuf[0] = false spentBuf[1] = false spentBuf[2] = true } if tc.blockHeight >= 105 && tc.blockHeight <= 120 && txNum == 0 { spentBuf[0] = false spentBuf[1] = false spentBuf[2] = true } if tc.blockHeight == 122 && txNum == 0 { spentBuf[0] = false spentBuf[1] = false spentBuf[2] = true } if tc.blockHeight == 125 && txNum == 0 { spentBuf[0] = false spentBuf[1] = false spentBuf[2] = true } if tc.blockHeight == 127 && txNum == 0 { spentBuf[0] = false spentBuf[1] = false spentBuf[2] = true } if tc.blockHeight == 131 && txNum == 0 { spentBuf[0] = false spentBuf[1] = false spentBuf[2] = true } if tc.blockHeight == 132 && txNum == 0 { spentBuf[0] = false spentBuf[1] = false spentBuf[2] = true } if tc.blockHeight == 134 && txNum == 0 { spentBuf[0] = false spentBuf[1] = false spentBuf[2] = true } if tc.blockHeight == 44 && txNum == 1 { spentBuf[0] = true spentBuf[1] = true spentBuf[2] = true spentBuf[3] = true spentBuf[4] = false spentBuf[5] = true } if tc.blockHeight == 60 && txNum == 1 { spentBuf[0] = false spentBuf[1] = true spentBuf[2] = false spentBuf[3] = false spentBuf[4] = false spentBuf[5] = false } if tc.blockHeight == 75 && txNum == 1 { spentBuf[0] = false spentBuf[1] = false spentBuf[2] = false spentBuf[3] = false spentBuf[4] = false spentBuf[5] = true } if tc.blockHeight == 78 && txNum == 2 { spentBuf[0] = false spentBuf[1] = false spentBuf[2] = false spentBuf[3] = false spentBuf[4] = false spentBuf[5] = true } if tc.blockHeight == 79 && txNum == 1 { spentBuf[0] = false spentBuf[1] = true spentBuf[2] = false spentBuf[3] = false spentBuf[4] = false spentBuf[5] = false } if tc.blockHeight == 89 && txNum == 1 { spentBuf[0] = false spentBuf[1] = false spentBuf[2] = true spentBuf[3] = false spentBuf[4] = false spentBuf[5] = false } if tc.blockHeight == 90 && txNum == 1 { spentBuf[0] = false spentBuf[1] = false spentBuf[2] = false spentBuf[3] = false spentBuf[4] = false spentBuf[5] = true } if tc.blockHeight == 93 && txNum == 1 { spentBuf[0] = false spentBuf[1] = false spentBuf[2] = false spentBuf[3] = false spentBuf[4] = false spentBuf[5] = true } if tc.blockHeight == 95 && txNum == 1 { spentBuf[0] = false spentBuf[1] = false spentBuf[2] = true spentBuf[3] = false spentBuf[4] = false spentBuf[5] = false } if tc.blockHeight == 97 && txNum == 3 { spentBuf[0] = false spentBuf[1] = false spentBuf[2] = false spentBuf[3] = true spentBuf[4] = false spentBuf[5] = false } if tc.blockHeight == 99 && txNum == 1 { spentBuf[0] = false spentBuf[1] = false spentBuf[2] = false spentBuf[3] = true spentBuf[4] = false spentBuf[5] = false } if tc.blockHeight == 101 && txNum == 1 { spentBuf[0] = false spentBuf[1] = false spentBuf[2] = false spentBuf[3] = false spentBuf[4] = false spentBuf[5] = true } if tc.blockHeight == 103 && txNum == 1 { spentBuf[0] = true spentBuf[1] = false spentBuf[2] = false spentBuf[3] = false spentBuf[4] = false spentBuf[5] = false } if tc.blockHeight == 106 && txNum == 1 { spentBuf[0] = true spentBuf[1] = true spentBuf[2] = true spentBuf[3] = true spentBuf[4] = true spentBuf[5] = false } if tc.blockHeight == 111 && txNum == 1 { spentBuf[0] = false spentBuf[1] = false spentBuf[2] = false spentBuf[3] = false spentBuf[4] = false spentBuf[5] = true } if tc.blockHeight == 113 && txNum == 1 { spentBuf[0] = false spentBuf[1] = false spentBuf[2] = false spentBuf[3] = false spentBuf[4] = true spentBuf[5] = false } if tc.blockHeight == 113 && txNum == 2 { spentBuf[0] = true spentBuf[1] = false spentBuf[2] = false spentBuf[3] = false spentBuf[4] = false spentBuf[5] = true } if tc.blockHeight == 117 && txNum == 1 { spentBuf[0] = false spentBuf[1] = false spentBuf[2] = true spentBuf[3] = false spentBuf[4] = false spentBuf[5] = false } if tc.blockHeight == 122 && txNum == 1 { spentBuf[0] = true spentBuf[1] = true spentBuf[2] = true spentBuf[3] = false spentBuf[4] = true spentBuf[5] = true } if tc.blockHeight == 131 && txNum == 1 { spentBuf[0] = true spentBuf[1] = false spentBuf[2] = true spentBuf[3] = true spentBuf[4] = true spentBuf[5] = true } if tc.blockHeight == 135 && txNum == 1 { spentBuf[0] = true spentBuf[1] = true spentBuf[2] = true spentBuf[3] = true spentBuf[4] = true spentBuf[5] = false } if tc.blockHeight == 141 && txNum == 1 { spentBuf[0] = true spentBuf[1] = true spentBuf[2] = true spentBuf[3] = true spentBuf[4] = false spentBuf[5] = true } if tc.blockHeight == 142 && txNum == 1 { spentBuf[0] = true spentBuf[1] = true spentBuf[2] = false spentBuf[3] = true spentBuf[4] = true spentBuf[5] = true } if tc.blockHeight == 145 && txNum == 1 { spentBuf[0] = true spentBuf[1] = false spentBuf[2] = true spentBuf[3] = true spentBuf[4] = true spentBuf[5] = true } if tc.blockHeight == 146 && txNum == 1 { spentBuf[0] = true spentBuf[1] = false spentBuf[2] = false spentBuf[3] = true spentBuf[4] = false spentBuf[5] = true } if tc.blockHeight == 146 && txNum == 2 { spentBuf[0] = true spentBuf[1] = true spentBuf[2] = true spentBuf[3] = true spentBuf[4] = false spentBuf[5] = true } if tc.blockHeight == 147 && txNum == 1 { spentBuf[0] = false spentBuf[1] = false spentBuf[2] = true spentBuf[3] = false spentBuf[4] = true spentBuf[5] = false } } return spentBuf }
// testFetchTxBySha ensures FetchTxBySha conforms to the interface contract. func testFetchTxBySha(tc *testContext) bool { var blockPrev *dcrutil.Block = nil if tc.block.Height() != 0 { var errBlockPrev error blockPrev, errBlockPrev = tc.db.FetchBlockBySha(&tc.block.MsgBlock().Header.PrevBlock) if errBlockPrev != nil { blockSha := tc.block.Sha() tc.t.Errorf("Failed to fetch parent block of block %v", blockSha) } } votebits := tc.block.MsgBlock().Header.VoteBits if dcrutil.IsFlagSet16(votebits, dcrutil.BlockValid) && blockPrev != nil { for i, tx := range blockPrev.Transactions() { txHash := tx.Sha() txReplyList, err := tc.db.FetchTxBySha(txHash) if err != nil { tc.t.Errorf("FetchTxBySha (%s): block #%d (%s) "+ "tx #%d (%s) err: %v", tc.dbType, tc.blockHeight, tc.blockHash, i, txHash, err) return false } if len(txReplyList) == 0 { tc.t.Errorf("FetchTxBySha (%s): block #%d (%s) "+ "tx #%d (%s) did not return reply data", tc.dbType, tc.blockHeight, tc.blockHash, i, txHash) return false } txFromDb := txReplyList[len(txReplyList)-1].Tx if !reflect.DeepEqual(tx.MsgTx(), txFromDb) { tc.t.Errorf("FetchTxBySha (%s): block #%d (%s) "+ "tx #%d (%s, %s) does not match stored tx\n"+ "got: %v\nwant: %v", tc.dbType, tc.blockHeight, tc.blockHash, i, txHash, txFromDb.TxSha(), spew.Sdump(txFromDb), spew.Sdump(tx.MsgTx())) return false } } } for i, tx := range tc.block.MsgBlock().STransactions { txHash := tx.TxSha() txReplyList, err := tc.db.FetchTxBySha(&txHash) if err != nil { tc.t.Errorf("FetchTxBySha (%s): block #%d (%s) "+ "sstx #%d (%s) err: %v", tc.dbType, tc.blockHeight, tc.blockHash, i, txHash, err) return false } if len(txReplyList) == 0 { tc.t.Errorf("FetchTxBySha (%s): block #%d (%s) "+ "sstx #%d (%s) did not return reply data", tc.dbType, tc.blockHeight, tc.blockHash, i, txHash) return false } txFromDb := txReplyList[len(txReplyList)-1].Tx if !reflect.DeepEqual(tx, txFromDb) { tc.t.Errorf("FetchTxBySha (%s): block #%d (%s) "+ "sstx #%d (%s) does not match stored sstx\n"+ "got: %v\nwant: %v", tc.dbType, tc.blockHeight, tc.blockHash, i, txHash, spew.Sdump(txFromDb), spew.Sdump(tx)) return false } } return true }