296fa0a5a0
This reworks the block index code such that it loads all of the headers in the main chain at startup and constructs the full block index accordingly. Since the full index from the current best tip all the way back to the genesis block is now guaranteed to be in memory, this also removes all code related to dynamically loading the nodes and updates some of the logic to take advantage of the fact traversing the block index can longer potentially fail. There are also more optimizations and simplifications that can be made in the future as a result of this. Due to removing all of the extra overhead of tracking the dynamic state, and ensuring the block node structs are aligned to eliminate extra padding, the end result of a fully populated block index now takes quite a bit less memory than the previous dynamically loaded version. The main downside is that it now takes a while to start whereas it was nearly instant before, however, it is much better to provide more efficient runtime operation since that is its ultimate purpose and the benefits far outweigh this downside. Some benefits are: - Since every block node is in memory, the recent code which reconstructs headers from block nodes means that all headers can always be served from memory which is important since the majority of the network has moved to header-based semantics - Several of the error paths can be removed since they are no longer necessary - It is no longer expensive to calculate CSV sequence locks or median times of blocks way in the past - It will be possible to create much more efficient iteration and simplified views of the overall index - The entire threshold state database cache can be removed since it is cheap to construct it from the full block index as needed An overview of the logic changes are as follows: - Move AncestorNode from blockIndex to blockNode and greatly simplify since it no longer has to deal with the possibility of dynamically loading nodes and related failures - Rename RelativeNode to RelativeAncestor, move to blockNode, and redefine in terms of AncestorNode - Move CalcPastMedianTime from blockIndex to blockNode and remove no longer necessary test for nil - Change calcSequenceLock to use Ancestor instead of RelativeAncestor since it reads more clearly
89 lines
3.3 KiB
Go
89 lines
3.3 KiB
Go
// Copyright (c) 2013-2017 The btcsuite developers
|
|
// Use of this source code is governed by an ISC
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package blockchain
|
|
|
|
import (
|
|
"github.com/btcsuite/btcd/database"
|
|
"github.com/btcsuite/btcutil"
|
|
)
|
|
|
|
// maybeAcceptBlock potentially accepts a block into the block chain and, if
|
|
// accepted, returns whether or not it is on the main 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.
|
|
//
|
|
// This function MUST be called with the chain state lock held (for writes).
|
|
func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, flags BehaviorFlags) (bool, error) {
|
|
dryRun := flags&BFDryRun == BFDryRun
|
|
|
|
// The height of this block is one more than the referenced previous
|
|
// block.
|
|
blockHeight := int32(0)
|
|
prevNode := b.index.LookupNode(&block.MsgBlock().Header.PrevBlock)
|
|
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
|
|
}
|
|
|
|
// Insert the block into the database if it's not already there. Even
|
|
// though it is possible the block will ultimately fail to connect, it
|
|
// has already passed all proof-of-work and validity tests which means
|
|
// it would be prohibitively expensive for an attacker to fill up the
|
|
// disk with a bunch of blocks that fail to connect. This is necessary
|
|
// since it allows block download to be decoupled from the much more
|
|
// expensive connection logic. It also has some other nice properties
|
|
// such as making blocks that never become part of the main chain or
|
|
// blocks that fail to connect available for further analysis.
|
|
err = b.db.Update(func(dbTx database.Tx) error {
|
|
return dbMaybeStoreBlock(dbTx, block)
|
|
})
|
|
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
|
|
newNode := newBlockNode(blockHeader, 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.
|
|
isMainChain, 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.chainLock.Unlock()
|
|
b.sendNotification(NTBlockAccepted, block)
|
|
b.chainLock.Lock()
|
|
}
|
|
|
|
return isMainChain, nil
|
|
}
|