// 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 }
// 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 }
// 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 }
// 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 }
// lookupTransaction is a special transaction lookup function that searches // the database, the block, and its parent for a transaction. This is needed // because indexBlockAddrs is called AFTER a block is added/removed in the // blockchain in blockManager, necessitating that the blocks internally be // searched for inputs for any given transaction too. Additionally, it's faster // to get the tx from the blocks here since they're already func (a *addrIndexer) lookupTransaction(txHash chainhash.Hash, blk *dcrutil.Block, parent *dcrutil.Block) (*wire.MsgTx, error) { // Search the previous block and parent first. txTreeRegularValid := dcrutil.IsFlagSet16(blk.MsgBlock().Header.VoteBits, dcrutil.BlockValid) // Search the regular tx tree of this and the last block if the // tx tree regular was validated. if txTreeRegularValid { for _, stx := range parent.STransactions() { if stx.Sha().IsEqual(&txHash) { return stx.MsgTx(), nil } } for _, tx := range parent.Transactions() { if tx.Sha().IsEqual(&txHash) { return tx.MsgTx(), nil } } for _, tx := range blk.Transactions() { if tx.Sha().IsEqual(&txHash) { return tx.MsgTx(), nil } } } else { // Just search this block's regular tx tree and the previous // block's stake tx tree. for _, stx := range parent.STransactions() { if stx.Sha().IsEqual(&txHash) { return stx.MsgTx(), nil } } for _, tx := range blk.Transactions() { if tx.Sha().IsEqual(&txHash) { return tx.MsgTx(), nil } } } // Lookup and fetch the referenced output's tx in the database. txList, err := a.server.db.FetchTxBySha(&txHash) if err != nil { adxrLog.Errorf("Error fetching tx %v: %v", txHash, err) return nil, err } if len(txList) == 0 { return nil, fmt.Errorf("transaction %v not found", txHash) } return txList[len(txList)-1].Tx, nil }
// 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 }
// connectTransactions updates the view by adding all new utxos created by all // of the transactions in the passed block, marking all utxos the transactions // spend as spent, and setting the best hash for the view to the passed block. // In addition, when the 'stxos' argument is not nil, it will be updated to // append an entry for each spent txout. func (b *BlockChain) connectTransactions(view *UtxoViewpoint, block *dcrutil.Block, parent *dcrutil.Block, stxos *[]spentTxOut) error { regularTxTreeValid := dcrutil.IsFlagSet16(block.MsgBlock().Header.VoteBits, dcrutil.BlockValid) thisNodeStakeViewpoint := ViewpointPrevInvalidStake if regularTxTreeValid { thisNodeStakeViewpoint = ViewpointPrevValidStake } if parent != nil && block.Height() != 0 { view.SetStakeViewpoint(ViewpointPrevValidInitial) err := view.fetchInputUtxos(b.db, block, parent) if err != nil { return err } mBlock := block.MsgBlock() votebits := mBlock.Header.VoteBits regularTxTreeValid := dcrutil.IsFlagSet16(votebits, dcrutil.BlockValid) if regularTxTreeValid { for i, tx := range parent.Transactions() { err := view.connectTransaction(tx, parent.Height(), uint32(i), stxos) if err != nil { return err } } } } for i, stx := range block.STransactions() { view.SetStakeViewpoint(thisNodeStakeViewpoint) err := view.fetchInputUtxos(b.db, block, parent) if err != nil { return err } err = view.connectTransaction(stx, block.Height(), uint32(i), stxos) if err != nil { return err } } // Update the best hash for view to include this block since all of its // transactions have been connected. view.SetBestHash(block.Sha()) 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 }
// 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 }
// DebugBlockString dumps a verbose message containing information about // the transactions of a block. func DebugBlockString(block *dcrutil.Block) string { if block == nil { return "block pointer nil" } var buffer bytes.Buffer hash := block.Sha() str := fmt.Sprintf("Block Header: %v Height: %v \n", hash, block.Height()) buffer.WriteString(str) str = fmt.Sprintf("Block contains %v regular transactions "+ "and %v stake transactions \n", len(block.Transactions()), len(block.STransactions())) buffer.WriteString(str) str = fmt.Sprintf("List of regular transactions \n") buffer.WriteString(str) for i, tx := range block.Transactions() { str = fmt.Sprintf("Index: %v, Hash: %v \n", i, tx.Sha()) buffer.WriteString(str) } if len(block.STransactions()) == 0 { return buffer.String() } str = fmt.Sprintf("List of stake transactions \n") buffer.WriteString(str) for i, stx := range block.STransactions() { txTypeStr := "" txType := stake.DetermineTxType(stx) switch txType { case stake.TxTypeSStx: txTypeStr = "SStx" case stake.TxTypeSSGen: txTypeStr = "SSGen" case stake.TxTypeSSRtx: txTypeStr = "SSRtx" default: txTypeStr = "Error" } str = fmt.Sprintf("Index: %v, Type: %v, Hash: %v \n", i, txTypeStr, stx.Sha()) buffer.WriteString(str) } return buffer.String() }
// DropAfterBlockBySha will remove any blocks from the database after // the given block. func (db *LevelDb) DropAfterBlockBySha(sha *chainhash.Hash) (rerr error) { db.dbLock.Lock() defer db.dbLock.Unlock() defer func() { if rerr == nil { rerr = db.processBatches() } else { db.lBatch().Reset() } }() startheight := db.nextBlock - 1 keepidx, err := db.getBlkLoc(sha) if err != nil { // should the error here be normalized ? log.Tracef("block loc failed %v ", sha) return err } for height := startheight; height > keepidx; height = height - 1 { var blk *dcrutil.Block blksha, buf, err := db.getBlkByHeight(height) if err != nil { return err } blk, err = dcrutil.NewBlockFromBytes(buf) if err != nil { return err } // Obtain previous block sha and buffer var blkprev *dcrutil.Block _, bufprev, errprev := db.getBlkByHeight(height - 1) // discard blkshaprev if errprev != nil { return errprev } // Do the same thing for the parent block blkprev, errprev = dcrutil.NewBlockFromBytes(bufprev) if errprev != nil { return errprev } // Unspend the stake tx in the current block for _, tx := range blk.MsgBlock().STransactions { err = db.unSpend(tx) if err != nil { return err } } // rather than iterate the list of tx backward, do it twice. for _, tx := range blk.STransactions() { var txUo txUpdateObj txUo.delete = true db.txUpdateMap[*tx.Sha()] = &txUo } // Check to see if the regular txs of the parent were even included; if // they are, unspend all of these regular tx too votebits := blk.MsgBlock().Header.VoteBits if dcrutil.IsFlagSet16(votebits, dcrutil.BlockValid) && height != 0 { // Unspend the regular tx in the current block for _, tx := range blkprev.MsgBlock().Transactions { err = db.unSpend(tx) if err != nil { return err } } // rather than iterate the list of tx backward, do it twice. for _, tx := range blkprev.Transactions() { var txUo txUpdateObj txUo.delete = true db.txUpdateMap[*tx.Sha()] = &txUo } } db.lBatch().Delete(shaBlkToKey(blksha)) db.lBatch().Delete(int64ToKey(height)) } // update the last block cache db.lastBlkShaCached = true db.lastBlkSha = *sha db.lastBlkIdx = keepidx db.nextBlock = keepidx + 1 return nil }
// makeUtxoView creates a mock unspent transaction output view by using the // transaction index in order to look up all inputs referenced by the // transactions in the block. This is sometimes needed when catching indexes up // because many of the txouts could actually already be spent however the // associated scripts are still required to index them. func makeUtxoView(dbTx database.Tx, block, parent *dcrutil.Block) (*blockchain.UtxoViewpoint, error) { view := blockchain.NewUtxoViewpoint() regularTxTreeValid := dcrutil.IsFlagSet16(block.MsgBlock().Header.VoteBits, dcrutil.BlockValid) if regularTxTreeValid { for txIdx, tx := range parent.Transactions() { // Coinbases do not reference any inputs. Since the block is // required to have already gone through full validation, it has // already been proven on the first transaction in the block is // a coinbase. if txIdx == 0 { continue } // Use the transaction index to load all of the referenced // inputs and add their outputs to the view. for _, txIn := range tx.MsgTx().TxIn { // Skip already fetched outputs. originOut := &txIn.PreviousOutPoint if view.LookupEntry(&originOut.Hash) != nil { continue } originTx, err := dbFetchTx(dbTx, originOut.Hash) if err != nil { return nil, err } view.AddTxOuts(dcrutil.NewTx(originTx), int64(wire.NullBlockHeight), wire.NullBlockIndex) } } } for _, tx := range block.STransactions() { msgTx := tx.MsgTx() isSSGen, _ := stake.IsSSGen(msgTx) // Use the transaction index to load all of the referenced // inputs and add their outputs to the view. for i, txIn := range msgTx.TxIn { // Skip stakebases. if isSSGen && i == 0 { continue } originOut := &txIn.PreviousOutPoint if view.LookupEntry(&originOut.Hash) != nil { continue } originTx, err := dbFetchTx(dbTx, originOut.Hash) if err != nil { return nil, err } view.AddTxOuts(dcrutil.NewTx(originTx), int64(wire.NullBlockHeight), wire.NullBlockIndex) } } return view, nil }
// findWhereDoubleSpent determines where a tx was previously doublespent. // VERY INTENSIVE BLOCKCHAIN SCANNING, USE TO DEBUG SIMULATED BLOCKCHAINS // ONLY. func (b *BlockChain) findWhereDoubleSpent(block *dcrutil.Block) error { height := int64(1) heightEnd := block.Height() hashes, err := b.db.FetchHeightRange(height, heightEnd) if err != nil { return err } var allTxs []*dcrutil.Tx txs := block.Transactions()[1:] stxs := block.STransactions() allTxs = append(txs, stxs...) for _, hash := range hashes { curBlock, err := b.getBlockFromHash(&hash) if err != nil { return err } log.Errorf("Cur block %v", curBlock.Height()) for _, localTx := range allTxs { for _, localTxIn := range localTx.MsgTx().TxIn { for _, tx := range curBlock.Transactions()[1:] { for _, txIn := range tx.MsgTx().TxIn { if txIn.PreviousOutPoint == localTxIn.PreviousOutPoint { log.Errorf("Double spend of {hash: %v, idx: %v,"+ " tree: %b}, previously found in tx %v "+ "of block %v txtree regular", txIn.PreviousOutPoint.Hash, txIn.PreviousOutPoint.Index, txIn.PreviousOutPoint.Tree, tx.Sha(), hash) } } } for _, tx := range curBlock.STransactions() { for _, txIn := range tx.MsgTx().TxIn { if txIn.PreviousOutPoint == localTxIn.PreviousOutPoint { log.Errorf("Double spend of {hash: %v, idx: %v,"+ " tree: %b}, previously found in tx %v "+ "of block %v txtree stake\n", txIn.PreviousOutPoint.Hash, txIn.PreviousOutPoint.Index, txIn.PreviousOutPoint.Tree, tx.Sha(), hash) } } } } } } for _, localTx := range stxs { for _, localTxIn := range localTx.MsgTx().TxIn { for _, tx := range txs { for _, txIn := range tx.MsgTx().TxIn { if txIn.PreviousOutPoint == localTxIn.PreviousOutPoint { log.Errorf("Double spend of {hash: %v, idx: %v,"+ " tree: %b}, previously found in tx %v "+ "of cur block stake txtree\n", txIn.PreviousOutPoint.Hash, txIn.PreviousOutPoint.Index, txIn.PreviousOutPoint.Tree, tx.Sha()) } } } } } return nil }
// disconnectTransactions updates the view by removing all of the transactions // created by the passed block, restoring all utxos the transactions spent by // using the provided spent txo information, and setting the best hash for the // view to the block before the passed block. // // This function will ONLY work correctly for a single transaction tree at a // time because of index tracking. func (b *BlockChain) disconnectTransactions(view *UtxoViewpoint, block *dcrutil.Block, parent *dcrutil.Block, stxos []spentTxOut) error { // Sanity check the correct number of stxos are provided. if len(stxos) != countSpentOutputs(block, parent) { return AssertError(fmt.Sprintf("disconnectTransactions "+ "called with bad spent transaction out information "+ "(len stxos %v, count is %v)", len(stxos), countSpentOutputs(block, parent))) } // Loop backwards through all transactions so everything is unspent in // reverse order. This is necessary since transactions later in a block // can spend from previous ones. regularTxTreeValid := dcrutil.IsFlagSet16(block.MsgBlock().Header.VoteBits, dcrutil.BlockValid) thisNodeStakeViewpoint := ViewpointPrevInvalidStake if regularTxTreeValid { thisNodeStakeViewpoint = ViewpointPrevValidStake } view.SetStakeViewpoint(thisNodeStakeViewpoint) err := view.fetchInputUtxos(b.db, block, parent) if err != nil { return err } stxoIdx := len(stxos) - 1 transactions := block.STransactions() for txIdx := len(transactions) - 1; txIdx > -1; txIdx-- { tx := transactions[txIdx] msgTx := tx.MsgTx() tt := stake.DetermineTxType(msgTx) // Clear this transaction from the view if it already exists or // create a new empty entry for when it does not. This is done // because the code relies on its existence in the view in order // to signal modifications have happened. entry := view.entries[*tx.Sha()] if entry == nil { entry = newUtxoEntry(msgTx.Version, uint32(block.Height()), uint32(txIdx), IsCoinBaseTx(msgTx), msgTx.Expiry != 0, tt) if tt == stake.TxTypeSStx { stakeExtra := make([]byte, serializeSizeForMinimalOutputs(tx)) putTxToMinimalOutputs(stakeExtra, tx) entry.stakeExtra = stakeExtra } view.entries[*tx.Sha()] = entry } entry.modified = true entry.sparseOutputs = make(map[uint32]*utxoOutput) // Loop backwards through all of the transaction inputs (except // for the coinbase which has no inputs) and unspend the // referenced txos. This is necessary to match the order of the // spent txout entries. for txInIdx := len(tx.MsgTx().TxIn) - 1; txInIdx > -1; txInIdx-- { // Skip empty vote stakebases. if txInIdx == 0 && (tt == stake.TxTypeSSGen) { continue } // Ensure the spent txout index is decremented to stay // in sync with the transaction input. stxo := &stxos[stxoIdx] stxoIdx-- // When there is not already an entry for the referenced // transaction in the view, it means it was fully spent, // so create a new utxo entry in order to resurrect it. txIn := tx.MsgTx().TxIn[txInIdx] originHash := &txIn.PreviousOutPoint.Hash originIndex := txIn.PreviousOutPoint.Index entry := view.LookupEntry(originHash) if entry == nil { if !stxo.txFullySpent { return AssertError(fmt.Sprintf("tried to revive utx %v from "+ "non-fully spent stx entry", originHash)) } entry = newUtxoEntry(tx.MsgTx().Version, stxo.height, stxo.index, stxo.isCoinBase, stxo.hasExpiry, stxo.txType) if stxo.txType == stake.TxTypeSStx { entry.stakeExtra = stxo.stakeExtra } view.entries[*originHash] = entry } // Mark the entry as modified since it is either new // or will be changed below. entry.modified = true // Restore the specific utxo using the stxo data from // the spend journal if it doesn't already exist in the // view. output, ok := entry.sparseOutputs[originIndex] if !ok { // Add the unspent transaction output. entry.sparseOutputs[originIndex] = &utxoOutput{ compressed: stxo.compressed, spent: false, amount: txIn.ValueIn, scriptVersion: stxo.scriptVersion, pkScript: stxo.pkScript, } continue } // Mark the existing referenced transaction output as // unspent. output.spent = false } } // There is no regular tx from before the genesis block, so ignore the genesis // block for the next step. if parent != nil && block.Height() != 0 { // Only bother to unspend transactions if the parent's tx tree was // validated. Otherwise, these transactions were never in the blockchain's // history in the first place. if regularTxTreeValid { view.SetStakeViewpoint(ViewpointPrevValidInitial) err = view.fetchInputUtxos(b.db, block, parent) if err != nil { return err } transactions := parent.Transactions() for txIdx := len(transactions) - 1; txIdx > -1; txIdx-- { tx := transactions[txIdx] // Clear this transaction from the view if it already exists or // create a new empty entry for when it does not. This is done // because the code relies on its existence in the view in order // to signal modifications have happened. isCoinbase := txIdx == 0 entry := view.entries[*tx.Sha()] if entry == nil { entry = newUtxoEntry(tx.MsgTx().Version, uint32(parent.Height()), uint32(txIdx), isCoinbase, tx.MsgTx().Expiry != 0, stake.TxTypeRegular) view.entries[*tx.Sha()] = entry } entry.modified = true entry.sparseOutputs = make(map[uint32]*utxoOutput) // Loop backwards through all of the transaction inputs (except // for the coinbase which has no inputs) and unspend the // referenced txos. This is necessary to match the order of the // spent txout entries. if isCoinbase { continue } for txInIdx := len(tx.MsgTx().TxIn) - 1; txInIdx > -1; txInIdx-- { // Ensure the spent txout index is decremented to stay // in sync with the transaction input. stxo := &stxos[stxoIdx] stxoIdx-- // When there is not already an entry for the referenced // transaction in the view, it means it was fully spent, // so create a new utxo entry in order to resurrect it. txIn := tx.MsgTx().TxIn[txInIdx] originHash := &txIn.PreviousOutPoint.Hash originIndex := txIn.PreviousOutPoint.Index entry := view.entries[*originHash] if entry == nil { if !stxo.txFullySpent { return AssertError(fmt.Sprintf("tried to "+ "revive utx %v from non-fully spent stx entry", originHash)) } entry = newUtxoEntry(tx.MsgTx().Version, stxo.height, stxo.index, stxo.isCoinBase, stxo.hasExpiry, stxo.txType) if stxo.txType == stake.TxTypeSStx { entry.stakeExtra = stxo.stakeExtra } view.entries[*originHash] = entry } // Mark the entry as modified since it is either new // or will be changed below. entry.modified = true // Restore the specific utxo using the stxo data from // the spend journal if it doesn't already exist in the // view. output, ok := entry.sparseOutputs[originIndex] if !ok { // Add the unspent transaction output. entry.sparseOutputs[originIndex] = &utxoOutput{ compressed: stxo.compressed, spent: false, amount: txIn.ValueIn, scriptVersion: stxo.scriptVersion, pkScript: stxo.pkScript, } continue } // Mark the existing referenced transaction output as // unspent. output.spent = false } } } } // Update the best hash for view to the previous block since all of the // transactions for the current block have been disconnected. view.SetBestHash(parent.Sha()) return nil }
// dbAddTxIndexEntries uses an existing database transaction to add a // transaction index entry for every transaction in the passed block. func dbAddTxIndexEntries(dbTx database.Tx, block, parent *dcrutil.Block, blockID uint32) error { // The offset and length of the transactions within the serialized // block, for the regular transactions of the parent (if added) // and the stake transactions of the current block. regularTxTreeValid := dcrutil.IsFlagSet16(block.MsgBlock().Header.VoteBits, dcrutil.BlockValid) var parentRegularTxs []*dcrutil.Tx var parentTxLocs []wire.TxLoc var parentBlockID uint32 if regularTxTreeValid && block.Height() > 1 { var err error parentRegularTxs = parent.Transactions() parentTxLocs, _, err = parent.TxLoc() if err != nil { return err } parentSha := parent.Sha() parentBlockID, err = dbFetchBlockIDByHash(dbTx, *parentSha) if err != nil { return err } } _, blockStxLocs, err := block.TxLoc() if err != nil { return err } allTxs := append(parentRegularTxs, block.STransactions()...) allTxsLocs := append(parentTxLocs, blockStxLocs...) stakeTxStartIdx := len(parentRegularTxs) // As an optimization, allocate a single slice big enough to hold all // of the serialized transaction index entries for the block and // serialize them directly into the slice. Then, pass the appropriate // subslice to the database to be written. This approach significantly // cuts down on the number of required allocations. offset := 0 serializedValues := make([]byte, len(allTxs)*txEntrySize) blockIDToUse := parentBlockID for i, tx := range allTxs { // Switch to using the newest block ID for the stake transactions, // since these are not from the parent. if i == stakeTxStartIdx { blockIDToUse = blockID } putTxIndexEntry(serializedValues[offset:], blockIDToUse, allTxsLocs[i]) endOffset := offset + txEntrySize txSha := tx.Sha() err := dbPutTxIndexEntry(dbTx, *txSha, serializedValues[offset:endOffset:endOffset]) if err != nil { return err } offset += txEntrySize } return 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 }
// indexBlockAddrs returns a populated index of the all the transactions in the // passed block based on the addresses involved in each transaction. func (a *addrIndexer) indexBlockAddrs(blk *dcrutil.Block, parent *dcrutil.Block) (database.BlockAddrIndex, error) { var addrIndex database.BlockAddrIndex _, stxLocs, err := blk.TxLoc() if err != nil { return nil, err } txTreeRegularValid := dcrutil.IsFlagSet16(blk.MsgBlock().Header.VoteBits, dcrutil.BlockValid) // Add regular transactions iff the block was validated. if txTreeRegularValid { txLocs, _, err := parent.TxLoc() if err != nil { return nil, err } for txIdx, tx := range parent.Transactions() { // Tx's offset and length in the block. locInBlock := &txLocs[txIdx] // Coinbases don't have any inputs. if !blockchain.IsCoinBase(tx) { // Index the SPK's of each input's previous outpoint // transaction. for _, txIn := range tx.MsgTx().TxIn { prevOutTx, err := a.lookupTransaction( txIn.PreviousOutPoint.Hash, blk, parent) inputOutPoint := prevOutTx.TxOut[txIn.PreviousOutPoint.Index] toAppend, err := convertToAddrIndex(inputOutPoint.Version, inputOutPoint.PkScript, parent.Height(), locInBlock) if err != nil { adxrLog.Tracef("Error converting tx txin %v: %v", txIn.PreviousOutPoint.Hash, err) continue } addrIndex = append(addrIndex, toAppend...) } } for _, txOut := range tx.MsgTx().TxOut { toAppend, err := convertToAddrIndex(txOut.Version, txOut.PkScript, parent.Height(), locInBlock) if err != nil { adxrLog.Tracef("Error converting tx txout %v: %v", tx.MsgTx().TxSha(), err) continue } addrIndex = append(addrIndex, toAppend...) } } } // Add stake transactions. for stxIdx, stx := range blk.STransactions() { // Tx's offset and length in the block. locInBlock := &stxLocs[stxIdx] isSSGen, _ := stake.IsSSGen(stx) // Index the SPK's of each input's previous outpoint // transaction. for i, txIn := range stx.MsgTx().TxIn { // Stakebases don't have any inputs. if isSSGen && i == 0 { continue } // Lookup and fetch the referenced output's tx. prevOutTx, err := a.lookupTransaction( txIn.PreviousOutPoint.Hash, blk, parent) inputOutPoint := prevOutTx.TxOut[txIn.PreviousOutPoint.Index] toAppend, err := convertToAddrIndex(inputOutPoint.Version, inputOutPoint.PkScript, blk.Height(), locInBlock) if err != nil { adxrLog.Tracef("Error converting stx txin %v: %v", txIn.PreviousOutPoint.Hash, err) continue } addrIndex = append(addrIndex, toAppend...) } for _, txOut := range stx.MsgTx().TxOut { toAppend, err := convertToAddrIndex(txOut.Version, txOut.PkScript, blk.Height(), locInBlock) if err != nil { adxrLog.Tracef("Error converting stx txout %v: %v", stx.MsgTx().TxSha(), err) continue } addrIndex = append(addrIndex, toAppend...) } } return addrIndex, nil }
// disconnectTransactions updates the passed map by undoing transaction and // spend information for all transactions in the passed block. Only // transactions in the passed map are updated. // This function should only ever have to disconnect transactions from the main // chain, so most of the calls are directly the the tmdb which contains all this // data in an organized bucket. func (b *BlockChain) disconnectTickets(tixStore TicketStore, node *blockNode, block *dcrutil.Block) error { tM := int64(b.chainParams.TicketMaturity) height := node.height // Nothing to do if tickets haven't yet possibly matured. if height < b.chainParams.StakeEnabledHeight { return nil } // PART 1: Remove newly maturing tickets // Calculate block number for where new tickets matured from and retrieve // this block from db. matureNode, err := b.getNodeAtHeightFromTopNode(node, tM) if err != nil { return err } matureBlock, errBlock := b.getBlockFromHash(matureNode.hash) if errBlock != nil { return errBlock } // Store pointers to empty ticket data in the ticket store and mark them as // non-existing. for _, stx := range matureBlock.STransactions() { if is, _ := stake.IsSStx(stx); is { // Leave this pointing to nothing, as the ticket technically does not // exist. It may exist when we add blocks later, but we can fill it // out then. td := &stake.TicketData{} tpd := NewTicketPatchData(td, TiNonexisting, nil) tixStore[*stx.Sha()] = tpd } } // PART 2: Unrevoke any SSRtx in this block and restore them as // missed tickets. for _, stx := range block.STransactions() { if is, _ := stake.IsSSRtx(stx); is { // Move the revoked ticket to missed tickets. Obtain the // revoked ticket data from the ticket database. msgTx := stx.MsgTx() sstxIn := msgTx.TxIn[0] // sstx input sstxHash := sstxIn.PreviousOutPoint.Hash td := b.tmdb.GetRevokedTicket(sstxHash) if td == nil { return fmt.Errorf("Failed to find revoked ticket %v in tmdb", sstxHash) } tpd := NewTicketPatchData(td, TiMissed, nil) tixStore[sstxHash] = tpd } } // PART 3: Unspend or unmiss all tickets spent/missed/expired at this block. // Query the stake db for used tickets (spentTicketDb), which includes all of // the spent and missed tickets. spentTickets, errDump := b.tmdb.DumpSpentTickets(height) if errDump != nil { return errDump } // Move all of these tickets into the ticket store as available tickets. for hash, td := range spentTickets { tpd := NewTicketPatchData(td, TiAvailable, nil) tixStore[hash] = tpd } return nil }
// connectTickets updates the passed map by removing removing any tickets // from the ticket pool that have been considered spent or missed in this block // according to the block header. Then, it connects all the newly mature tickets // to the passed map. func (b *BlockChain) connectTickets(tixStore TicketStore, node *blockNode, block *dcrutil.Block) error { if tixStore == nil { return fmt.Errorf("nil ticket store!") } // Nothing to do if tickets haven't yet possibly matured. height := node.height if height < b.chainParams.StakeEnabledHeight { return nil } parentBlock, err := b.GetBlockFromHash(node.parentHash) if err != nil { return err } revocations := node.header.Revocations tM := int64(b.chainParams.TicketMaturity) // Skip a number of validation steps before we requiring chain // voting. if node.height >= b.chainParams.StakeValidationHeight { regularTxTreeValid := dcrutil.IsFlagSet16(node.header.VoteBits, dcrutil.BlockValid) thisNodeStakeViewpoint := ViewpointPrevInvalidStake if regularTxTreeValid { thisNodeStakeViewpoint = ViewpointPrevValidStake } // We need the missed tickets bucket from the original perspective of // the node. missedTickets, err := b.GenerateMissedTickets(tixStore) if err != nil { return err } // TxStore at blockchain HEAD + TxTreeRegular of prevBlock (if // validated) for this node. txInputStoreStake, err := b.fetchInputTransactions(node, block, thisNodeStakeViewpoint) if err != nil { errStr := fmt.Sprintf("fetchInputTransactions failed for incoming "+ "node %v; error given: %v", node.hash, err) return errors.New(errStr) } // PART 1: Spend/miss winner tickets // Iterate through all the SSGen (vote) tx in the block and add them to // a map of tickets that were actually used. spentTicketsFromBlock := make(map[chainhash.Hash]bool) numberOfSSgen := 0 for _, staketx := range block.STransactions() { if is, _ := stake.IsSSGen(staketx); is { msgTx := staketx.MsgTx() sstxIn := msgTx.TxIn[1] // sstx input sstxHash := sstxIn.PreviousOutPoint.Hash originTx, exists := txInputStoreStake[sstxHash] if !exists { str := fmt.Sprintf("unable to find input transaction "+ "%v for transaction %v", sstxHash, staketx.Sha()) return ruleError(ErrMissingTx, str) } sstxHeight := originTx.BlockHeight // Check maturity of ticket; we can only spend the ticket after it // hits maturity at height + tM + 1. if (height - sstxHeight) < (tM + 1) { blockSha := block.Sha() errStr := fmt.Sprintf("Error: A ticket spend as an SSGen in "+ "block height %v was immature! Block sha %v", height, blockSha) return errors.New(errStr) } // Fill out the ticket data. spentTicketsFromBlock[sstxHash] = true numberOfSSgen++ } } // Obtain the TicketsPerBlock many tickets that were selected this round, // then check these against the tickets that were actually used to make // sure that any SSGen actually match the selected tickets. Commit the // spent or missed tickets to the ticket store after. spentAndMissedTickets := make(TicketStore) tixSpent := 0 tixMissed := 0 // Sort the entire list of tickets lexicographically by sorting // each bucket and then appending it to the list. Start by generating // a prefix matched map of tickets to speed up the lookup. tpdBucketMap := make(map[uint8][]*TicketPatchData) for _, tpd := range tixStore { // Bucket does not exist. if _, ok := tpdBucketMap[tpd.td.Prefix]; !ok { tpdBucketMap[tpd.td.Prefix] = make([]*TicketPatchData, 1) tpdBucketMap[tpd.td.Prefix][0] = tpd } else { // Bucket exists. data := tpdBucketMap[tpd.td.Prefix] tpdBucketMap[tpd.td.Prefix] = append(data, tpd) } } totalTickets := 0 sortedSlice := make([]*stake.TicketData, 0) for i := 0; i < stake.BucketsSize; i++ { ltb, err := b.GenerateLiveTicketBucket(tixStore, tpdBucketMap, uint8(i)) if err != nil { h := node.hash str := fmt.Sprintf("Failed to generate live ticket bucket "+ "%v for node %v, height %v! Error: %v", i, h, node.height, err.Error()) return fmt.Errorf(str) } mapLen := len(ltb) tempTdSlice := stake.NewTicketDataSlice(mapLen) itr := 0 // Iterator for _, td := range ltb { tempTdSlice[itr] = td itr++ totalTickets++ } sort.Sort(tempTdSlice) sortedSlice = append(sortedSlice, tempTdSlice...) } // Use the parent block's header to seed a PRNG that picks the // lottery winners. ticketsPerBlock := int(b.chainParams.TicketsPerBlock) pbhB, err := parentBlock.MsgBlock().Header.Bytes() if err != nil { return err } prng := stake.NewHash256PRNG(pbhB) ts, err := stake.FindTicketIdxs(int64(totalTickets), ticketsPerBlock, prng) if err != nil { return err } ticketsToSpendOrMiss := make([]*stake.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 spentTicketsFromBlock map. for _, ticket := range ticketsToSpendOrMiss { // Move the ticket from active tickets map into the used tickets // map if the ticket was spent. wasSpent, _ := spentTicketsFromBlock[ticket.SStxHash] if wasSpent { tpd := NewTicketPatchData(ticket, TiSpent, nil) spentAndMissedTickets[ticket.SStxHash] = tpd tixSpent++ } else { // Ticket missed being spent and --> false or nil tpd := NewTicketPatchData(ticket, TiMissed, nil) spentAndMissedTickets[ticket.SStxHash] = tpd tixMissed++ } } // This error is thrown if for some reason there exists an SSGen in // the block that doesn't spend a ticket from the eligible list of // tickets, thus making it invalid. if tixSpent != numberOfSSgen { errStr := fmt.Sprintf("an invalid number %v "+ "tickets was spent, but %v many tickets should "+ "have been spent!", tixSpent, numberOfSSgen) return errors.New(errStr) } if tixMissed != (ticketsPerBlock - numberOfSSgen) { errStr := fmt.Sprintf("an invalid number %v "+ "tickets was missed, but %v many tickets should "+ "have been missed!", tixMissed, ticketsPerBlock-numberOfSSgen) return errors.New(errStr) } if (tixSpent + tixMissed) != int(b.chainParams.TicketsPerBlock) { errStr := fmt.Sprintf("an invalid number %v "+ "tickets was spent and missed, but TicketsPerBlock %v many "+ "tickets should have been spent!", tixSpent, ticketsPerBlock) return errors.New(errStr) } // Calculate all the tickets expiring this block and mark them as missed. tpdBucketMap = make(map[uint8][]*TicketPatchData) for _, tpd := range tixStore { // Bucket does not exist. if _, ok := tpdBucketMap[tpd.td.Prefix]; !ok { tpdBucketMap[tpd.td.Prefix] = make([]*TicketPatchData, 1) tpdBucketMap[tpd.td.Prefix][0] = tpd } else { // Bucket exists. data := tpdBucketMap[tpd.td.Prefix] tpdBucketMap[tpd.td.Prefix] = append(data, tpd) } } toExpireHeight := node.height - int64(b.chainParams.TicketExpiry) if !(toExpireHeight < int64(b.chainParams.StakeEnabledHeight)) { for i := 0; i < stake.BucketsSize; i++ { // Generate the live ticket bucket. ltb, err := b.GenerateLiveTicketBucket(tixStore, tpdBucketMap, uint8(i)) if err != nil { return err } for _, ticket := range ltb { if ticket.BlockHeight == toExpireHeight { tpd := NewTicketPatchData(ticket, TiMissed, nil) spentAndMissedTickets[ticket.SStxHash] = tpd } } } } // Merge the ticket store patch containing the spent and missed tickets // with the ticket store. for hash, tpd := range spentAndMissedTickets { tixStore[hash] = tpd } // At this point our tixStore now contains all the spent and missed tx // as per this block. // PART 2: Remove tickets that were missed and are now revoked. // Iterate through all the SSGen (vote) tx in the block and add them to // a map of tickets that were actually used. revocationsFromBlock := make(map[chainhash.Hash]struct{}) numberOfSSRtx := 0 for _, staketx := range block.STransactions() { if is, _ := stake.IsSSRtx(staketx); is { msgTx := staketx.MsgTx() sstxIn := msgTx.TxIn[0] // sstx input sstxHash := sstxIn.PreviousOutPoint.Hash // Fill out the ticket data. revocationsFromBlock[sstxHash] = struct{}{} numberOfSSRtx++ } } if numberOfSSRtx != int(revocations) { errStr := fmt.Sprintf("an invalid revocations %v was calculated "+ "the block header indicates %v instead", numberOfSSRtx, revocations) return errors.New(errStr) } // Lookup the missed ticket. If we find it in the patch data, // modify the patch data so that it doesn't exist. // Otherwise, just modify load the missed ticket data from // the ticket db and create patch data based on that. for hash, _ := range revocationsFromBlock { ticketWasMissed := false if td, is := missedTickets[hash]; is { maturedHeight := td.BlockHeight // Check maturity of ticket; we can only spend the ticket after it // hits maturity at height + tM + 2. if height < maturedHeight+2 { blockSha := block.Sha() errStr := fmt.Sprintf("Error: A ticket spend as an "+ "SSRtx in block height %v was immature! Block sha %v", height, blockSha) return errors.New(errStr) } ticketWasMissed = true } if !ticketWasMissed { errStr := fmt.Sprintf("SSRtx spent missed sstx %v, "+ "but that missed sstx could not be found!", hash) return errors.New(errStr) } } } // PART 3: Add newly maturing tickets // This is the only chunk we need to do for blocks appearing before // stake validation height. // Calculate block number for where new tickets are maturing from and retrieve // this block from db. // Get the block that is maturing. matureNode, err := b.getNodeAtHeightFromTopNode(node, tM) if err != nil { return err } matureBlock, errBlock := b.getBlockFromHash(matureNode.hash) if errBlock != nil { return errBlock } // Maturing tickets are from the maturingBlock; fill out the ticket patch data // and then push them to the tixStore. for _, stx := range matureBlock.STransactions() { if is, _ := stake.IsSStx(stx); is { // Calculate the prefix for pre-sort. sstxHash := *stx.Sha() prefix := uint8(sstxHash[0]) // Fill out the ticket data. td := stake.NewTicketData(sstxHash, prefix, chainhash.Hash{}, height, false, // not missed false) // not expired tpd := NewTicketPatchData(td, TiAvailable, nil) tixStore[*stx.Sha()] = tpd } } return nil }
// maybeAcceptBlock potentially accepts a block into the memory block chain. // It performs several validation checks which depend on its position within // the block chain before adding it. The block is expected to have already gone // through ProcessBlock before calling this function with it. // // The flags modify the behavior of this function as follows: // - BFDryRun: The memory chain index will not be pruned and no accept // notification will be sent since the block is not being accepted. func (b *BlockChain) maybeAcceptBlock(block *dcrutil.Block, flags BehaviorFlags) (bool, error) { dryRun := flags&BFDryRun == BFDryRun // Get a block node for the block previous to this one. Will be nil // if this is the genesis block. prevNode, err := b.getPrevNodeFromBlock(block) if err != nil { log.Debugf("getPrevNodeFromBlock: %v", err) return false, err } // The height of this block is one more than the referenced previous // block. blockHeight := int64(0) if prevNode != nil { blockHeight = prevNode.height + 1 } block.SetHeight(blockHeight) // The block must pass all of the validation rules which depend on the // position of the block within the block chain. err = b.checkBlockContext(block, prevNode, flags) if err != nil { return false, err } // Prune block nodes which are no longer needed before creating // a new node. if !dryRun { err = b.pruneBlockNodes() if err != nil { return false, err } } // Create a new block node for the block and add it to the in-memory // block chain (could be either a side chain or the main chain). blockHeader := &block.MsgBlock().Header var voteBitsStake []uint16 for _, stx := range block.STransactions() { if is, _ := stake.IsSSGen(stx); is { vb := stake.GetSSGenVoteBits(stx) voteBitsStake = append(voteBitsStake, vb) } } newNode := newBlockNode(blockHeader, block.Sha(), blockHeight, voteBitsStake) if prevNode != nil { newNode.parent = prevNode newNode.height = blockHeight newNode.workSum.Add(prevNode.workSum, newNode.workSum) } // Connect the passed block to the chain while respecting proper chain // selection according to the chain with the most proof of work. This // also handles validation of the transaction scripts. var onMainChain bool onMainChain, err = b.connectBestChain(newNode, block, flags) if err != nil { return false, err } // Notify the caller that the new block was accepted into the block // chain. The caller would typically want to react by relaying the // inventory to other peers. if !dryRun { b.sendNotification(NTBlockAccepted, &BlockAcceptedNtfnsData{onMainChain, block}) } return onMainChain, nil }
// ConnectBlock is invoked by the index manager when a new block has been // connected to the main chain. This indexer adds a key for each address // the transactions in the block involve. // // This is part of the Indexer interface. func (idx *ExistsAddrIndex) ConnectBlock(dbTx database.Tx, block, parent *dcrutil.Block, view *blockchain.UtxoViewpoint) error { regularTxTreeValid := dcrutil.IsFlagSet16(block.MsgBlock().Header.VoteBits, dcrutil.BlockValid) var parentTxs []*dcrutil.Tx if regularTxTreeValid && block.Height() > 1 { parentTxs = parent.Transactions() } blockTxns := block.STransactions() allTxns := append(parentTxs, blockTxns...) usedAddrs := make(map[[addrKeySize]byte]struct{}) for _, tx := range allTxns { msgTx := tx.MsgTx() isSStx, _ := stake.IsSStx(msgTx) for _, txIn := range msgTx.TxIn { if txscript.IsMultisigSigScript(txIn.SignatureScript) { rs, err := txscript.MultisigRedeemScriptFromScriptSig( txIn.SignatureScript) if err != nil { continue } class, addrs, _, err := txscript.ExtractPkScriptAddrs( txscript.DefaultScriptVersion, rs, idx.chainParams) if err != nil { // Non-standard outputs are skipped. continue } if class != txscript.MultiSigTy { // This should never happen, but be paranoid. continue } for _, addr := range addrs { k, err := addrToKey(addr, idx.chainParams) if err != nil { continue } usedAddrs[k] = struct{}{} } } } for _, txOut := range tx.MsgTx().TxOut { class, addrs, _, err := txscript.ExtractPkScriptAddrs( txOut.Version, txOut.PkScript, idx.chainParams) if err != nil { // Non-standard outputs are skipped. continue } if isSStx && class == txscript.NullDataTy { addr, err := stake.AddrFromSStxPkScrCommitment(txOut.PkScript, idx.chainParams) if err != nil { // Ignore unsupported address types. continue } addrs = append(addrs, addr) } for _, addr := range addrs { k, err := addrToKey(addr, idx.chainParams) if err != nil { // Ignore unsupported address types. continue } usedAddrs[k] = struct{}{} } } } // Write all the newly used addresses to the database, // skipping any keys that already exist. Write any // addresses we see in mempool at this time, too, // then remove them from the unconfirmed map drop // dropping the old map and reassigning a new map. idx.unconfirmedLock.Lock() for k := range idx.mpExistsAddr { usedAddrs[k] = struct{}{} } idx.mpExistsAddr = make(map[[addrKeySize]byte]struct{}) idx.unconfirmedLock.Unlock() meta := dbTx.Metadata() existsAddrIndex := meta.Bucket(existsAddrIndexKey) newUsedAddrs := make(map[[addrKeySize]byte]struct{}) for k := range usedAddrs { if !idx.existsAddress(existsAddrIndex, k) { newUsedAddrs[k] = struct{}{} } } for k := range newUsedAddrs { err := dbPutExistsAddr(existsAddrIndex, k) if err != nil { return err } } return nil }
// fetchInputTransactions fetches the input transactions referenced by the // transactions in the given block from its point of view. See fetchTxList // for more details on what the point of view entails. // Decred: This function is for verifying the validity of the regular tx tree in // this block for the case that it does get accepted in the next block. func (b *BlockChain) fetchInputTransactions(node *blockNode, block *dcrutil.Block, viewpoint int8) (TxStore, error) { // Verify we have the same node as we do block. blockHash := block.Sha() if !node.hash.IsEqual(blockHash) { return nil, fmt.Errorf("node and block hash are different!") } // If we need the previous block, grab it. var parentBlock *dcrutil.Block if viewpoint == ViewpointPrevValidInitial || viewpoint == ViewpointPrevValidStake || viewpoint == ViewpointPrevValidRegular { var errFetchBlock error parentBlock, errFetchBlock = b.getBlockFromHash(node.parentHash) if errFetchBlock != nil { return nil, errFetchBlock } } txInFlight := map[chainhash.Hash]int{} txNeededSet := make(map[chainhash.Hash]struct{}) txStore := make(TxStore) // Case 1: ViewpointPrevValidInitial. We need the viewpoint of the // current chain without the TxTreeRegular of the previous block // added so we can validate that. if viewpoint == ViewpointPrevValidInitial { // 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. transactions := parentBlock.Transactions() for i, tx := range transactions { txInFlight[*tx.Sha()] = i } // Loop through all of the transaction inputs (except for the coinbase // which has no inputs) collecting them into sets of what is needed and // what is already known (in-flight). for i, tx := range transactions[1:] { for _, txIn := range tx.MsgTx().TxIn { // Add an entry to the transaction store for the needed // transaction with it set to missing by default. originHash := &txIn.PreviousOutPoint.Hash txD := &TxData{Hash: originHash, Err: database.ErrTxShaMissing} txStore[*originHash] = txD // 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. Update the transaction // store acccordingly when this is the case. Otherwise, // we still need the transaction. // // NOTE: The >= is correct here because i is one less // than the actual position of the transaction within // the block due to skipping the coinbase. if inFlightIndex, ok := txInFlight[*originHash]; ok && i >= inFlightIndex { originTx := transactions[inFlightIndex] txD.Tx = originTx txD.BlockHeight = node.height - 1 txD.BlockIndex = uint32(inFlightIndex) txD.Spent = make([]bool, len(originTx.MsgTx().TxOut)) txD.Err = nil } else { txNeededSet[*originHash] = struct{}{} } } } // Request the input transactions from the point of view of the node. txNeededStore, err := b.fetchTxStore(node, block, txNeededSet, viewpoint) if err != nil { return nil, err } // Merge the results of the requested transactions and the in-flight // transactions. for _, txD := range txNeededStore { txStore[*txD.Hash] = txD } return txStore, nil } // Case 2+3: ViewpointPrevValidStake and ViewpointPrevValidStake. // For ViewpointPrevValidStake, we need the viewpoint of the // current chain with the TxTreeRegular of the previous block // added so we can validate the TxTreeStake of the current block. // For ViewpointPrevInvalidStake, we need the viewpoint of the // current chain with the TxTreeRegular of the previous block // missing so we can validate the TxTreeStake of the current block. if viewpoint == ViewpointPrevValidStake || viewpoint == ViewpointPrevInvalidStake { // We need all of the stake tx txins. None of these are considered // in-flight in relation to the regular tx tree or to other tx in // the stake tx tree, so don't do any of those expensive checks and // just append it to the tx slice. stransactions := block.STransactions() for _, tx := range stransactions { isSSGen, _ := stake.IsSSGen(tx) for i, txIn := range tx.MsgTx().TxIn { // Ignore stakebases. if isSSGen && i == 0 { continue } // Add an entry to the transaction store for the needed // transaction with it set to missing by default. originHash := &txIn.PreviousOutPoint.Hash txD := &TxData{Hash: originHash, Err: database.ErrTxShaMissing} txStore[*originHash] = txD txNeededSet[*originHash] = struct{}{} } } // Request the input transactions from the point of view of the node. txNeededStore, err := b.fetchTxStore(node, block, txNeededSet, viewpoint) if err != nil { return nil, err } return txNeededStore, nil } // Case 4+5: ViewpointPrevValidRegular and // ViewpointPrevInvalidRegular. // For ViewpointPrevValidRegular, we need the viewpoint of the // current chain with the TxTreeRegular of the previous block // and the TxTreeStake of the current block added so we can // validate the TxTreeRegular of the current block. // For ViewpointPrevInvalidRegular, we need the viewpoint of the // current chain with the TxTreeRegular of the previous block // missing and the TxTreeStake of the current block added so we // can validate the TxTreeRegular of the current block. if viewpoint == ViewpointPrevValidRegular || viewpoint == ViewpointPrevInvalidRegular { transactions := block.Transactions() for i, tx := range transactions { txInFlight[*tx.Sha()] = i } // Loop through all of the transaction inputs (except for the coinbase // which has no inputs) collecting them into sets of what is needed and // what is already known (in-flight). txNeededSet := make(map[chainhash.Hash]struct{}) txStore = make(TxStore) for i, tx := range transactions[1:] { for _, txIn := range tx.MsgTx().TxIn { // Add an entry to the transaction store for the needed // transaction with it set to missing by default. originHash := &txIn.PreviousOutPoint.Hash txD := &TxData{Hash: originHash, Err: database.ErrTxShaMissing} txStore[*originHash] = txD // 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. Update the transaction // store acccordingly when this is the case. Otherwise, // we still need the transaction. // // NOTE: The >= is correct here because i is one less // than the actual position of the transaction within // the block due to skipping the coinbase. if inFlightIndex, ok := txInFlight[*originHash]; ok && i >= inFlightIndex { originTx := transactions[inFlightIndex] txD.Tx = originTx txD.BlockHeight = node.height txD.BlockIndex = uint32(inFlightIndex) txD.Spent = make([]bool, len(originTx.MsgTx().TxOut)) txD.Err = nil } else { txNeededSet[*originHash] = struct{}{} } } } // Request the input transactions from the point of view of the node. txNeededStore, err := b.fetchTxStore(node, block, txNeededSet, viewpoint) if err != nil { return nil, err } // Merge the results of the requested transactions and the in-flight // transactions. for _, txD := range txNeededStore { txStore[*txD.Hash] = txD } return txStore, nil } return nil, fmt.Errorf("Invalid viewpoint passed to fetchInputTransactions") }
func connectTransactions(txStore TxStore, block *dcrutil.Block, parent *dcrutil.Block) error { // There is no regular tx from before the genesis block, so ignore the genesis // block for the next step. if parent != nil && block.Height() != 0 { mBlock := block.MsgBlock() votebits := mBlock.Header.VoteBits regularTxTreeValid := dcrutil.IsFlagSet16(votebits, dcrutil.BlockValid) // Only add the transactions in the event that the parent block's regular // tx were validated. if regularTxTreeValid { // Loop through all of the regular 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 parent.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() - 1 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 } } } } } // Loop through all of the stake 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 block.STransactions() { // 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 nil }
// disconnectTransactions updates the passed map by undoing transaction and // spend information for all transactions in the passed block. Only // transactions in the passed map are updated. func disconnectTransactions(txStore TxStore, block *dcrutil.Block, parent *dcrutil.Block) error { // Loop through all of the stake transactions in the block to see if any of // them are ones that need to be undone based on the transaction store. for _, tx := range block.STransactions() { // Clear this transaction from the transaction store if needed. // Only clear it rather than deleting it because the transaction // connect code relies on its presence to decide whether or not // to update the store and any transactions which exist on both // sides of a fork would otherwise not be updated. if txD, exists := txStore[*tx.Sha()]; exists { txD.Tx = nil txD.BlockHeight = int64(wire.NullBlockHeight) txD.BlockIndex = wire.NullBlockIndex txD.Spent = nil txD.Err = database.ErrTxShaMissing } // Unspend the origin transaction output. for _, txIn := range tx.MsgTx().TxIn { originHash := &txIn.PreviousOutPoint.Hash originIndex := txIn.PreviousOutPoint.Index originTx, exists := txStore[*originHash] if exists && originTx.Tx != nil && originTx.Err == nil { if originTx.Spent == nil { continue } if originIndex >= uint32(len(originTx.Spent)) { continue } originTx.Spent[originIndex] = false } } } // There is no regular tx from before the genesis block, so ignore the genesis // block for the next step. if parent != nil && block.Height() != 0 { mBlock := block.MsgBlock() votebits := mBlock.Header.VoteBits regularTxTreeValid := dcrutil.IsFlagSet16(votebits, dcrutil.BlockValid) // Only bother to unspend transactions if the parent's tx tree was // validated. Otherwise, these transactions were never in the blockchain's // history in the first place. if regularTxTreeValid { // Loop through all of the regular transactions in the block to see if // any of them are ones that need to be undone based on the // transaction store. for _, tx := range parent.Transactions() { // Clear this transaction from the transaction store if needed. // Only clear it rather than deleting it because the transaction // connect code relies on its presence to decide whether or not // to update the store and any transactions which exist on both // sides of a fork would otherwise not be updated. if txD, exists := txStore[*tx.Sha()]; exists { txD.Tx = nil txD.BlockHeight = int64(wire.NullBlockHeight) txD.BlockIndex = wire.NullBlockIndex txD.Spent = nil txD.Err = database.ErrTxShaMissing } // Unspend the origin transaction output. for _, txIn := range tx.MsgTx().TxIn { originHash := &txIn.PreviousOutPoint.Hash originIndex := txIn.PreviousOutPoint.Index originTx, exists := txStore[*originHash] if exists && originTx.Tx != nil && originTx.Err == nil { if originTx.Spent == nil { continue } if originIndex >= uint32(len(originTx.Spent)) { continue } originTx.Spent[originIndex] = false } } } } } 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 }
// indexBlock extract all of the standard addresses from all of the transactions // in the passed block and maps each of them to the assocaited transaction using // the passed map. func (idx *AddrIndex) indexBlock(data writeIndexData, block, parent *dcrutil.Block, view *blockchain.UtxoViewpoint) { regularTxTreeValid := dcrutil.IsFlagSet16(block.MsgBlock().Header.VoteBits, dcrutil.BlockValid) var stakeStartIdx int if regularTxTreeValid { for txIdx, tx := range parent.Transactions() { // Coinbases do not reference any inputs. Since the block is // required to have already gone through full validation, it has // already been proven on the first transaction in the block is // a coinbase. if txIdx != 0 { for _, txIn := range tx.MsgTx().TxIn { // The view should always have the input since // the index contract requires it, however, be // safe and simply ignore any missing entries. origin := &txIn.PreviousOutPoint entry := view.LookupEntry(&origin.Hash) if entry == nil { log.Warnf("Missing input %v for tx %v while "+ "indexing block %v (height %v)\n", origin.Hash, tx.Sha(), block.Sha(), block.Height()) continue } version := entry.ScriptVersionByIndex(origin.Index) pkScript := entry.PkScriptByIndex(origin.Index) txType := entry.TransactionType() idx.indexPkScript(data, version, pkScript, txIdx, txType == stake.TxTypeSStx) } } for _, txOut := range tx.MsgTx().TxOut { idx.indexPkScript(data, txOut.Version, txOut.PkScript, txIdx, false) } } stakeStartIdx = len(parent.Transactions()) } for txIdx, tx := range block.STransactions() { msgTx := tx.MsgTx() thisTxOffset := txIdx + stakeStartIdx isSSGen, _ := stake.IsSSGen(msgTx) for i, txIn := range msgTx.TxIn { // Skip stakebases. if isSSGen && i == 0 { continue } // The view should always have the input since // the index contract requires it, however, be // safe and simply ignore any missing entries. origin := &txIn.PreviousOutPoint entry := view.LookupEntry(&origin.Hash) if entry == nil { log.Warnf("Missing input %v for tx %v while "+ "indexing block %v (height %v)\n", origin.Hash, tx.Sha(), block.Sha(), block.Height()) continue } version := entry.ScriptVersionByIndex(origin.Index) pkScript := entry.PkScriptByIndex(origin.Index) txType := entry.TransactionType() idx.indexPkScript(data, version, pkScript, thisTxOffset, txType == stake.TxTypeSStx) } isSStx, _ := stake.IsSStx(msgTx) for _, txOut := range msgTx.TxOut { idx.indexPkScript(data, txOut.Version, txOut.PkScript, thisTxOffset, isSStx) } } }