lbcd/blockchain/blockindex.go

352 lines
11 KiB
Go
Raw Permalink Normal View History

// 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/lbryio/lbcd/chaincfg"
"github.com/lbryio/lbcd/chaincfg/chainhash"
"github.com/lbryio/lbcd/database"
"github.com/lbryio/lbcd/wire"
)
// blockStatus is a bit field representing the validation state of the block.
type blockStatus byte
const (
// statusDataStored indicates that the block's payload is stored on disk.
statusDataStored blockStatus = 1 << iota
// statusValid indicates that the block has been fully validated.
statusValid
// statusValidateFailed indicates that the block has failed validation.
statusValidateFailed
// statusInvalidAncestor indicates that one of the block's ancestors has
// has failed validation, thus the block is also invalid.
statusInvalidAncestor
// statusNone indicates that the block has no validation state flags set.
//
// NOTE: This must be defined last in order to avoid influencing iota.
statusNone blockStatus = 0
)
// HaveData returns whether the full block data is stored in the database. This
// will return false for a block node where only the header is downloaded or
// kept.
func (status blockStatus) HaveData() bool {
return status&statusDataStored != 0
}
// KnownValid returns whether the block is known to be valid. This will return
// false for a valid block that has not been fully validated yet.
func (status blockStatus) KnownValid() bool {
return status&statusValid != 0
}
// KnownInvalid returns whether the block is known to be invalid. This may be
// because the block itself failed validation or any of its ancestors is
// invalid. This will return false for invalid blocks that have not been proven
// invalid yet.
func (status blockStatus) KnownInvalid() bool {
return status&(statusValidateFailed|statusInvalidAncestor) != 0
}
// 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 {
blockchain: Convert to full block index in mem. 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
2017-02-03 19:13:53 +01:00
// 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
blockchain: Convert to full block index in mem. 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
2017-02-03 19:13:53 +01:00
// height is the position in the block chain.
height int32
// 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
claimTrie chainhash.Hash
// status is a bitfield representing the validation state of the block. The
// status field, unlike the other fields, may be written to and so should
// only be accessed using the concurrent-safe NodeStatus method on
// blockIndex once the node has been added to the global index.
status blockStatus
}
// initBlockNode initializes a block node from the given header and parent node,
// calculating the height and workSum from the respective fields on the parent.
blockchain: Convert to full block index in mem. 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
2017-02-03 19:13:53 +01:00
// 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, parent *blockNode) {
blockchain: Convert to full block index in mem. 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
2017-02-03 19:13:53 +01:00
*node = blockNode{
hash: blockHeader.BlockHash(),
workSum: CalcWork(blockHeader.Bits),
version: blockHeader.Version,
bits: blockHeader.Bits,
nonce: blockHeader.Nonce,
timestamp: blockHeader.Timestamp.Unix(),
merkleRoot: blockHeader.MerkleRoot,
claimTrie: blockHeader.ClaimTrie,
}
if parent != nil {
node.parent = parent
node.height = parent.height + 1
node.workSum = node.workSum.Add(parent.workSum, node.workSum)
}
blockchain: Convert to full block index in mem. 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
2017-02-03 19:13:53 +01:00
}
// newBlockNode returns a new block node for the given block header and parent
// node, calculating the height and workSum from the respective fields on the
// parent. This function is NOT safe for concurrent access.
func newBlockNode(blockHeader *wire.BlockHeader, parent *blockNode) *blockNode {
blockchain: Convert to full block index in mem. 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
2017-02-03 19:13:53 +01:00
var node blockNode
initBlockNode(&node, blockHeader, parent)
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
blockchain: Convert to full block index in mem. 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
2017-02-03 19:13:53 +01:00
if node.parent != nil {
prevHash = &node.parent.hash
}
return wire.BlockHeader{
Version: node.version,
blockchain: Convert to full block index in mem. 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
2017-02-03 19:13:53 +01:00
PrevBlock: *prevHash,
MerkleRoot: node.merkleRoot,
ClaimTrie: node.claimTrie,
Timestamp: time.Unix(node.timestamp, 0),
Bits: node.bits,
Nonce: node.nonce,
}
}
blockchain: Convert to full block index in mem. 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
2017-02-03 19:13:53 +01:00
// 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.
//
blockchain: Convert to full block index in mem. 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
2017-02-03 19:13:53 +01:00
// This function is safe for concurrent access.
func (node *blockNode) Ancestor(height int32) *blockNode {
if height < 0 || height > node.height {
return nil
}
blockchain: Convert to full block index in mem. 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
2017-02-03 19:13:53 +01:00
n := node
for ; n != nil && n.height != height; n = n.parent {
// Intentionally left blank
}
blockchain: Convert to full block index in mem. 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
2017-02-03 19:13:53 +01:00
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
blockchain: Convert to full block index in mem. 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
2017-02-03 19:13:53 +01:00
// 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
blockchain: Convert to full block index in mem. 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
2017-02-03 19:13:53 +01:00
index map[chainhash.Hash]*blockNode
dirty map[*blockNode]struct{}
}
// 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),
dirty: make(map[*blockNode]struct{}),
}
}
// 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 and marks it as dirty.
// Duplicate entries are not checked so it is up to caller to avoid adding them.
//
// This function is safe for concurrent access.
blockchain: Convert to full block index in mem. 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
2017-02-03 19:13:53 +01:00
func (bi *blockIndex) AddNode(node *blockNode) {
bi.Lock()
bi.addNode(node)
bi.dirty[node] = struct{}{}
bi.Unlock()
}
// addNode adds the provided node to the block index, but does not mark it as
// dirty. This can be used while initializing the block index.
//
// This function is NOT safe for concurrent access.
func (bi *blockIndex) addNode(node *blockNode) {
bi.index[node.hash] = node
}
// NodeStatus provides concurrent-safe access to the status field of a node.
//
// This function is safe for concurrent access.
func (bi *blockIndex) NodeStatus(node *blockNode) blockStatus {
bi.RLock()
status := node.status
bi.RUnlock()
return status
}
// SetStatusFlags flips the provided status flags on the block node to on,
// regardless of whether they were on or off previously. This does not unset any
// flags currently on.
//
// This function is safe for concurrent access.
func (bi *blockIndex) SetStatusFlags(node *blockNode, flags blockStatus) {
bi.Lock()
node.status |= flags
bi.dirty[node] = struct{}{}
bi.Unlock()
}
// UnsetStatusFlags flips the provided status flags on the block node to off,
// regardless of whether they were on or off previously.
//
// This function is safe for concurrent access.
func (bi *blockIndex) UnsetStatusFlags(node *blockNode, flags blockStatus) {
bi.Lock()
node.status &^= flags
bi.dirty[node] = struct{}{}
bi.Unlock()
}
// flushToDB writes all dirty block nodes to the database. If all writes
// succeed, this clears the dirty set.
func (bi *blockIndex) flushToDB() error {
bi.Lock()
if len(bi.dirty) == 0 {
bi.Unlock()
return nil
}
err := bi.db.Update(func(dbTx database.Tx) error {
for node := range bi.dirty {
err := dbStoreBlockNode(dbTx, node)
if err != nil {
return err
}
}
return nil
})
// If write was successful, clear the dirty set.
if err == nil {
bi.dirty = make(map[*blockNode]struct{})
}
bi.Unlock()
return err
}