// connectTransactions updates the passed map by applying transaction and // spend information for all the transactions in the passed block. Only // transactions in the passed map are updated. func connectTransactions(txStore TxStore, block *btcutil.Block) error { // 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 _, tx := range block.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.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 originIndex > uint32(len(originTx.Spent)) { continue } originTx.Spent[originIndex] = true } } } return nil }
// 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 *btcutil.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, view); err != nil { return err } // Update the current index tip. prevHash := &block.MsgBlock().Header.PrevBlock return dbPutIndexerTip(dbTx, idxKey, prevHash, block.Height()-1) }
// 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 *btcutil.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 }
// createBlock creates a new block building from the previous block. func createBlock(prevBlock *btcutil.Block, inclusionTxs []*btcutil.Tx, blockVersion int32, blockTime time.Time, miningAddr btcutil.Address, net *chaincfg.Params) (*btcutil.Block, error) { prevHash := prevBlock.Hash() blockHeight := prevBlock.Height() + 1 // If a target block time was specified, then use that as the header's // timestamp. Otherwise, add one second to the previous block unless // it's the genesis block in which case use the current time. var ts time.Time switch { case !blockTime.IsZero(): ts = blockTime default: ts = prevBlock.MsgBlock().Header.Timestamp.Add(time.Second) } extraNonce := uint64(0) coinbaseScript, err := standardCoinbaseScript(blockHeight, extraNonce) if err != nil { return nil, err } coinbaseTx, err := createCoinbaseTx(coinbaseScript, blockHeight, miningAddr, net) if err != nil { return nil, err } // Create a new block ready to be solved. blockTxns := []*btcutil.Tx{coinbaseTx} if inclusionTxs != nil { blockTxns = append(blockTxns, inclusionTxs...) } merkles := blockchain.BuildMerkleTreeStore(blockTxns) var block wire.MsgBlock block.Header = wire.BlockHeader{ Version: blockVersion, PrevBlock: *prevHash, MerkleRoot: *merkles[len(merkles)-1], Timestamp: ts, Bits: net.PowLimitBits, } for _, tx := range blockTxns { if err := block.AddTransaction(tx.MsgTx()); err != nil { return nil, err } } found := solveBlock(&block.Header, net.PowLimit) if !found { return nil, errors.New("Unable to solve block") } utilBlock := btcutil.NewBlock(&block) utilBlock.SetHeight(blockHeight) return utilBlock, nil }
// CheckConnectBlock performs several checks to confirm connecting the passed // block to the main chain does not violate any rules. An example of some of // the checks performed are ensuring connecting the block would not cause any // duplicate transaction hashes for old transactions that aren't already fully // spent, double spends, exceeding the maximum allowed signature operations // per block, invalid values in relation to the expected block subsidy, or fail // transaction script validation. // // This function is NOT safe for concurrent access. func (b *BlockChain) CheckConnectBlock(block *btcutil.Block) error { prevNode := b.bestChain newNode := newBlockNode(&block.MsgBlock().Header, block.Sha(), block.Height()) if prevNode != nil { newNode.parent = prevNode newNode.workSum.Add(prevNode.workSum, newNode.workSum) } return b.checkConnectBlock(newNode, block) }
// 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 (view *UtxoViewpoint) connectTransactions(block *btcutil.Block, stxos *[]spentTxOut) error { for _, tx := range block.Transactions() { err := view.connectTransaction(tx, block.Height(), 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 }
// fetchInputUtxos loads utxo details about the input transactions referenced // by the transactions in the given block into the view from the database as // needed. In particular, referenced entries that are earlier in the block are // added to the view and entries that are already in the view are not modified. func (view *UtxoViewpoint) fetchInputUtxos(db database.DB, block *btcutil.Block) error { // 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. txInFlight := map[wire.ShaHash]int{} 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[wire.ShaHash]struct{}) for i, tx := range transactions[1:] { for _, txIn := range tx.MsgTx().TxIn { // 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. Add the outputs of the // referenced transaction as available utxos when this // is the case. Otherwise, the utxo details are still // needed. // // 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. originHash := &txIn.PreviousOutPoint.Hash if inFlightIndex, ok := txInFlight[*originHash]; ok && i >= inFlightIndex { originTx := transactions[inFlightIndex] view.AddTxOuts(originTx, block.Height()) continue } // Don't request entries that are already in the view // from the database. if _, ok := view.entries[*originHash]; ok { continue } txNeededSet[*originHash] = struct{}{} } } // Request the input utxos from the database. return view.fetchUtxosMain(db, txNeededSet) }
// 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 *btcutil.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, view); err != nil { return err } // Update the current index tip. return dbPutIndexerTip(dbTx, idxKey, block.Sha(), block.Height()) }
// 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. func (view *UtxoViewpoint) disconnectTransactions(block *btcutil.Block, stxos []spentTxOut) error { // Sanity check the correct number of stxos are provided. if len(stxos) != countSpentOutputs(block) { return AssertError("disconnectTransactions called with bad " + "spent transaction out information") } // 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. stxoIdx := len(stxos) - 1 transactions := block.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, isCoinbase, block.Height()) 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 { entry = newUtxoEntry(stxo.version, stxo.isCoinBase, stxo.height) 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{ spent: false, compressed: stxo.compressed, amount: stxo.amount, 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(&block.MsgBlock().Header.PrevBlock) return nil }
// IsCheckpointCandidate returns whether or not the passed block is a good // checkpoint candidate. // // The factors used to determine a good checkpoint are: // - The block must be in the main chain // - The block must be at least 'CheckpointConfirmations' blocks prior to the // current end of the main chain // - The timestamps for the blocks before and after the checkpoint must have // timestamps which are also before and after the checkpoint, respectively // (due to the median time allowance this is not always the case) // - The block must not contain any strange transaction such as those with // nonstandard scripts // // The intent is that candidates are reviewed by a developer to make the final // decision and then manually added to the list of checkpoints for a network. func (b *BlockChain) IsCheckpointCandidate(block *btcutil.Block) (bool, error) { // Checkpoints must be enabled. if b.noCheckpoints { return false, fmt.Errorf("checkpoints are disabled") } // A checkpoint must be in the main chain. exists, err := b.db.ExistsSha(block.Sha()) if err != nil { return false, err } if !exists { return false, nil } // A checkpoint must be at least CheckpointConfirmations blocks before // the end of the main chain. blockHeight := block.Height() _, mainChainHeight, err := b.db.NewestSha() if err != nil { return false, err } if blockHeight > (mainChainHeight - CheckpointConfirmations) { return false, nil } // Get the previous block. prevHash := &block.MsgBlock().Header.PrevBlock prevBlock, err := b.db.FetchBlockBySha(prevHash) if err != nil { return false, err } // Get the next block. nextHash, err := b.db.FetchBlockShaByHeight(blockHeight + 1) if err != nil { return false, err } nextBlock, err := b.db.FetchBlockBySha(nextHash) if err != nil { return false, err } // A checkpoint must have timestamps for the block and the blocks on // either side of it in order (due to the median time allowance this is // not always the case). prevTime := prevBlock.MsgBlock().Header.Timestamp curTime := block.MsgBlock().Header.Timestamp nextTime := nextBlock.MsgBlock().Header.Timestamp if prevTime.After(curTime) || nextTime.Before(curTime) { return false, nil } // A checkpoint must have transactions that only contain standard // scripts. for _, tx := range block.Transactions() { if isNonstandardTransaction(tx) { return false, nil } } return true, nil }
// IsCheckpointCandidate returns whether or not the passed block is a good // checkpoint candidate. // // The factors used to determine a good checkpoint are: // - The block must be in the main chain // - The block must be at least 'CheckpointConfirmations' blocks prior to the // current end of the main chain // - The timestamps for the blocks before and after the checkpoint must have // timestamps which are also before and after the checkpoint, respectively // (due to the median time allowance this is not always the case) // - The block must not contain any strange transaction such as those with // nonstandard scripts // // The intent is that candidates are reviewed by a developer to make the final // decision and then manually added to the list of checkpoints for a network. // // This function is safe for concurrent access. func (b *BlockChain) IsCheckpointCandidate(block *btcutil.Block) (bool, error) { b.chainLock.RLock() defer b.chainLock.RUnlock() // Checkpoints must be enabled. if b.noCheckpoints { return false, fmt.Errorf("checkpoints are disabled") } var isCandidate bool err := b.db.View(func(dbTx database.Tx) error { // A checkpoint must be in the main chain. blockHeight, err := dbFetchHeightByHash(dbTx, block.Sha()) if err != nil { // Only return an error if it's not due to the block not // being in the main chain. if !isNotInMainChainErr(err) { return err } return nil } // Ensure the height of the passed block and the entry for the // block in the main chain match. This should always be the // case unless the caller provided an invalid block. if blockHeight != block.Height() { return fmt.Errorf("passed block height of %d does not "+ "match the main chain height of %d", block.Height(), blockHeight) } // A checkpoint must be at least CheckpointConfirmations blocks // before the end of the main chain. mainChainHeight := b.bestNode.height if blockHeight > (mainChainHeight - CheckpointConfirmations) { return nil } // Get the previous block header. prevHash := &block.MsgBlock().Header.PrevBlock prevHeader, err := dbFetchHeaderByHash(dbTx, prevHash) if err != nil { return err } // Get the next block header. nextHeader, err := dbFetchHeaderByHeight(dbTx, blockHeight+1) if err != nil { return err } // A checkpoint must have timestamps for the block and the // blocks on either side of it in order (due to the median time // allowance this is not always the case). prevTime := prevHeader.Timestamp curTime := block.MsgBlock().Header.Timestamp nextTime := nextHeader.Timestamp if prevTime.After(curTime) || nextTime.Before(curTime) { return nil } // A checkpoint must have transactions that only contain // standard scripts. for _, tx := range block.Transactions() { if isNonstandardTransaction(tx) { return nil } } // All of the checks passed, so the block is a candidate. isCandidate = true return nil }) return isCandidate, err }
// 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. // // The flags are also passed to checkBlockContext and connectBestChain. See // their documentation for how the flags modify their behavior. func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, flags BehaviorFlags) 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.Errorf("getPrevNodeFromBlock: %v", err) return err } // The height of this block is one more than the referenced previous // block. blockHeight := int32(0) if prevNode != nil { blockHeight = prevNode.height + 1 } if blockHeight == 0 { blockHeight = block.Height() } newestSha, newestHeight, _ := b.db.NewestSha() latestCheckpoint := b.LatestCheckpoint() log.Infof("SHA: %v | Height: %v | Diff: %v", newestSha, newestHeight, newestHeight-latestCheckpoint.Height) pastSha, _ := b.db.FetchBlockShaByHeight(newestHeight - b.chainParams.PruneBlockBufferSize) if pastSha != nil { log.Infof("Dropping old block: %v", pastSha) b.db.DropBlockBySha(pastSha) } // 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 err } // Prune block nodes which are no longer needed before creating // a new node. if !dryRun { err = b.pruneBlockNodes() if err != nil { return 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 newNode := newBlockNode(blockHeader, block.Sha(), blockHeight) 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. err = b.connectBestChain(newNode, block, flags) if err != nil { return 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, block) } return nil }