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
231 lines
7.8 KiB
Go
231 lines
7.8 KiB
Go
// Copyright (c) 2015-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 (
|
|
"math/big"
|
|
"sort"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/btcsuite/btcd/chaincfg"
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
"github.com/btcsuite/btcd/database"
|
|
"github.com/btcsuite/btcd/wire"
|
|
)
|
|
|
|
// blockNode represents a block within the block chain and is primarily used to
|
|
// aid in selecting the best chain to be the main chain. The main chain is
|
|
// stored into the block database.
|
|
type blockNode struct {
|
|
// NOTE: Additions, deletions, or modifications to the order of the
|
|
// definitions in this struct should not be changed without considering
|
|
// how it affects alignment on 64-bit platforms. The current order is
|
|
// specifically crafted to result in minimal padding. There will be
|
|
// hundreds of thousands of these in memory, so a few extra bytes of
|
|
// padding adds up.
|
|
|
|
// parent is the parent block for this node.
|
|
parent *blockNode
|
|
|
|
// hash is the double sha 256 of the block.
|
|
hash chainhash.Hash
|
|
|
|
// workSum is the total amount of work in the chain up to and including
|
|
// this node.
|
|
workSum *big.Int
|
|
|
|
// height is the position in the block chain.
|
|
height int32
|
|
|
|
// inMainChain denotes whether the block node is currently on the
|
|
// the main chain or not. This is used to help find the common
|
|
// ancestor when switching chains.
|
|
inMainChain bool
|
|
|
|
// Some fields from block headers to aid in best chain selection and
|
|
// reconstructing headers from memory. These must be treated as
|
|
// immutable and are intentionally ordered to avoid padding on 64-bit
|
|
// platforms.
|
|
version int32
|
|
bits uint32
|
|
nonce uint32
|
|
timestamp int64
|
|
merkleRoot chainhash.Hash
|
|
}
|
|
|
|
// initBlockNode initializes a block node from the given header and height. The
|
|
// node is completely disconnected from the chain and the workSum value is just
|
|
// the work for the passed block. The work sum must be updated accordingly when
|
|
// the node is inserted into a chain.
|
|
//
|
|
// This function is NOT safe for concurrent access. It must only be called when
|
|
// initially creating a node.
|
|
func initBlockNode(node *blockNode, blockHeader *wire.BlockHeader, height int32) {
|
|
*node = blockNode{
|
|
hash: blockHeader.BlockHash(),
|
|
workSum: CalcWork(blockHeader.Bits),
|
|
height: height,
|
|
version: blockHeader.Version,
|
|
bits: blockHeader.Bits,
|
|
nonce: blockHeader.Nonce,
|
|
timestamp: blockHeader.Timestamp.Unix(),
|
|
merkleRoot: blockHeader.MerkleRoot,
|
|
}
|
|
}
|
|
|
|
// newBlockNode returns a new block node for the given block header. It is
|
|
// completely disconnected from the chain and the workSum value is just the work
|
|
// for the passed block. The work sum must be updated accordingly when the node
|
|
// is inserted into a chain.
|
|
func newBlockNode(blockHeader *wire.BlockHeader, height int32) *blockNode {
|
|
var node blockNode
|
|
initBlockNode(&node, blockHeader, height)
|
|
return &node
|
|
}
|
|
|
|
// Header constructs a block header from the node and returns it.
|
|
//
|
|
// This function is safe for concurrent access.
|
|
func (node *blockNode) Header() wire.BlockHeader {
|
|
// No lock is needed because all accessed fields are immutable.
|
|
prevHash := zeroHash
|
|
if node.parent != nil {
|
|
prevHash = &node.parent.hash
|
|
}
|
|
return wire.BlockHeader{
|
|
Version: node.version,
|
|
PrevBlock: *prevHash,
|
|
MerkleRoot: node.merkleRoot,
|
|
Timestamp: time.Unix(node.timestamp, 0),
|
|
Bits: node.bits,
|
|
Nonce: node.nonce,
|
|
}
|
|
}
|
|
|
|
// Ancestor returns the ancestor block node at the provided height by following
|
|
// the chain backwards from this node. The returned block will be nil when a
|
|
// height is requested that is after the height of the passed node or is less
|
|
// than zero.
|
|
//
|
|
// This function is safe for concurrent access.
|
|
func (node *blockNode) Ancestor(height int32) *blockNode {
|
|
if height < 0 || height > node.height {
|
|
return nil
|
|
}
|
|
|
|
n := node
|
|
for ; n != nil && n.height != height; n = n.parent {
|
|
// Intentionally left blank
|
|
}
|
|
|
|
return n
|
|
}
|
|
|
|
// RelativeAncestor returns the ancestor block node a relative 'distance' blocks
|
|
// before this node. This is equivalent to calling Ancestor with the node's
|
|
// height minus provided distance.
|
|
//
|
|
// This function is safe for concurrent access.
|
|
func (node *blockNode) RelativeAncestor(distance int32) *blockNode {
|
|
return node.Ancestor(node.height - distance)
|
|
}
|
|
|
|
// CalcPastMedianTime calculates the median time of the previous few blocks
|
|
// prior to, and including, the block node.
|
|
//
|
|
// This function is safe for concurrent access.
|
|
func (node *blockNode) CalcPastMedianTime() time.Time {
|
|
// Create a slice of the previous few block timestamps used to calculate
|
|
// the median per the number defined by the constant medianTimeBlocks.
|
|
timestamps := make([]int64, medianTimeBlocks)
|
|
numNodes := 0
|
|
iterNode := node
|
|
for i := 0; i < medianTimeBlocks && iterNode != nil; i++ {
|
|
timestamps[i] = iterNode.timestamp
|
|
numNodes++
|
|
|
|
iterNode = iterNode.parent
|
|
}
|
|
|
|
// Prune the slice to the actual number of available timestamps which
|
|
// will be fewer than desired near the beginning of the block chain
|
|
// and sort them.
|
|
timestamps = timestamps[:numNodes]
|
|
sort.Sort(timeSorter(timestamps))
|
|
|
|
// NOTE: The consensus rules incorrectly calculate the median for even
|
|
// numbers of blocks. A true median averages the middle two elements
|
|
// for a set with an even number of elements in it. Since the constant
|
|
// for the previous number of blocks to be used is odd, this is only an
|
|
// issue for a few blocks near the beginning of the chain. I suspect
|
|
// this is an optimization even though the result is slightly wrong for
|
|
// a few of the first blocks since after the first few blocks, there
|
|
// will always be an odd number of blocks in the set per the constant.
|
|
//
|
|
// This code follows suit to ensure the same rules are used, however, be
|
|
// aware that should the medianTimeBlocks constant ever be changed to an
|
|
// even number, this code will be wrong.
|
|
medianTimestamp := timestamps[numNodes/2]
|
|
return time.Unix(medianTimestamp, 0)
|
|
}
|
|
|
|
// blockIndex provides facilities for keeping track of an in-memory index of the
|
|
// block chain. Although the name block chain suggests a single chain of
|
|
// blocks, it is actually a tree-shaped structure where any node can have
|
|
// multiple children. However, there can only be one active branch which does
|
|
// indeed form a chain from the tip all the way back to the genesis block.
|
|
type blockIndex struct {
|
|
// The following fields are set when the instance is created and can't
|
|
// be changed afterwards, so there is no need to protect them with a
|
|
// separate mutex.
|
|
db database.DB
|
|
chainParams *chaincfg.Params
|
|
|
|
sync.RWMutex
|
|
index map[chainhash.Hash]*blockNode
|
|
}
|
|
|
|
// newBlockIndex returns a new empty instance of a block index. The index will
|
|
// be dynamically populated as block nodes are loaded from the database and
|
|
// manually added.
|
|
func newBlockIndex(db database.DB, chainParams *chaincfg.Params) *blockIndex {
|
|
return &blockIndex{
|
|
db: db,
|
|
chainParams: chainParams,
|
|
index: make(map[chainhash.Hash]*blockNode),
|
|
}
|
|
}
|
|
|
|
// HaveBlock returns whether or not the block index contains the provided hash.
|
|
//
|
|
// This function is safe for concurrent access.
|
|
func (bi *blockIndex) HaveBlock(hash *chainhash.Hash) bool {
|
|
bi.RLock()
|
|
_, hasBlock := bi.index[*hash]
|
|
bi.RUnlock()
|
|
return hasBlock
|
|
}
|
|
|
|
// LookupNode returns the block node identified by the provided hash. It will
|
|
// return nil if there is no entry for the hash.
|
|
//
|
|
// This function is safe for concurrent access.
|
|
func (bi *blockIndex) LookupNode(hash *chainhash.Hash) *blockNode {
|
|
bi.RLock()
|
|
node := bi.index[*hash]
|
|
bi.RUnlock()
|
|
return node
|
|
}
|
|
|
|
// AddNode adds the provided node to the block index. Duplicate entries are not
|
|
// checked so it is up to caller to avoid adding them.
|
|
//
|
|
// This function is safe for concurrent access.
|
|
func (bi *blockIndex) AddNode(node *blockNode) {
|
|
bi.Lock()
|
|
bi.index[node.hash] = node
|
|
bi.Unlock()
|
|
}
|