From fc91d2ccbfbba5c79cc059769030e34b9060efa6 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Sat, 26 May 2018 21:47:45 -0500 Subject: [PATCH] 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 no longer potentially fail. There are also many 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. It also speeds up the initial startup process by roughly 2x since it is faster to bulk load the nodes in order as opposed to dynamically loading only the nodes near the tip in backwards order. For example, here is some startup timing information before and after this commit on a node that contains roughly 238,000 blocks: 7200 RPM HDD: ------------- Startup time before this commit: ~7.71s Startup time after this commit: ~3.47s SSD: ---- Startup time before this commit: ~6.34s Startup time after this commit: ~3.51s Some additional benefits are: - Since every block node is in memory, the code which reconstructs headers from block nodes means that all headers can always be served from memory which will be important since the network will be moving 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 is much less expensive to calculate the initial states for the various intervals such as the stake and voter version - It will be possible to create much more efficient iteration and simplified views of the overall index 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 - Replace nodeAtHeightFromTopNode from BlockChain with RelativeAncestor on blockNode and define it in terms of AncestorNode - Move CalcPastMedianTime from blockIndex to blockNode and remove no longer necessary test for nil - Remove findNode and replace all of its uses with direct queries of the block index - Remove blockExists and replace all of its uses with direct queries of the block index - Remove all functions and fields related to dynamically loading nodes - children and parentHash fields from blockNode - depNodes from blockIndex - loadBlockNode from blockIndex - PrevNodeFromBlock from blockIndex - {p,P}revNodeFromNode from blockIndex - RemoveNode - Replace all instances of iterating backwards through nodes to directly access the parent now that nodes don't potentially need to be dynamically loaded - Introduce a lookupNode function on blockIndex which allows the initialization code to locklessly query the index - No longer take the chain lock when only access to the block index, which has its own lock, is needed - Removed the error paths from several functions that can no longer fail - getReorganizeNodes - findPrevTestNetDifficulty - sumPurchasedTickets - findStakeVersionPriorNode - Removed all error paths related to node iteration that can no longer fail - Modify FetchUtxoView to return an empty view for the genesis block --- blockchain/accept.go | 16 +- blockchain/blockindex.go | 423 ++++++++------------------------ blockchain/blockindex_test.go | 6 +- blockchain/blocklocator.go | 6 - blockchain/chain.go | 216 ++++------------ blockchain/chainio.go | 137 +++++++---- blockchain/difficulty.go | 173 +++---------- blockchain/process.go | 52 +--- blockchain/sequencelock.go | 15 +- blockchain/sequencelock_test.go | 16 +- blockchain/stakeext.go | 28 +-- blockchain/stakenode.go | 38 +-- blockchain/stakeversion.go | 140 ++++------- blockchain/stakeversion_test.go | 6 +- blockchain/thresholdstate.go | 115 ++++----- blockchain/utxoviewpoint.go | 20 +- blockchain/validate.go | 49 ++-- 17 files changed, 431 insertions(+), 1025 deletions(-) diff --git a/blockchain/accept.go b/blockchain/accept.go index 3b2fcd4426..69437bb9bc 100644 --- a/blockchain/accept.go +++ b/blockchain/accept.go @@ -114,18 +114,11 @@ func IsFinalizedTransaction(tx *dcrutil.Tx, blockHeight int64, blockTime time.Ti // // This function MUST be called with the chain state lock held (for writes). func (b *BlockChain) maybeAcceptBlock(block *dcrutil.Block, flags BehaviorFlags) (int64, error) { - // Get a block node for the block previous to this one. Will be nil - // if this is the genesis block. - prevNode, err := b.index.PrevNodeFromBlock(block) - if err != nil { - log.Debugf("PrevNodeFromBlock: %v", err) - return 0, err - } - // This function should never be called with orphan blocks or the // genesis block. + prevHash := &block.MsgBlock().Header.PrevBlock + prevNode := b.index.LookupNode(prevHash) if prevNode == nil { - prevHash := &block.MsgBlock().Header.PrevBlock str := fmt.Sprintf("previous block %s is not known", prevHash) return 0, ruleError(ErrMissingParent, str) } @@ -133,7 +126,6 @@ func (b *BlockChain) maybeAcceptBlock(block *dcrutil.Block, flags BehaviorFlags) // There is no need to validate the block if an ancestor is already // known to be invalid. if b.index.NodeStatus(prevNode).KnownInvalid() { - prevHash := &block.MsgBlock().Header.PrevBlock str := fmt.Sprintf("previous block %s is known to be invalid", prevHash) return 0, ruleError(ErrInvalidAncestorBlock, str) @@ -141,7 +133,7 @@ func (b *BlockChain) maybeAcceptBlock(block *dcrutil.Block, flags BehaviorFlags) // 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) + err := b.checkBlockContext(block, prevNode, flags) if err != nil { return 0, err } @@ -197,7 +189,7 @@ func (b *BlockChain) maybeAcceptBlock(block *dcrutil.Block, flags BehaviorFlags) // Grab the parent block since it is required throughout the block // connection process. - parent, err := b.fetchBlockByHash(&newNode.parentHash) + parent, err := b.fetchBlockByHash(&newNode.parent.hash) if err != nil { return 0, err } diff --git a/blockchain/blockindex.go b/blockchain/blockindex.go index b3bb28ab52..79500d51b0 100644 --- a/blockchain/blockindex.go +++ b/blockchain/blockindex.go @@ -7,7 +7,6 @@ package blockchain import ( "bytes" - "fmt" "math/big" "sort" "sync" @@ -17,7 +16,6 @@ import ( "github.com/decred/dcrd/chaincfg" "github.com/decred/dcrd/chaincfg/chainhash" "github.com/decred/dcrd/database" - "github.com/decred/dcrd/dcrutil" "github.com/decred/dcrd/wire" ) @@ -69,51 +67,40 @@ func (status blockStatus) KnownInvalid() bool { // 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 - // children contains the child nodes for this node. Typically there - // will only be one, but sometimes there can be more than one and that - // is when the best chain selection algorithm is used. - children []*blockNode - // hash is the hash of the block this node represents. hash chainhash.Hash - // parentHash is the hash of the parent block of the block this node - // represents. This is kept here over simply relying on parent.hash - // directly since block nodes are sparse and the parent node might not be - // in memory when its hash is needed. - parentHash chainhash.Hash - - // height is the position in the block chain. - height int64 - // workSum is the total amount of work in the chain up to and including // this node. workSum *big.Int - // 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. - blockVersion int32 + height int64 voteBits uint16 finalState [6]byte + blockVersion int32 voters uint16 freshStake uint8 + revocations uint8 poolSize uint32 bits uint32 sbits int64 timestamp int64 merkleRoot chainhash.Hash stakeRoot chainhash.Hash - revocations uint8 blockSize uint32 nonce uint32 extraData [32]byte @@ -126,6 +113,11 @@ type blockNode struct { // methods on blockIndex once the node has been added to the index. status blockStatus + // 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 + // stakeNode contains all the consensus information required for the // staking system. The node also caches information required to add or // remove stake nodes, so that the stake node itself may be pruneable @@ -151,7 +143,6 @@ type blockNode struct { func initBlockNode(node *blockNode, blockHeader *wire.BlockHeader, parent *blockNode) { *node = blockNode{ hash: blockHeader.BlockHash(), - parentHash: blockHeader.PrevBlock, workSum: CalcWork(blockHeader.Bits), height: int64(blockHeader.Height), blockVersion: blockHeader.Version, @@ -191,9 +182,13 @@ func newBlockNode(blockHeader *wire.BlockHeader, parent *blockNode) *blockNode { // 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.blockVersion, - PrevBlock: node.parentHash, + PrevBlock: *prevHash, MerkleRoot: node.merkleRoot, StakeRoot: node.stakeRoot, VoteBits: node.voteBits, @@ -243,35 +238,78 @@ func (node *blockNode) populateTicketInfo(spentTickets *stake.SpentTicketsInBloc node.votes = spentTickets.Votes } -// removeChildNode deletes node from the provided slice of child block -// nodes. It ensures the final pointer reference is set to nil to prevent -// potential memory leaks. The original slice is returned unmodified if node -// is invalid or not in the slice. +// 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 MUST be called with the chain state lock held (for writes). -func removeChildNode(children []*blockNode, node *blockNode) []*blockNode { - if node == nil { - return children +// This function is safe for concurrent access. +func (node *blockNode) Ancestor(height int64) *blockNode { + if height < 0 || height > node.height { + return nil } - // An indexing for loop is intentionally used over a range here as range - // does not reevaluate the slice on each iteration nor does it adjust - // the index for the modified slice. - for i := 0; i < len(children); i++ { - if children[i].hash == node.hash { - copy(children[i:], children[i+1:]) - children[len(children)-1] = nil - return children[:len(children)-1] - } + n := node + for ; n != nil && n.height != height; n = n.parent { + // Intentionally left blank } - return children + + 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 int64) *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 suggest 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. +// 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 @@ -281,7 +319,6 @@ type blockIndex struct { sync.RWMutex index map[chainhash.Hash]*blockNode - depNodes map[chainhash.Hash][]*blockNode chainTips map[int64][]*blockNode } @@ -293,7 +330,6 @@ func newBlockIndex(db database.DB, chainParams *chaincfg.Params) *blockIndex { db: db, chainParams: chainParams, index: make(map[chainhash.Hash]*blockNode), - depNodes: make(map[chainhash.Hash][]*blockNode), chainTips: make(map[int64][]*blockNode), } } @@ -308,205 +344,12 @@ func (bi *blockIndex) HaveBlock(hash *chainhash.Hash) bool { return hasBlock } -// loadBlockNode loads the block identified by hash from the block database, -// creates a block node from it, and updates the memory block chain accordingly. -// It is used mainly to dynamically load previous blocks from the database as -// they are needed to avoid needing to put the entire block chain in memory. -// -// This function MUST be called with the block index lock held (for writes). -// The database transaction may be read-only. -func (bi *blockIndex) loadBlockNode(dbTx database.Tx, hash *chainhash.Hash) (*blockNode, error) { - // Try to look up the height for passed block hash in the main chain. - height, err := dbFetchHeightByHash(dbTx, hash) - if err != nil { - return nil, err - } - - // Load the block node for the provided hash and height from the - // database. - entry, err := dbFetchBlockIndexEntry(dbTx, hash, uint32(height)) - if err != nil { - return nil, err - } - node := new(blockNode) - initBlockNode(node, &entry.header, nil) - node.ticketsVoted = entry.ticketsVoted - node.ticketsRevoked = entry.ticketsRevoked - node.votes = entry.voteInfo - node.inMainChain = true - - // Add the node to the chain. - // There are a few possibilities here: - // 1) This node is a child of an existing block node - // 2) This node is the parent of one or more nodes - // 3) Neither 1 or 2 is true which implies it's an orphan block and - // therefore is an error to insert into the chain - prevHash := &node.parentHash - if parentNode, ok := bi.index[*prevHash]; ok { - // Case 1 -- This node is a child of an existing block node. - // Update the node's work sum with the sum of the parent node's - // work sum and this node's work, append the node as a child of - // the parent node and set this node's parent to the parent - // node. - node.workSum = node.workSum.Add(parentNode.workSum, node.workSum) - parentNode.children = append(parentNode.children, node) - node.parent = parentNode - - // Also, since this node is extending an existing chain it is a - // new chain tip and the parent is no longer a tip. - bi.addChainTip(node) - bi.removeChainTip(parentNode) - - } else if childNodes, ok := bi.depNodes[*hash]; ok { - // Case 2 -- This node is the parent of one or more nodes. - // Update the node's work sum by subtracting this node's work - // from the sum of its first child, and connect the node to all - // of its children. - node.workSum.Sub(childNodes[0].workSum, node.workSum) - for _, childNode := range childNodes { - childNode.parent = node - node.children = append(node.children, childNode) - } - - } else { - // Case 3 -- The node doesn't have a parent in the node cache - // and is not the parent of another node. This means an arbitrary - // orphan block is trying to be loaded which is not allowed. - str := "loadBlockNode: attempt to insert orphan block %v" - return nil, AssertError(fmt.Sprintf(str, hash)) - } - - // Add the new node to the indices for faster lookups. - bi.index[*hash] = node - bi.depNodes[*prevHash] = append(bi.depNodes[*prevHash], node) - - return node, nil -} - -// PrevNodeFromBlock returns a block node for the block previous to the -// passed block (the passed block's parent). When it is already in the block -// index, it simply returns it. Otherwise, it loads the previous block header -// from the block database, creates a new block node from it, and returns it. -// The returned node will be nil if the genesis block is passed. -// -// This function is safe for concurrent access. -func (bi *blockIndex) PrevNodeFromBlock(block *dcrutil.Block) (*blockNode, error) { - // Genesis block. - prevHash := &block.MsgBlock().Header.PrevBlock - if prevHash.IsEqual(zeroHash) { - return nil, nil - } - - bi.Lock() - defer bi.Unlock() - - // Return the existing previous block node if it's already there. - if bn, ok := bi.index[*prevHash]; ok { - return bn, nil - } - - // Dynamically load the previous block from the block database, create - // a new block node for it, and update the memory chain accordingly. - var prevBlockNode *blockNode - err := bi.db.View(func(dbTx database.Tx) error { - var err error - prevBlockNode, err = bi.loadBlockNode(dbTx, prevHash) - return err - }) - return prevBlockNode, err -} - -// prevNodeFromNode returns a block node for the block previous to the passed -// block node (the passed block node's parent). When the node is already -// connected to a parent, it simply returns it. Otherwise, it loads the -// associated block from the database to obtain the previous hash and uses that -// to dynamically create a new block node and return it. The memory block -// chain is updated accordingly. The returned node will be nil if the genesis -// block is passed. -// -// This function MUST be called with the block index lock held (for writes). -func (bi *blockIndex) prevNodeFromNode(node *blockNode) (*blockNode, error) { - // Return the existing previous block node if it's already there. - if node.parent != nil { - return node.parent, nil - } - - // Genesis block. - if node.hash.IsEqual(bi.chainParams.GenesisHash) { - return nil, nil - } - - // Dynamically load the previous block from the block database, create - // a new block node for it, and update the memory chain accordingly. - var prevBlockNode *blockNode - err := bi.db.View(func(dbTx database.Tx) error { - var err error - prevBlockNode, err = bi.loadBlockNode(dbTx, &node.parentHash) - return err - }) - return prevBlockNode, err -} - -// PrevNodeFromNode returns a block node for the block previous to the -// passed block node (the passed block node's parent). When the node is already -// connected to a parent, it simply returns it. Otherwise, it loads the -// associated block from the database to obtain the previous hash and uses that -// to dynamically create a new block node and return it. The memory block -// chain is updated accordingly. The returned node will be nil if the genesis -// block is passed. -// -// This function is safe for concurrent access. -func (bi *blockIndex) PrevNodeFromNode(node *blockNode) (*blockNode, error) { - bi.Lock() - node, err := bi.prevNodeFromNode(node) - bi.Unlock() - return node, err -} - -// AncestorNode returns the ancestor block node at the provided height by -// following the chain backwards from the given node while dynamically loading -// any pruned nodes from the database and updating the memory block chain as -// needed. 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 (bi *blockIndex) AncestorNode(node *blockNode, height int64) (*blockNode, error) { - // Nothing to do if the requested height is outside of the valid range. - if height > node.height || height < 0 { - return nil, nil - } - - // Iterate backwards until the requested height is reached. - bi.Lock() - iterNode := node - for iterNode != nil && iterNode.height > height { - // Get the previous block node. This function is used over - // simply accessing iterNode.parent directly as it will - // dynamically create previous block nodes as needed. This - // helps allow only the pieces of the chain that are needed - // to remain in memory. - var err error - iterNode, err = bi.prevNodeFromNode(iterNode) - if err != nil { - log.Errorf("prevNodeFromNode: %v", err) - return nil, err - } - } - bi.Unlock() - - return iterNode, nil -} - -// AddNode adds the provided node to the block index. Duplicate entries are not +// 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() +// This function MUST be called with the block index lock held (for writes). +func (bi *blockIndex) addNode(node *blockNode) { bi.index[node.hash] = node - if prevHash := node.parentHash; prevHash != *zeroHash { - bi.depNodes[prevHash] = append(bi.depNodes[prevHash], node) - } // Since the block index does not support nodes that do not connect to // an existing node (except the genesis block), all new nodes are either @@ -515,32 +358,17 @@ func (bi *blockIndex) AddNode(node *blockNode) { // chain, the parent is no longer a tip. bi.addChainTip(node) if node.parent != nil { - node.parent.children = append(node.parent.children, node) bi.removeChainTip(node.parent) } - bi.Unlock() } -// RemoveNode removes the provided node from the block index. No checks are -// performed to ensure the node already exists, so it's up to the caller to -// avoid removing them. +// 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) RemoveNode(node *blockNode) { +func (bi *blockIndex) AddNode(node *blockNode) { bi.Lock() - if parent := node.parent; parent != nil { - parent.children = removeChildNode(parent.children, node) - } - if prevHash := node.parentHash; prevHash != *zeroHash { - depNodes := bi.depNodes[prevHash] - depNodes = removeChildNode(depNodes, node) - if len(depNodes) == 0 { - delete(bi.depNodes, prevHash) - } else { - bi.depNodes[prevHash] = depNodes - } - } - delete(bi.index, node.hash) + bi.addNode(node) bi.Unlock() } @@ -574,13 +402,21 @@ func (bi *blockIndex) removeChainTip(tip *blockNode) { } } +// lookupNode returns the block node identified by the provided hash. It will +// return nil if there is no entry for the hash. +// +// This function MUST be called with the block index lock held (for reads). +func (bi *blockIndex) lookupNode(hash *chainhash.Hash) *blockNode { + return bi.index[*hash] +} + // 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] + node := bi.lookupNode(hash) bi.RUnlock() return node } @@ -614,60 +450,3 @@ func (bi *blockIndex) UnsetStatusFlags(node *blockNode, flags blockStatus) { node.status &^= flags bi.Unlock() } - -// CalcPastMedianTime calculates the median time of the previous few blocks -// prior to, and including, the passed block node. -// -// This function is safe for concurrent access. -func (bi *blockIndex) CalcPastMedianTime(startNode *blockNode) (time.Time, error) { - // Genesis block. - if startNode == nil { - return bi.chainParams.GenesisBlock.Header.Timestamp, nil - } - - // 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 := startNode - bi.Lock() - for i := 0; i < medianTimeBlocks && iterNode != nil; i++ { - timestamps[i] = iterNode.timestamp - numNodes++ - - // Get the previous block node. This function is used over - // simply accessing iterNode.parent directly as it will - // dynamically create previous block nodes as needed. This - // helps allow only the pieces of the chain that are needed - // to remain in memory. - var err error - iterNode, err = bi.prevNodeFromNode(iterNode) - if err != nil { - bi.Unlock() - log.Errorf("prevNodeFromNode failed to find node: %v", err) - return time.Time{}, err - } - } - bi.Unlock() - - // 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: bitcoind incorrectly calculates 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 as bitcoind - // 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), nil -} diff --git a/blockchain/blockindex_test.go b/blockchain/blockindex_test.go index 35fb84427b..efbfef46e6 100644 --- a/blockchain/blockindex_test.go +++ b/blockchain/blockindex_test.go @@ -162,11 +162,7 @@ func TestCalcPastMedianTime(t *testing.T) { } // Ensure the median time is the expected value. - gotTime, err := bc.index.CalcPastMedianTime(node) - if err != nil { - t.Errorf("%s: unexpected error: %v", test.name, err) - continue - } + gotTime := node.CalcPastMedianTime() wantTime := time.Unix(test.expected, 0) if !gotTime.Equal(wantTime) { t.Errorf("%s: mismatched timestamps -- got: %v, want: %v", diff --git a/blockchain/blocklocator.go b/blockchain/blocklocator.go index 22a2a5aafd..bad87cced7 100644 --- a/blockchain/blocklocator.go +++ b/blockchain/blocklocator.go @@ -112,12 +112,6 @@ func (bi *blockIndex) blockLocatorFromHash(hash *chainhash.Hash) BlockLocator { // backwards along the side chain nodes to each block // height. if forkHeight != -1 && blockHeight > forkHeight { - // Intentionally use parent field instead of the - // PrevNodeFromNode function since we don't - // want to dynamically load nodes when building - // block locators. Side chain blocks should - // always be in memory already, and if they - // aren't for some reason it's ok to skip them. for iterNode != nil && blockHeight > iterNode.height { iterNode = iterNode.parent } diff --git a/blockchain/chain.go b/blockchain/chain.go index 47d307e216..1a8ba63696 100644 --- a/blockchain/chain.go +++ b/blockchain/chain.go @@ -214,12 +214,9 @@ type StakeVersions struct { // GetStakeVersions returns a cooked array of StakeVersions. We do this in // order to not bloat memory by returning raw blocks. func (b *BlockChain) GetStakeVersions(hash *chainhash.Hash, count int32) ([]StakeVersions, error) { - exists, err := b.HaveBlock(hash) - if err != nil { - return nil, err - } - if !exists { - return nil, fmt.Errorf("hash '%s' not found on chain", hash.String()) + startNode := b.index.LookupNode(hash) + if startNode == nil { + return nil, fmt.Errorf("block %s is not known", hash) } // Nothing to do if no count requested. @@ -232,14 +229,6 @@ func (b *BlockChain) GetStakeVersions(hash *chainhash.Hash, count int32) ([]Stak "got %d", count) } - b.chainLock.Lock() - defer b.chainLock.Unlock() - - startNode, err := b.findNode(hash, 0) - if err != nil { - return nil, err - } - // Limit the requested count to the max possible for the requested block. if count > int32(startNode.height+1) { count = int32(startNode.height + 1) @@ -258,10 +247,7 @@ func (b *BlockChain) GetStakeVersions(hash *chainhash.Hash, count int32) ([]Stak result = append(result, sv) - prevNode, err = b.index.PrevNodeFromNode(prevNode) - if err != nil { - return nil, err - } + prevNode = prevNode.parent } return result, nil @@ -337,14 +323,7 @@ func (b *BlockChain) FetchSubsidyCache() *SubsidyCache { // // This function is safe for concurrent access. func (b *BlockChain) HaveBlock(hash *chainhash.Hash) (bool, error) { - b.chainLock.RLock() - exists, err := b.blockExists(hash) - b.chainLock.RUnlock() - - if err != nil { - return false, err - } - return exists || b.IsKnownOrphan(hash), nil + return b.index.HaveBlock(hash) || b.IsKnownOrphan(hash), nil } // IsKnownOrphan returns whether the passed hash is currently a known orphan. @@ -494,61 +473,6 @@ func (b *BlockChain) TipGeneration() ([]chainhash.Hash, error) { return nodeHashes, nil } -// findNode finds the node scaling backwards from best chain or return an -// error. If searchDepth equal zero there is no searchDepth. -// -// This function MUST be called with the chain state lock held (for writes). -func (b *BlockChain) findNode(nodeHash *chainhash.Hash, searchDepth int) (*blockNode, error) { - var node *blockNode - err := b.db.View(func(dbTx database.Tx) error { - // Most common case; we're checking a block that wants to be connected - // on top of the current main chain. - distance := 0 - if *nodeHash == b.bestNode.hash { - node = b.bestNode - } else { - // Look backwards in our blockchain and try to find it in the - // parents of blocks. - foundPrev := b.bestNode - notFound := true - for !foundPrev.hash.IsEqual(b.chainParams.GenesisHash) { - if searchDepth != 0 && distance >= searchDepth { - break - } - - if foundPrev.hash.IsEqual(nodeHash) { - notFound = false - break - } - - last := foundPrev.parentHash - foundPrev = foundPrev.parent - if foundPrev == nil { - parent, err := b.index.loadBlockNode(dbTx, &last) - if err != nil { - return err - } - - foundPrev = parent - } - - distance++ - } - - if notFound { - return fmt.Errorf("couldn't find node %v in best chain", - nodeHash) - } - - node = foundPrev - } - - return nil - }) - - return node, err -} - // fetchMainChainBlockByHash returns the block from the main chain with the // given hash. It first attempts to use cache and then falls back to loading it // from the database. @@ -678,7 +602,11 @@ func (b *BlockChain) BestPrevHash() chainhash.Hash { b.chainLock.Lock() defer b.chainLock.Unlock() - return b.bestNode.parentHash + var prevHash chainhash.Hash + if b.bestNode.parent != nil { + prevHash = b.bestNode.parent.hash + } + return prevHash } // isMajorityVersion determines if a previous number of blocks in the chain @@ -690,21 +618,13 @@ func (b *BlockChain) isMajorityVersion(minVer int32, startNode *blockNode, numRe iterNode := startNode for i := uint64(0); i < b.chainParams.BlockUpgradeNumToCheck && numFound < numRequired && iterNode != nil; i++ { + // This node has a version that is at least the minimum version. if iterNode.blockVersion >= minVer { numFound++ } - // Get the previous block node. This function is used over - // simply accessing iterNode.parent directly as it will - // dynamically create previous block nodes as needed. This - // helps allow only the pieces of the chain that are needed - // to remain in memory. - var err error - iterNode, err = b.index.PrevNodeFromNode(iterNode) - if err != nil { - break - } + iterNode = iterNode.parent } return numFound >= numRequired @@ -719,18 +639,18 @@ func (b *BlockChain) isMajorityVersion(minVer int32, startNode *blockNode, numRe // passed node is not on a side chain. // // This function MUST be called with the chain state lock held (for reads). -func (b *BlockChain) getReorganizeNodes(node *blockNode) (*list.List, *list.List, error) { +func (b *BlockChain) getReorganizeNodes(node *blockNode) (*list.List, *list.List) { // Nothing to detach or attach if there is no node. attachNodes := list.New() detachNodes := list.New() if node == nil { - return detachNodes, attachNodes, nil + return detachNodes, attachNodes } // Don't allow a reorganize to a descendant of a known invalid block. if b.index.NodeStatus(node.parent).KnownInvalid() { b.index.SetStatusFlags(node, statusInvalidAncestor) - return detachNodes, attachNodes, nil + return detachNodes, attachNodes } // Find the fork point (if any) adding each block to the list of nodes @@ -745,12 +665,6 @@ func (b *BlockChain) getReorganizeNodes(node *blockNode) (*list.List, *list.List attachNodes.PushFront(ancestor) } - // TODO(davec): Use prevNodeFromNode function in case the requested - // node is further back than the what is in memory. This shouldn't - // happen in the normal course of operation, but the ability to fetch - // input transactions of arbitrary blocks will likely to be exposed at - // some point and that could lead to an issue here. - // Start from the end of the main chain and work backwards until the // common ancestor adding each block to the list of nodes to detach from // the main chain. @@ -759,17 +673,9 @@ func (b *BlockChain) getReorganizeNodes(node *blockNode) (*list.List, *list.List break } detachNodes.PushBack(n) - - if n.parent == nil { - var err error - n.parent, err = b.findNode(&n.parentHash, maxSearchDepth) - if err != nil { - return nil, nil, err - } - } } - return detachNodes, attachNodes, nil + return detachNodes, attachNodes } // pushMainChainBlockCache pushes a block onto the main chain block cache, @@ -814,12 +720,6 @@ func (b *BlockChain) connectBlock(node *blockNode, block, parent *dcrutil.Block, countSpentOutputs(block, parent)) } - // Calculate the median time for the block. - medianTime, err := b.index.CalcPastMedianTime(node) - if err != nil { - return err - } - // Generate a new best state snapshot that will be used to update the // database and later memory if all database updates are successful. b.stateLock.RLock() @@ -836,7 +736,7 @@ func (b *BlockChain) connectBlock(node *blockNode, block, parent *dcrutil.Block, blockSize := uint64(block.MsgBlock().Header.Size) state := newBestState(node, blockSize, numTxns, curTotalTxns+numTxns, - medianTime, curTotalSubsidy+subsidy) + node.CalcPastMedianTime(), curTotalSubsidy+subsidy) // Get the stake node for this node, filling in any data that // may have yet to have been filled in. In all cases this @@ -1001,21 +901,6 @@ func (b *BlockChain) disconnectBlock(node *blockNode, block, parent *dcrutil.Blo b.bestNode.height) } - // Get the previous block node. This function is used over simply - // accessing node.parent directly as it will dynamically create previous - // block nodes as needed. This helps allow only the pieces of the chain - // that are needed to remain in memory. - prevNode, err := b.index.PrevNodeFromNode(node) - if err != nil { - return err - } - - // Calculate the median time for the previous block. - medianTime, err := b.index.CalcPastMedianTime(prevNode) - if err != nil { - return err - } - // Generate a new best state snapshot that will be used to update the // database and later memory if all database updates are successful. b.stateLock.RLock() @@ -1033,8 +918,9 @@ func (b *BlockChain) disconnectBlock(node *blockNode, block, parent *dcrutil.Blo subsidy := CalculateAddedSubsidy(block, parent) newTotalSubsidy := curTotalSubsidy - subsidy + prevNode := node.parent state := newBestState(prevNode, parentBlockSize, numTxns, newTotalTxns, - medianTime, newTotalSubsidy) + prevNode.CalcPastMedianTime(), newTotalSubsidy) // Prepare the information required to update the stake database // contents. @@ -1195,10 +1081,10 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error if attachNodes.Len() != 0 && detachNodes.Len() != 0 { firstAttachNode := attachNodes.Front().Value.(*blockNode) lastDetachNode := detachNodes.Back().Value.(*blockNode) - if firstAttachNode.parentHash != lastDetachNode.parentHash { + if firstAttachNode.parent.hash != lastDetachNode.parent.hash { panicf("reorganize nodes do not have the same fork point -- first "+ "attach parent %v, last detach parent %v", - &firstAttachNode.parentHash, &lastDetachNode.parentHash) + &firstAttachNode.parent.hash, &lastDetachNode.parent.hash) } } @@ -1245,7 +1131,7 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error // Grab the parent of the current block and also save a reference to it // as the next block to detach so it doesn't need to be loaded again on // the next iteration. - parent, err := b.fetchMainChainBlockByHash(&n.parentHash) + parent, err := b.fetchMainChainBlockByHash(&n.parent.hash) if err != nil { return err } @@ -1278,7 +1164,7 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error return err } - newBest = n + newBest = n.parent } // Set the fork point and grab the fork block when there are nodes to be @@ -1287,12 +1173,9 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error var forkNode *blockNode var forkBlock *dcrutil.Block if attachNodes.Len() > 0 { - var err error - forkNode, err = b.index.PrevNodeFromNode(newBest) - if err != nil { - return err - } + forkNode = newBest + var err error forkBlock, err = b.fetchMainChainBlockByHash(&forkNode.hash) if err != nil { return err @@ -1325,10 +1208,10 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error if i > 0 { parent = attachBlocks[i-1] } - if n.parentHash != *parent.Hash() { + if n.parent.hash != *parent.Hash() { panicf("attach block node hash %v (height %v) parent hash %v does "+ "not match previous parent block hash %v", &n.hash, n.height, - &n.parentHash, parent.Hash()) + &n.parent.hash, parent.Hash()) } // Store the loaded block for later. @@ -1380,10 +1263,10 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error if i < len(detachBlocks)-1 { parent = detachBlocks[i+1] } - if n.parentHash != *parent.Hash() { + if n.parent.hash != *parent.Hash() { panicf("detach block node hash %v (height %v) parent hash %v does "+ "not match previous parent block hash %v", &n.hash, n.height, - &n.parentHash, parent.Hash()) + &n.parent.hash, parent.Hash()) } // Load all of the utxos referenced by the block that aren't @@ -1420,10 +1303,10 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error if i > 0 { parent = attachBlocks[i-1] } - if n.parentHash != *parent.Hash() { + if n.parent.hash != *parent.Hash() { panicf("attach block node hash %v (height %v) parent hash %v does "+ "not match previous parent block hash %v", &n.hash, n.height, - &n.parentHash, parent.Hash()) + &n.parent.hash, parent.Hash()) } // Update the view to mark all utxos referenced by the block @@ -1460,6 +1343,8 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error // forceReorganizationToBlock forces a reorganization of the block chain to the // block hash requested, so long as it matches up with the current organization // of the best chain. +// +// This function MUST be called with the chain state lock held (for writes). func (b *BlockChain) forceHeadReorganization(formerBest chainhash.Hash, newBest chainhash.Hash) error { if formerBest.IsEqual(&newBest) { return fmt.Errorf("can't reorganize to the same block") @@ -1475,7 +1360,7 @@ func (b *BlockChain) forceHeadReorganization(formerBest chainhash.Hash, newBest // Child to reorganize to is missing. newBestNode := b.index.LookupNode(&newBest) - if newBestNode == nil || newBestNode.parentHash != formerBestNode.parentHash { + if newBestNode == nil || newBestNode.parent != formerBestNode.parent { return ruleError(ErrForceReorgMissingChild, "missing child of "+ "common parent for forced reorg") } @@ -1487,7 +1372,7 @@ func (b *BlockChain) forceHeadReorganization(formerBest chainhash.Hash, newBest // Check to make sure our forced-in node validates correctly. view := NewUtxoViewpoint() - view.SetBestHash(&b.bestNode.parentHash) + view.SetBestHash(&b.bestNode.parent.hash) view.SetStakeViewpoint(ViewpointPrevValidInitial) formerBestBlock, err := b.fetchBlockByHash(&formerBest) @@ -1540,11 +1425,7 @@ func (b *BlockChain) forceHeadReorganization(formerBest chainhash.Hash, newBest return err } - attach, detach, err := b.getReorganizeNodes(newBestNode) - if err != nil { - return err - } - + attach, detach := b.getReorganizeNodes(newBestNode) return b.reorganizeChain(attach, detach) } @@ -1575,15 +1456,16 @@ func (b *BlockChain) connectBestChain(node *blockNode, block, parent *dcrutil.Bl fastAdd := flags&BFFastAdd == BFFastAdd // Ensure the passed parent is actually the parent of the block. - if *parent.Hash() != node.parentHash { + if *parent.Hash() != node.parent.hash { panicf("parent block %v (height %v) does not match expected parent %v "+ "(height %v)", parent.Hash(), parent.MsgBlock().Header.Height, - node.parentHash, node.height-1) + node.parent.hash, node.height-1) } // We are extending the main (best) chain with a new block. This is the // most common case. - if node.parentHash == b.bestNode.hash { + parentHash := &block.MsgBlock().Header.PrevBlock + if *parentHash == b.bestNode.hash { // Skip expensive checks if the block has already been fully // validated. fastAdd = fastAdd || b.index.NodeStatus(node).KnownValid() @@ -1592,7 +1474,7 @@ func (b *BlockChain) connectBestChain(node *blockNode, block, parent *dcrutil.Bl // to the main chain without violating any rules and without // actually connecting the block. view := NewUtxoViewpoint() - view.SetBestHash(&node.parentHash) + view.SetBestHash(parentHash) view.SetStakeViewpoint(ViewpointPrevValidInitial) var stxos []spentTxOut if !fastAdd { @@ -1655,19 +1537,14 @@ func (b *BlockChain) connectBestChain(node *blockNode, block, parent *dcrutil.Bl if node.workSum.Cmp(b.bestNode.workSum) <= 0 { // Find the fork point. fork := node - for fork.parent != nil { + for ; fork.parent != nil; fork = fork.parent { if fork.inMainChain { break } - var err error - fork, err = b.index.PrevNodeFromNode(fork) - if err != nil { - return 0, err - } } // Log information about how the block is forking the chain. - if fork.hash == node.parent.hash { + if fork.hash == *parentHash { log.Infof("FORK: Block %v (height %v) forks the chain at height "+ "%d/block %v, but does not cause a reorganize", node.hash, node.height, fork.height, fork.hash) @@ -1688,14 +1565,11 @@ func (b *BlockChain) connectBestChain(node *blockNode, block, parent *dcrutil.Bl // blocks that form the (now) old fork from the main chain, and attach // the blocks that form the new chain to the main chain starting at the // common ancenstor (the point where the chain forked). - detachNodes, attachNodes, err := b.getReorganizeNodes(node) - if err != nil { - return 0, err - } + detachNodes, attachNodes := b.getReorganizeNodes(node) // Reorganize the chain. log.Infof("REORGANIZE: Block %v is causing a reorganize.", node.hash) - err = b.reorganizeChain(detachNodes, attachNodes) + err := b.reorganizeChain(detachNodes, attachNodes) if err != nil { return 0, err } diff --git a/blockchain/chainio.go b/blockchain/chainio.go index a5074ce5fd..6f6ddb2e55 100644 --- a/blockchain/chainio.go +++ b/blockchain/chainio.go @@ -418,20 +418,6 @@ func dbPutBlockNode(dbTx database.Tx, node *blockNode) error { return bucket.Put(key, serialized) } -// dbFetchBlockIndexEntry fetches the block index entry for the passed hash and -// height from the block index. -func dbFetchBlockIndexEntry(dbTx database.Tx, hash *chainhash.Hash, height uint32) (*blockIndexEntry, error) { - bucket := dbTx.Metadata().Bucket(dbnamespace.BlockIndexBucketName) - key := blockIndexKey(hash, height) - serialized := bucket.Get(key) - if serialized == nil { - return nil, AssertError(fmt.Sprintf("missing block node %s "+ - "(height %d)", hash, height)) - } - - return deserializeBlockIndexEntry(serialized) -} - // dbMaybeStoreBlock stores the provided block in the database if it's not // already there. func dbMaybeStoreBlock(dbTx database.Tx, block *dcrutil.Block) error { @@ -1771,7 +1757,8 @@ func (b *BlockChain) initChainState(interrupt <-chan struct{}) error { // When it doesn't exist, it means the database hasn't been // initialized for use with chain yet, so break out now to allow // that to happen under a writable database transaction. - serializedData := dbTx.Metadata().Get(dbnamespace.ChainStateKeyName) + meta := dbTx.Metadata() + serializedData := meta.Get(dbnamespace.ChainStateKeyName) if serializedData == nil { return nil } @@ -1781,59 +1768,119 @@ func (b *BlockChain) initChainState(interrupt <-chan struct{}) error { return err } - // Load the best and parent blocks and cache them. - utilBlock, err := dbFetchBlockByHash(dbTx, &state.hash) - if err != nil { - return err + log.Infof("Loading block index...") + bidxStart := time.Now() + + // Determine how many blocks will be loaded into the index in order to + // allocate the right amount as a single alloc versus a whole bunch of + // littles ones to reduce pressure on the GC. + blockIndexBucket := meta.Bucket(dbnamespace.BlockIndexBucketName) + var blockCount int32 + cursor := blockIndexBucket.Cursor() + for ok := cursor.First(); ok; ok = cursor.Next() { + blockCount++ } - b.mainchainBlockCache[state.hash] = utilBlock - block := utilBlock.MsgBlock() - header := &block.Header - if header.Height > 0 { - parentBlock, err := dbFetchBlockByHash(dbTx, &header.PrevBlock) + blockNodes := make([]blockNode, blockCount) + + // Load all of the block index entries and construct the block index + // accordingly. + // + // NOTE: No locks are used on the block index here since this is + // initialization code. + var i int32 + var lastNode *blockNode + cursor = blockIndexBucket.Cursor() + for ok := cursor.First(); ok; ok = cursor.Next() { + entry, err := deserializeBlockIndexEntry(cursor.Value()) if err != nil { return err } - b.mainchainBlockCache[header.PrevBlock] = parentBlock + header := &entry.header + + // Determine the parent block node. Since the block headers are + // iterated in order of height, there is a very good chance the + // previous header processed is the parent. + var parent *blockNode + if lastNode == nil { + blockHash := header.BlockHash() + if blockHash != *b.chainParams.GenesisHash { + return AssertError(fmt.Sprintf("initChainState: expected "+ + "first entry in block index to be genesis block, "+ + "found %s", blockHash)) + } + } else if header.PrevBlock == lastNode.hash { + parent = lastNode + } else { + parent = b.index.lookupNode(&header.PrevBlock) + if parent == nil { + return AssertError(fmt.Sprintf("initChainState: could "+ + "not find parent for block %s", header.BlockHash())) + } + } + + // Initialize the block node, connect it, and add it to the block + // index. + node := &blockNodes[i] + initBlockNode(node, header, parent) + node.ticketsVoted = entry.ticketsVoted + node.ticketsRevoked = entry.ticketsRevoked + node.votes = entry.voteInfo + b.index.addNode(node) + + lastNode = node + i++ + } + + // Set the best chain to the stored best state. + tip := b.index.lookupNode(&state.hash) + if tip == nil { + return AssertError(fmt.Sprintf("initChainState: cannot find "+ + "chain tip %s in block index", state.hash)) } + b.bestNode = tip - // Create a new node and set it as the best node. The preceding - // nodes will be loaded on demand as needed. - node := newBlockNode(header, nil) - node.populateTicketInfo(stake.FindSpentTicketsInBlock(block)) - node.status = statusDataStored | statusValid - node.inMainChain = true - node.workSum = state.workSum + // Mark all of the nodes from the tip back to the genesis block + // as part of the main chain. + for n := tip; n != nil; n = n.parent { + n.inMainChain = true + } + + log.Debugf("Block index loaded in %v", time.Since(bidxStart)) // Exception for version 1 blockchains: skip loading the stake // node, as the upgrade path handles ensuring this is correctly // set. if b.dbInfo.version >= 2 { - node.stakeNode, err = stake.LoadBestNode(dbTx, uint32(node.height), - node.hash, *header, b.chainParams) + tip.stakeNode, err = stake.LoadBestNode(dbTx, uint32(tip.height), + tip.hash, tip.Header(), b.chainParams) if err != nil { return err } - node.stakeUndoData = node.stakeNode.UndoData() - node.newTickets = node.stakeNode.NewTickets() + tip.stakeUndoData = tip.stakeNode.UndoData() + tip.newTickets = tip.stakeNode.NewTickets() } - b.bestNode = node - - // Add the new node to the indices for faster lookups. - b.index.AddNode(node) - - // Calculate the median time for the block. - medianTime, err := b.index.CalcPastMedianTime(node) + // Load the best and parent blocks and cache them. + utilBlock, err := dbFetchBlockByHash(dbTx, &tip.hash) if err != nil { return err } + b.mainchainBlockCache[tip.hash] = utilBlock + if tip.parent != nil { + parentBlock, err := dbFetchBlockByHash(dbTx, &tip.parent.hash) + if err != nil { + return err + } + b.mainchainBlockCache[tip.parent.hash] = parentBlock + } // Initialize the state related to the best block. + block := utilBlock.MsgBlock() blockSize := uint64(block.SerializeSize()) numTxns := uint64(len(block.Transactions)) - b.stateSnapshot = newBestState(b.bestNode, blockSize, numTxns, - state.totalTxns, medianTime, state.totalSubsidy) + b.stateSnapshot = newBestState(tip, blockSize, numTxns, + state.totalTxns, tip.CalcPastMedianTime(), + state.totalSubsidy) return nil }) diff --git a/blockchain/difficulty.go b/blockchain/difficulty.go index 690043a41f..7113494069 100644 --- a/blockchain/difficulty.go +++ b/blockchain/difficulty.go @@ -205,7 +205,7 @@ func (b *BlockChain) calcEasiestDifficulty(bits uint32, duration time.Duration) // did not have the special testnet minimum difficulty rule applied. // // This function MUST be called with the chain state lock held (for writes). -func (b *BlockChain) findPrevTestNetDifficulty(startNode *blockNode) (uint32, error) { +func (b *BlockChain) findPrevTestNetDifficulty(startNode *blockNode) uint32 { // Search backwards through the chain for the last block without // the special rule applied. blocksPerRetarget := b.chainParams.WorkDiffWindowSize * @@ -214,17 +214,7 @@ func (b *BlockChain) findPrevTestNetDifficulty(startNode *blockNode) (uint32, er for iterNode != nil && iterNode.height%blocksPerRetarget != 0 && iterNode.bits == b.chainParams.PowLimitBits { - // Get the previous block node. This function is used over - // simply accessing iterNode.parent directly as it will - // dynamically create previous block nodes as needed. This - // helps allow only the pieces of the chain that are needed - // to remain in memory. - var err error - iterNode, err = b.index.PrevNodeFromNode(iterNode) - if err != nil { - log.Errorf("PrevNodeFromNode: %v", err) - return 0, err - } + iterNode = iterNode.parent } // Return the found difficulty or the minimum difficulty if no @@ -233,7 +223,7 @@ func (b *BlockChain) findPrevTestNetDifficulty(startNode *blockNode) (uint32, er if iterNode != nil { lastBits = iterNode.bits } - return lastBits, nil + return lastBits } // calcNextRequiredDifficulty calculates the required difficulty for the block @@ -241,8 +231,6 @@ func (b *BlockChain) findPrevTestNetDifficulty(startNode *blockNode) (uint32, er // This function differs from the exported CalcNextRequiredDifficulty in that // the exported version uses the current best chain as the previous block node // while this function accepts any block node. -// -// This function MUST be called with the chain state lock held (for writes). func (b *BlockChain) calcNextRequiredDifficulty(curNode *blockNode, newBlockTime time.Time) (uint32, error) { // Genesis block. if curNode == nil { @@ -294,11 +282,7 @@ func (b *BlockChain) calcNextRequiredDifficulty(curNode *blockNode, newBlockTime // The block was mined within the desired timeframe, so // return the difficulty for the last block which did // not have the special minimum difficulty rule applied. - prevBits, err := b.findPrevTestNetDifficulty(curNode) - if err != nil { - return 0, err - } - return prevBits, nil + return b.findPrevTestNetDifficulty(curNode), nil } return oldDiff, nil @@ -369,22 +353,10 @@ func (b *BlockChain) calcNextRequiredDifficulty(curNode *blockNode, newBlockTime break // Exit for loop when we hit the end. } - // Get the previous block node. This function is used over - // simply accessing firstNode.parent directly as it will - // dynamically create previous block nodes as needed. This - // helps allow only the pieces of the chain that are needed - // to remain in memory. - var err error - tempNode := oldNode - oldNode, err = b.index.PrevNodeFromNode(oldNode) - if err != nil { - return 0, err - } - - // If we're at the genesis block, reset the oldNode - // so that it stays at the genesis block. - if oldNode == nil { - oldNode = tempNode + // Get the previous node while staying at the genesis block as + // needed. + if oldNode.parent != nil { + oldNode = oldNode.parent } } @@ -439,10 +411,9 @@ func (b *BlockChain) calcNextRequiredDifficulty(curNode *blockNode, newBlockTime // // This function is NOT safe for concurrent access. func (b *BlockChain) CalcNextRequiredDiffFromNode(hash *chainhash.Hash, timestamp time.Time) (uint32, error) { - // Fetch the block to get the difficulty for. - node, err := b.findNode(hash, maxSearchDepth) - if err != nil { - return 0, err + node := b.index.LookupNode(hash) + if node == nil { + return 0, fmt.Errorf("block %s is not known", hash) } return b.calcNextRequiredDifficulty(node, timestamp) @@ -581,18 +552,10 @@ func (b *BlockChain) calcNextRequiredStakeDifficultyV1(curNode *blockNode) (int6 break // Exit for loop when we hit the end. } - // Get the previous block node. - var err error - tempNode := oldNode - oldNode, err = b.index.PrevNodeFromNode(oldNode) - if err != nil { - return 0, err - } - - // If we're at the genesis block, reset the oldNode - // so that it stays at the genesis block. - if oldNode == nil { - oldNode = tempNode + // Get the previous node while staying at the genesis block as + // needed. + if oldNode.parent != nil { + oldNode = oldNode.parent } } @@ -676,18 +639,10 @@ func (b *BlockChain) calcNextRequiredStakeDifficultyV1(curNode *blockNode) (int6 break // Exit for loop when we hit the end. } - // Get the previous block node. - var err error - tempNode := oldNode - oldNode, err = b.index.PrevNodeFromNode(oldNode) - if err != nil { - return 0, err - } - - // If we're at the genesis block, reset the oldNode - // so that it stays at the genesis block. - if oldNode == nil { - oldNode = tempNode + // Get the previous node while staying at the genesis block as + // needed. + if oldNode.parent != nil { + oldNode = oldNode.parent } } @@ -787,28 +742,16 @@ func estimateSupply(params *chaincfg.Params, height int64) int64 { // sumPurchasedTickets returns the sum of the number of tickets purchased in the // most recent specified number of blocks from the point of view of the passed // node. -// -// This function MUST be called with the chain state lock held (for writes). -func (b *BlockChain) sumPurchasedTickets(startNode *blockNode, numToSum int64) (int64, error) { +func (b *BlockChain) sumPurchasedTickets(startNode *blockNode, numToSum int64) int64 { var numPurchased int64 for node, numTraversed := startNode, int64(0); node != nil && numTraversed < numToSum; numTraversed++ { numPurchased += int64(node.freshStake) - - // Get the previous block node. This function is used over - // simply accessing iterNode.parent directly as it will - // dynamically create previous block nodes as needed. This - // helps allow only the pieces of the chain that are needed - // to remain in memory. - var err error - node, err = b.index.PrevNodeFromNode(node) - if err != nil { - return 0, err - } + node = node.parent } - return numPurchased, nil + return numPurchased } // calcNextStakeDiffV2 calculates the next stake difficulty for the given set @@ -921,19 +864,13 @@ func (b *BlockChain) calcNextRequiredStakeDifficultyV2(curNode *blockNode) (int6 // originally calculated. var prevPoolSize int64 prevRetargetHeight := nextHeight - intervalSize - 1 - prevRetargetNode, err := b.index.AncestorNode(curNode, prevRetargetHeight) - if err != nil { - return 0, err - } + prevRetargetNode := curNode.Ancestor(prevRetargetHeight) if prevRetargetNode != nil { prevPoolSize = int64(prevRetargetNode.poolSize) } ticketMaturity := int64(b.chainParams.TicketMaturity) - prevImmatureTickets, err := b.sumPurchasedTickets(prevRetargetNode, + prevImmatureTickets := b.sumPurchasedTickets(prevRetargetNode, ticketMaturity) - if err != nil { - return 0, err - } // Return the existing ticket price for the first few intervals to avoid // division by zero and encourage initial pool population. @@ -943,10 +880,7 @@ func (b *BlockChain) calcNextRequiredStakeDifficultyV2(curNode *blockNode) (int6 } // Count the number of currently immature tickets. - immatureTickets, err := b.sumPurchasedTickets(curNode, ticketMaturity) - if err != nil { - return 0, err - } + immatureTickets := b.sumPurchasedTickets(curNode, ticketMaturity) // Calculate and return the final next required difficulty. curPoolSizeAll := int64(curNode.poolSize) + immatureTickets @@ -1148,18 +1082,10 @@ func (b *BlockChain) estimateNextStakeDifficultyV1(curNode *blockNode, ticketsIn break // Exit for loop when we hit the end. } - // Get the previous block node. - var err error - tempNode := oldNode - oldNode, err = b.index.PrevNodeFromNode(oldNode) - if err != nil { - return 0, err - } - - // If we're at the genesis block, reset the oldNode - // so that it stays at the genesis block. - if oldNode == nil { - oldNode = tempNode + // Get the previous node while staying at the genesis block as + // needed. + if oldNode.parent != nil { + oldNode = oldNode.parent } } @@ -1243,18 +1169,10 @@ func (b *BlockChain) estimateNextStakeDifficultyV1(curNode *blockNode, ticketsIn break // Exit for loop when we hit the end. } - // Get the previous block node. - var err error - tempNode := oldNode - oldNode, err = b.index.PrevNodeFromNode(oldNode) - if err != nil { - return 0, err - } - - // If we're at the genesis block, reset the oldNode - // so that it stays at the genesis block. - if oldNode == nil { - oldNode = tempNode + // Get the previous node while staying at the genesis block as + // needed. + if oldNode.parent != nil { + oldNode = oldNode.parent } } @@ -1367,18 +1285,12 @@ func (b *BlockChain) estimateNextStakeDifficultyV2(curNode *blockNode, newTicket // originally calculated. var prevPoolSize int64 prevRetargetHeight := nextRetargetHeight - intervalSize - 1 - prevRetargetNode, err := b.index.AncestorNode(curNode, prevRetargetHeight) - if err != nil { - return 0, err - } + prevRetargetNode := curNode.Ancestor(prevRetargetHeight) if prevRetargetNode != nil { prevPoolSize = int64(prevRetargetNode.poolSize) } - prevImmatureTickets, err := b.sumPurchasedTickets(prevRetargetNode, + prevImmatureTickets := b.sumPurchasedTickets(prevRetargetNode, ticketMaturity) - if err != nil { - return 0, err - } // Return the existing ticket price for the first few intervals to avoid // division by zero and encourage initial pool population. @@ -1399,11 +1311,8 @@ func (b *BlockChain) estimateNextStakeDifficultyV2(curNode *blockNode, newTicket var remainingImmatureTickets int64 nextMaturityFloor := nextRetargetHeight - ticketMaturity - 1 if curHeight > nextMaturityFloor { - remainingImmatureTickets, err = b.sumPurchasedTickets(curNode, + remainingImmatureTickets = b.sumPurchasedTickets(curNode, curHeight-nextMaturityFloor) - if err != nil { - return 0, err - } } // Add the number of tickets that will still be immature at the next @@ -1426,16 +1335,10 @@ func (b *BlockChain) estimateNextStakeDifficultyV2(curNode *blockNode, newTicket if finalMaturingHeight > curHeight { finalMaturingHeight = curHeight } - finalMaturingNode, err := b.index.AncestorNode(curNode, finalMaturingHeight) - if err != nil { - return 0, err - } + finalMaturingNode := curNode.Ancestor(finalMaturingHeight) firstMaturingHeight := curHeight - ticketMaturity - maturingTickets, err := b.sumPurchasedTickets(finalMaturingNode, + maturingTickets := b.sumPurchasedTickets(finalMaturingNode, finalMaturingHeight-firstMaturingHeight+1) - if err != nil { - return 0, err - } // Add the number of tickets that will mature based on the estimated data. // diff --git a/blockchain/process.go b/blockchain/process.go index b3c6e13cef..1a3e03f0ba 100644 --- a/blockchain/process.go +++ b/blockchain/process.go @@ -10,7 +10,6 @@ import ( "time" "github.com/decred/dcrd/chaincfg/chainhash" - "github.com/decred/dcrd/database" "github.com/decred/dcrd/dcrutil" ) @@ -34,43 +33,6 @@ const ( BFNone BehaviorFlags = 0 ) -// blockExists determines whether a block with the given hash exists either in -// the main chain or any side chains. -// -// This function MUST be called with the chain state lock held (for reads). -func (b *BlockChain) blockExists(hash *chainhash.Hash) (bool, error) { - // Check block index first (could be main chain or side chain blocks). - if b.index.HaveBlock(hash) { - return true, nil - } - - // Check in the database. - var exists bool - err := b.db.View(func(dbTx database.Tx) error { - var err error - exists, err = dbTx.HasBlock(hash) - if err != nil || !exists { - return err - } - - // Ignore side chain blocks in the database. This is necessary - // because there is not currently any record of the associated - // block index data, so it's not yet possible to efficiently load the - // block and do anything useful with it. - // - // Ultimately the entire block index should be serialized - // instead of only the current main chain so it can be consulted - // directly. - _, err = dbFetchHeightByHash(dbTx, hash) - if isNotInMainChainErr(err) { - exists = false - return nil - } - return err - }) - return exists, err -} - // processOrphans determines if there are any orphans which depend on the passed // block hash (they are no longer orphans if true) and potentially accepts them. // It repeats the process for the newly accepted blocks (to detect further @@ -159,11 +121,7 @@ func (b *BlockChain) ProcessBlock(block *dcrutil.Block, flags BehaviorFlags) (in }() // The block must not already exist in the main chain or side chains. - exists, err := b.blockExists(blockHash) - if err != nil { - return 0, false, err - } - if exists { + if b.index.HaveBlock(blockHash) { str := fmt.Sprintf("already have block %v", blockHash) return 0, false, ruleError(ErrDuplicateBlock, str) } @@ -175,7 +133,7 @@ func (b *BlockChain) ProcessBlock(block *dcrutil.Block, flags BehaviorFlags) (in } // Perform preliminary sanity checks on the block and its transactions. - err = checkBlockSanity(block, b.timeSource, flags, b.chainParams) + err := checkBlockSanity(block, b.timeSource, flags, b.chainParams) if err != nil { return 0, false, err } @@ -224,11 +182,7 @@ func (b *BlockChain) ProcessBlock(block *dcrutil.Block, flags BehaviorFlags) (in // Handle orphan blocks. prevHash := &blockHeader.PrevBlock - prevHashExists, err := b.blockExists(prevHash) - if err != nil { - return 0, false, err - } - if !prevHashExists { + if !b.index.HaveBlock(prevHash) { log.Infof("Adding orphan block %v with parent %v", blockHash, prevHash) b.addOrphanBlock(block) diff --git a/blockchain/sequencelock.go b/blockchain/sequencelock.go index cb67a154e1..3a2ffa62b6 100644 --- a/blockchain/sequencelock.go +++ b/blockchain/sequencelock.go @@ -39,8 +39,6 @@ func isStakeBaseTx(tx *wire.MsgTx) bool { // from the point of view of the block node passed in as the first argument. // // See the CalcSequenceLock comments for more details. -// -// This function MUST be called with the chain state lock held (for writes). func (b *BlockChain) calcSequenceLock(node *blockNode, tx *dcrutil.Tx, view *UtxoViewpoint, isActive bool) (*SequenceLock, error) { // A value of -1 for each lock type allows a transaction to be included // in a block at any given height or time. @@ -102,17 +100,8 @@ func (b *BlockChain) calcSequenceLock(node *blockNode, tx *dcrutil.Tx, view *Utx if prevInputHeight < 0 { prevInputHeight = 0 } - blockNode, err := b.index.AncestorNode(node, prevInputHeight) - if err != nil { - return sequenceLock, err - } - - // Calculate the past median time of the block prior to - // the one which included the output being spent. - medianTime, err := b.index.CalcPastMedianTime(blockNode) - if err != nil { - return sequenceLock, err - } + blockNode := node.Ancestor(prevInputHeight) + medianTime := blockNode.CalcPastMedianTime() // Calculate the minimum required timestamp based on the // sum of the aforementioned past median time and diff --git a/blockchain/sequencelock_test.go b/blockchain/sequencelock_test.go index da76de9a83..b3887e9bd6 100644 --- a/blockchain/sequencelock_test.go +++ b/blockchain/sequencelock_test.go @@ -74,25 +74,13 @@ func TestCalcSequenceLock(t *testing.T) { // Obtain the median time past from the PoV of the input created above. // The median time for the input is the median time from the PoV of the // block *prior* to the one that included it. - medianNode, err := bc.index.AncestorNode(node, node.height-5) - if err != nil { - t.Fatalf("Unable to obtain median node: %v", err) - } - medianT, err := bc.index.CalcPastMedianTime(medianNode) - if err != nil { - t.Fatalf("Unable to obtain median node time: %v", err) - } - medianTime := medianT.Unix() + medianTime := node.RelativeAncestor(5).CalcPastMedianTime().Unix() // The median time calculated from the PoV of the best block in the // test chain. For unconfirmed inputs, this value will be used since // the median time will be calculated from the PoV of the // yet-to-be-mined block. - nextMedianT, err := bc.index.CalcPastMedianTime(node) - if err != nil { - t.Fatalf("Unable to obtain next median node time: %v", err) - } - nextMedianTime := nextMedianT.Unix() + nextMedianTime := node.CalcPastMedianTime().Unix() nextBlockHeight := int64(numBlocks) + 1 // Add an additional transaction which will serve as our unconfirmed diff --git a/blockchain/stakeext.go b/blockchain/stakeext.go index 4f19209da2..167f9c99e5 100644 --- a/blockchain/stakeext.go +++ b/blockchain/stakeext.go @@ -6,6 +6,8 @@ package blockchain import ( + "fmt" + "github.com/decred/dcrd/chaincfg/chainhash" "github.com/decred/dcrd/database" "github.com/decred/dcrd/dcrutil" @@ -50,15 +52,9 @@ func (b *BlockChain) lotteryDataForNode(node *blockNode) ([]chainhash.Hash, int, // This function is NOT safe for concurrent access and must have the chainLock // held for write access. func (b *BlockChain) lotteryDataForBlock(hash *chainhash.Hash) ([]chainhash.Hash, int, [6]byte, error) { - var node *blockNode - if n := b.index.LookupNode(hash); n != nil { - node = n - } else { - var err error - node, err = b.findNode(hash, maxSearchDepth) - if err != nil { - return nil, 0, [6]byte{}, err - } + node := b.index.LookupNode(hash) + if node == nil { + return nil, 0, [6]byte{}, fmt.Errorf("block %s is not known", hash) } winningTickets, poolSize, finalState, err := b.lotteryDataForNode(node) @@ -73,15 +69,15 @@ func (b *BlockChain) lotteryDataForBlock(hash *chainhash.Hash) ([]chainhash.Hash // chain, including side chain blocks. // // It is safe for concurrent access. -// TODO An optimization can be added that only calls the read lock if the -// block is not minMemoryStakeNodes blocks before the current best node. -// This is because all the data for these nodes can be assumed to be -// in memory. func (b *BlockChain) LotteryDataForBlock(hash *chainhash.Hash) ([]chainhash.Hash, int, [6]byte, error) { + // TODO: An optimization can be added that only calls the read lock if the + // block is not minMemoryStakeNodes blocks before the current best node. + // This is because all the data for these nodes can be assumed to be + // in memory. b.chainLock.Lock() - defer b.chainLock.Unlock() - - return b.lotteryDataForBlock(hash) + winningTickets, poolSize, finalState, err := b.lotteryDataForBlock(hash) + b.chainLock.Unlock() + return winningTickets, poolSize, finalState, err } // LiveTickets returns all currently live tickets from the stake database. diff --git a/blockchain/stakenode.go b/blockchain/stakenode.go index 74c0226f9e..1c7f364376 100644 --- a/blockchain/stakenode.go +++ b/blockchain/stakenode.go @@ -13,29 +13,6 @@ import ( "github.com/decred/dcrd/database" ) -// nodeAtHeightFromTopNode goes backwards through a node until it a reaches -// the node with a desired block height; it returns this block. The benefit is -// this works for both the main chain and the side chain. -func (b *BlockChain) nodeAtHeightFromTopNode(node *blockNode, toTraverse int64) (*blockNode, error) { - oldNode := node - var err error - - for i := 0; i < int(toTraverse); i++ { - // Get the previous block node. - oldNode, err = b.index.PrevNodeFromNode(oldNode) - if err != nil { - return nil, err - } - - if oldNode == nil { - return nil, fmt.Errorf("unable to obtain previous node; " + - "ancestor is genesis block") - } - } - - return oldNode, nil -} - // fetchNewTicketsForNode fetches the list of newly maturing tickets for a // given node by traversing backwards through its parents until it finds the // block that contains the original tickets to mature. @@ -59,10 +36,10 @@ func (b *BlockChain) fetchNewTicketsForNode(node *blockNode) ([]chainhash.Hash, // Calculate block number for where new tickets matured from and retrieve // this block from DB or in memory if it's a sidechain. - matureNode, err := b.nodeAtHeightFromTopNode(node, - int64(b.chainParams.TicketMaturity)) - if err != nil { - return nil, err + matureNode := node.RelativeAncestor(int64(b.chainParams.TicketMaturity)) + if matureNode == nil { + return nil, fmt.Errorf("unable to obtain previous node; " + + "ancestor is genesis block") } matureBlock, errBlock := b.fetchBlockByHash(&matureNode.hash) @@ -128,17 +105,14 @@ func (b *BlockChain) fetchStakeNode(node *blockNode) (*stake.Node, error) { // it through the entire path. The bestNode stake node must // always be filled in, so assume it is safe to begin working // backwards from there. - detachNodes, attachNodes, err := b.getReorganizeNodes(node) - if err != nil { - return nil, err - } + detachNodes, attachNodes := b.getReorganizeNodes(node) current := b.bestNode // Move backwards through the main chain, undoing the ticket // treaps for each block. The database is passed because the // undo data and new tickets data for each block may not yet // be filled in and may require the database to look up. - err = b.db.View(func(dbTx database.Tx) error { + err := b.db.View(func(dbTx database.Tx) error { for e := detachNodes.Front(); e != nil; e = e.Next() { n := e.Value.(*blockNode) if n.stakeNode == nil { diff --git a/blockchain/stakeversion.go b/blockchain/stakeversion.go index 0a35bda168..289e412643 100644 --- a/blockchain/stakeversion.go +++ b/blockchain/stakeversion.go @@ -23,7 +23,7 @@ var ( // version and a hash. This is used for caches that require a version in // addition to a simple hash. func stakeMajorityCacheVersionKey(version uint32, hash *chainhash.Hash) [stakeMajorityCacheKeySize]byte { - key := [stakeMajorityCacheKeySize]byte{} + var key [stakeMajorityCacheKeySize]byte binary.LittleEndian.PutUint32(key[0:], version) copy(key[4:], hash[:]) return key @@ -33,13 +33,11 @@ func stakeMajorityCacheVersionKey(version uint32, hash *chainhash.Hash) [stakeMa // interval given a stake validation height, stake validation interval, and // block height. func calcWantHeight(stakeValidationHeight, interval, height int64) int64 { - intervalOffset := stakeValidationHeight % interval - // The adjusted height accounts for the fact the starting validation // height does not necessarily start on an interval and thus the // intervals might not be zero-based. + intervalOffset := stakeValidationHeight % interval adjustedHeight := height - intervalOffset - 1 - return (adjustedHeight - ((adjustedHeight + 1) % interval)) + intervalOffset } @@ -58,30 +56,18 @@ func (b *BlockChain) CalcWantHeight(interval, height int64) int64 { // prior to the stake validation interval. // // This function MUST be called with the chain state lock held (for writes). -func (b *BlockChain) findStakeVersionPriorNode(prevNode *blockNode) (*blockNode, error) { +func (b *BlockChain) findStakeVersionPriorNode(prevNode *blockNode) *blockNode { // Check to see if the blockchain is high enough to begin accounting // stake versions. + svh := b.chainParams.StakeValidationHeight + svi := b.chainParams.StakeVersionInterval nextHeight := prevNode.height + 1 - if nextHeight < b.chainParams.StakeValidationHeight+ - b.chainParams.StakeVersionInterval { - return nil, nil - } - - wantHeight := calcWantHeight(b.chainParams.StakeValidationHeight, - b.chainParams.StakeVersionInterval, nextHeight) - - // Walk backwards until we find an interval block and make sure we - // don't blow through the minimum height. - iterNode := prevNode - for iterNode.height > wantHeight { - var err error - iterNode, err = b.index.PrevNodeFromNode(iterNode) - if err != nil { - return nil, err - } + if nextHeight < svh+svi { + return nil } - return iterNode, nil + wantHeight := calcWantHeight(svh, svi, nextHeight) + return prevNode.Ancestor(wantHeight) } // isVoterMajorityVersion determines if minVer requirement is met based on @@ -94,10 +80,7 @@ func (b *BlockChain) findStakeVersionPriorNode(prevNode *blockNode) (*blockNode, // This function MUST be called with the chain state lock held (for writes). func (b *BlockChain) isVoterMajorityVersion(minVer uint32, prevNode *blockNode) bool { // Walk blockchain backwards to calculate version. - node, err := b.findStakeVersionPriorNode(prevNode) - if err != nil { - return false - } + node := b.findStakeVersionPriorNode(prevNode) if node == nil { return 0 >= minVer } @@ -122,11 +105,7 @@ func (b *BlockChain) isVoterMajorityVersion(minVer uint32, prevNode *blockNode) } } - var err error - iterNode, err = b.index.PrevNodeFromNode(iterNode) - if err != nil { - return false - } + iterNode = iterNode.parent } // Determine the required amount of votes to reach supermajority. @@ -150,10 +129,7 @@ func (b *BlockChain) isVoterMajorityVersion(minVer uint32, prevNode *blockNode) // This function MUST be called with the chain state lock held (for writes). func (b *BlockChain) isStakeMajorityVersion(minVer uint32, prevNode *blockNode) bool { // Walk blockchain backwards to calculate version. - node, err := b.findStakeVersionPriorNode(prevNode) - if err != nil { - return false - } + node := b.findStakeVersionPriorNode(prevNode) if node == nil { return 0 >= minVer } @@ -164,9 +140,9 @@ func (b *BlockChain) isStakeMajorityVersion(minVer uint32, prevNode *blockNode) return result } - // Tally how many of the block headers in the previous stake version validation - // interval have their stake version set to at least the requested minimum - // version. + // Tally how many of the block headers in the previous stake version + // validation interval have their stake version set to at least the + // requested minimum version. versionCount := int32(0) iterNode := node for i := int64(0); i < b.chainParams.StakeVersionInterval && iterNode != nil; i++ { @@ -174,12 +150,7 @@ func (b *BlockChain) isStakeMajorityVersion(minVer uint32, prevNode *blockNode) versionCount += 1 } - var err error - iterNode, err = b.index.PrevNodeFromNode(iterNode) - if err != nil { - b.isStakeMajorityVersionCache[key] = false - return false - } + iterNode = iterNode.parent } // Determine the required amount of votes to reach supermajority. @@ -201,10 +172,7 @@ func (b *BlockChain) isStakeMajorityVersion(minVer uint32, prevNode *blockNode) // This function MUST be called with the chain state lock held (for writes). func (b *BlockChain) calcPriorStakeVersion(prevNode *blockNode) (uint32, error) { // Walk blockchain backwards to calculate version. - node, err := b.findStakeVersionPriorNode(prevNode) - if err != nil { - return 0, err - } + node := b.findStakeVersionPriorNode(prevNode) if node == nil { return 0, nil } @@ -221,11 +189,7 @@ func (b *BlockChain) calcPriorStakeVersion(prevNode *blockNode) (uint32, error) for i := int64(0); i < b.chainParams.StakeVersionInterval && iterNode != nil; i++ { versions[iterNode.stakeVersion]++ - var err error - iterNode, err = b.index.PrevNodeFromNode(iterNode) - if err != nil { - return 0, err - } + iterNode = iterNode.parent } // Determine the required amount of votes to reach supermajority. @@ -273,7 +237,6 @@ func (b *BlockChain) calcVoterVersionInterval(prevNode *blockNode) (uint32, erro // Tally both the total number of votes in the previous stake version validation // interval and how many of each version those votes have. - var err error versions := make(map[uint32]int32) // [version][count] totalVotesFound := int32(0) iterNode := prevNode @@ -283,10 +246,7 @@ func (b *BlockChain) calcVoterVersionInterval(prevNode *blockNode) (uint32, erro versions[v.Version]++ } - iterNode, err = b.index.PrevNodeFromNode(iterNode) - if err != nil { - return 0, err - } + iterNode = iterNode.parent } // Determine the required amount of votes to reach supermajority. @@ -311,10 +271,7 @@ func (b *BlockChain) calcVoterVersionInterval(prevNode *blockNode) (uint32, erro // This function MUST be called with the chain state lock held (for writes). func (b *BlockChain) calcVoterVersion(prevNode *blockNode) (uint32, *blockNode) { // Walk blockchain backwards to find interval. - node, err := b.findStakeVersionPriorNode(prevNode) - if err != nil { - return 0, nil - } + node := b.findStakeVersionPriorNode(prevNode) // Iterate over versions until a majority is found. Don't try to count // votes before the stake validation height since there could not @@ -328,26 +285,22 @@ func (b *BlockChain) calcVoterVersion(prevNode *blockNode) (uint32, *blockNode) break } - prevIntervalHeight := node.height - b.chainParams.StakeVersionInterval - node, err = b.index.AncestorNode(node, prevIntervalHeight) - if err != nil { - break - } + node = node.RelativeAncestor(b.chainParams.StakeVersionInterval) } - // We didn't find a marority version. + // No majority version found. return 0, nil } -// calcStakeVersion calculates the header stake version based on voter -// versions. If there is a majority of voter versions it uses the header stake -// version to prevent reverting to a prior version. +// calcStakeVersion calculates the header stake version based on voter versions. +// If there is a majority of voter versions it uses the header stake version to +// prevent reverting to a prior version. // // This function MUST be called with the chain state lock held (for writes). func (b *BlockChain) calcStakeVersion(prevNode *blockNode) uint32 { version, node := b.calcVoterVersion(prevNode) if version == 0 || node == nil { - // short circuit + // Short circuit. return 0 } @@ -361,35 +314,34 @@ func (b *BlockChain) calcStakeVersion(prevNode *blockNode) uint32 { // prior interval; hence the + 1. startIntervalHeight := calcWantHeight(b.chainParams.StakeValidationHeight, b.chainParams.StakeVersionInterval, node.height) + 1 - iterNode := node - for iterNode.height > startIntervalHeight { - var err error - iterNode, err = b.index.PrevNodeFromNode(iterNode) - if err != nil || iterNode == nil { - b.calcStakeVersionCache[node.hash] = 0 - return 0 - } + startNode := node.Ancestor(startIntervalHeight) + if startNode == nil { + // Note that should this not be possible to hit because a + // majority voter version was obtained above, which means there + // is at least an interval of nodes. However, be paranoid. + b.calcStakeVersionCache[node.hash] = 0 } - // See if we are enforcing V3 blocks yet. Just return V0 since it it + // See if we are enforcing V3 blocks yet. Just return V0 since it // wasn't enforced and therefore irrelevant. - if !b.isMajorityVersion(3, iterNode, + if !b.isMajorityVersion(3, startNode, b.chainParams.BlockRejectNumRequired) { b.calcStakeVersionCache[node.hash] = 0 return 0 } - ourVersion := version + // Don't allow the stake version to go backwards once it has been locked + // in by a previous majority, even if the majority of votes are now a + // lower version. if b.isStakeMajorityVersion(version, node) { priorVersion, _ := b.calcPriorStakeVersion(node) - if version <= priorVersion { - ourVersion = priorVersion + if priorVersion > version { + version = priorVersion } } - b.calcStakeVersionCache[node.hash] = ourVersion - - return ourVersion + b.calcStakeVersionCache[node.hash] = version + return version } // calcStakeVersionByHash calculates the last prior valid majority stake @@ -399,16 +351,16 @@ func (b *BlockChain) calcStakeVersion(prevNode *blockNode) uint32 { // // This function MUST be called with the chain state lock held (for writes). func (b *BlockChain) calcStakeVersionByHash(hash *chainhash.Hash) (uint32, error) { - prevNode, err := b.findNode(hash, 0) - if err != nil { - return 0, err + prevNode := b.index.LookupNode(hash) + if prevNode == nil { + return 0, fmt.Errorf("block %s is not known", hash) } return b.calcStakeVersion(prevNode), nil } -// CalcStakeVersionByHash calculates the expected stake version for the -// provided block hash. +// CalcStakeVersionByHash calculates the expected stake version for the block +// AFTER provided block hash. // // This function is safe for concurrent access. func (b *BlockChain) CalcStakeVersionByHash(hash *chainhash.Hash) (uint32, error) { diff --git a/blockchain/stakeversion_test.go b/blockchain/stakeversion_test.go index 1cce8f1884..776838d152 100644 --- a/blockchain/stakeversion_test.go +++ b/blockchain/stakeversion_test.go @@ -254,9 +254,9 @@ func TestCalcStakeVersionCorners(t *testing.T) { } } -// TestCalcStakeVersionByNode ensures that stake version calculation works as +// TestCalcStakeVersion ensures that stake version calculation works as // intended when -func TestCalcStakeVersionByNode(t *testing.T) { +func TestCalcStakeVersion(t *testing.T) { params := &chaincfg.SimNetParams svh := params.StakeValidationHeight svi := params.StakeVersionInterval @@ -718,7 +718,7 @@ func TestLarge(t *testing.T) { // validate calcStakeVersion version := bc.calcStakeVersion(node) if version != test.expectedCalcVersion { - t.Fatalf("%v calcStakeVersionByNode got %v expected %v", + t.Fatalf("%v calcStakeVersion got %v expected %v", test.name, version, test.expectedCalcVersion) } end := time.Now() diff --git a/blockchain/thresholdstate.go b/blockchain/thresholdstate.go index 15b32555db..9657d504e4 100644 --- a/blockchain/thresholdstate.go +++ b/blockchain/thresholdstate.go @@ -233,11 +233,7 @@ func (b *BlockChain) thresholdState(version uint32, prevNode *blockNode, checker wantHeight := calcWantHeight(svh, int64(checker.RuleChangeActivationInterval()), prevNode.height+1) - var err error - prevNode, err = b.index.AncestorNode(prevNode, wantHeight) - if err != nil { - return newThresholdState(ThresholdFailed, invalidChoice), err - } + prevNode = prevNode.Ancestor(wantHeight) // Iterate backwards through each of the previous confirmation windows // to find the most recently cached threshold state. @@ -251,11 +247,7 @@ func (b *BlockChain) thresholdState(version uint32, prevNode *blockNode, checker // The start and expiration times are based on the median block // time, so calculate it now. - medianTime, err := b.index.CalcPastMedianTime(prevNode) - if err != nil { - return newThresholdState(ThresholdFailed, - invalidChoice), err - } + medianTime := prevNode.CalcPastMedianTime() // The state is simply defined if the start time hasn't been // been reached yet. @@ -273,12 +265,7 @@ func (b *BlockChain) thresholdState(version uint32, prevNode *blockNode, checker // Get the ancestor that is the last block of the previous // confirmation window. - prevNode, err = b.index.AncestorNode(prevNode, prevNode.height- - confirmationWindow) - if err != nil { - return newThresholdState(ThresholdFailed, - invalidChoice), err - } + prevNode = prevNode.RelativeAncestor(confirmationWindow) } // Start with the threshold state for the most recent confirmation @@ -310,11 +297,7 @@ func (b *BlockChain) thresholdState(version uint32, prevNode *blockNode, checker // The deployment of the rule change fails if it expires // before it is accepted and locked in. - medianTime, err := b.index.CalcPastMedianTime(prevNode) - if err != nil { - return newThresholdState(ThresholdFailed, - invalidChoice), err - } + medianTime := prevNode.CalcPastMedianTime() medianTimeUnix := uint64(medianTime.Unix()) if medianTimeUnix >= checker.EndTime() { stateTuple.State = ThresholdFailed @@ -346,19 +329,15 @@ func (b *BlockChain) thresholdState(version uint32, prevNode *blockNode, checker case ThresholdStarted: // The deployment of the rule change fails if it expires // before it is accepted and locked in. - medianTime, err := b.index.CalcPastMedianTime(prevNode) - if err != nil { - return newThresholdState(ThresholdFailed, - invalidChoice), err - } + medianTime := prevNode.CalcPastMedianTime() if uint64(medianTime.Unix()) >= checker.EndTime() { stateTuple.State = ThresholdFailed break } // At this point, the rule change is still being voted - // on by the miners, so iterate backwards through the - // confirmation window to count all of the votes in it. + // on, so iterate backwards through the confirmation + // window to count all of the votes in it. var ( counts []thresholdConditionTally totalVotes uint32 @@ -389,17 +368,7 @@ func (b *BlockChain) thresholdState(version uint32, prevNode *blockNode, checker } } - // Get the previous block node. This function - // is used over simply accessing countNode.parent - // directly as it will dynamically create - // previous block nodes as needed. This helps - // allow only the pieces of the chain that are - // needed to remain in memory. - countNode, err = b.index.PrevNodeFromNode(countNode) - if err != nil { - return newThresholdState( - ThresholdFailed, invalidChoice), err - } + countNode = countNode.parent } // Determine if we have reached quorum. @@ -567,14 +536,23 @@ type VoteCounts struct { } // getVoteCounts returns the vote counts for the specified version for the -// current interval. +// current rule change activation interval. // // This function MUST be called with the chain state lock held (for writes). -func (b *BlockChain) getVoteCounts(node *blockNode, version uint32, d chaincfg.ConsensusDeployment) (VoteCounts, error) { - height := calcWantHeight(b.chainParams.StakeValidationHeight, - int64(b.chainParams.RuleChangeActivationInterval), node.height) +func (b *BlockChain) getVoteCounts(node *blockNode, version uint32, d *chaincfg.ConsensusDeployment) (VoteCounts, error) { + // Don't try to count votes before the stake validation height since there + // could not possibly have been any. + svh := b.chainParams.StakeValidationHeight + if node.height < svh { + return VoteCounts{ + VoteChoices: make([]uint32, len(d.Vote.Choices)), + }, nil + } + + // Calculate the final height of the prior interval. + rcai := int64(b.chainParams.RuleChangeActivationInterval) + height := calcWantHeight(svh, rcai, node.height) - var err error result := VoteCounts{ VoteChoices: make([]uint32, len(d.Vote.Choices)), } @@ -600,39 +578,31 @@ func (b *BlockChain) getVoteCounts(node *blockNode, version uint32, d chaincfg.C result.VoteChoices[index]++ } - // Get the previous block node. This function - // is used over simply accessing countNode.parent - // directly as it will dynamically create - // previous block nodes as needed. This helps - // allow only the pieces of the chain that are - // needed to remain in memory. - countNode, err = b.index.PrevNodeFromNode(countNode) - if err != nil { - return VoteCounts{}, err - } + countNode = countNode.parent } return result, nil } // GetVoteCounts returns the vote counts for the specified version and -// deployment identifier for the current interval. +// deployment identifier for the current rule change activation interval. // // This function is safe for concurrent access. func (b *BlockChain) GetVoteCounts(version uint32, deploymentID string) (VoteCounts, error) { for k := range b.chainParams.Deployments[version] { - if b.chainParams.Deployments[version][k].Vote.Id == deploymentID { + deployment := &b.chainParams.Deployments[version][k] + if deployment.Vote.Id == deploymentID { b.chainLock.Lock() - defer b.chainLock.Unlock() - return b.getVoteCounts(b.bestNode, version, - b.chainParams.Deployments[version][k]) + counts, err := b.getVoteCounts(b.bestNode, version, deployment) + b.chainLock.Unlock() + return counts, err } } return VoteCounts{}, DeploymentError(deploymentID) } // CountVoteVersion returns the total number of version votes for the current -// interval. +// rule change activation interval. // // This function is safe for concurrent access. func (b *BlockChain) CountVoteVersion(version uint32) (uint32, error) { @@ -640,11 +610,17 @@ func (b *BlockChain) CountVoteVersion(version uint32) (uint32, error) { defer b.chainLock.Unlock() countNode := b.bestNode - height := calcWantHeight(b.chainParams.StakeValidationHeight, - int64(b.chainParams.RuleChangeActivationInterval), - countNode.height) + // Don't try to count votes before the stake validation height since there + // could not possibly have been any. + svh := b.chainParams.StakeValidationHeight + if countNode.height < svh { + return 0, nil + } + + // Calculate the final height of the prior interval. + rcai := int64(b.chainParams.RuleChangeActivationInterval) + height := calcWantHeight(svh, rcai, countNode.height) - var err error total := uint32(0) for countNode.height > height { for _, vote := range countNode.votes { @@ -657,16 +633,7 @@ func (b *BlockChain) CountVoteVersion(version uint32) (uint32, error) { total++ } - // Get the previous block node. This function - // is used over simply accessing countNode.parent - // directly as it will dynamically create - // previous block nodes as needed. This helps - // allow only the pieces of the chain that are - // needed to remain in memory. - countNode, err = b.index.PrevNodeFromNode(countNode) - if err != nil { - return 0, err - } + countNode = countNode.parent } return total, nil diff --git a/blockchain/utxoviewpoint.go b/blockchain/utxoviewpoint.go index aa3ebcfb8c..521cbe7ed9 100644 --- a/blockchain/utxoviewpoint.go +++ b/blockchain/utxoviewpoint.go @@ -1080,16 +1080,26 @@ func (b *BlockChain) FetchUtxoView(tx *dcrutil.Tx, treeValid bool) (*UtxoViewpoi b.chainLock.RLock() defer b.chainLock.RUnlock() + // The genesis block does not have any spendable transactions, so there + // can't possibly be any details about it. This is also necessary + // because the code below requires the parent block and the genesis + // block doesn't have one. + tip := b.bestNode + view := NewUtxoViewpoint() + if tip.height == 0 { + view.SetBestHash(&tip.hash) + return view, nil + } + // Request the utxos from the point of view of the end of the main // chain. - view := NewUtxoViewpoint() if treeValid { view.SetStakeViewpoint(ViewpointPrevValidRegular) - block, err := b.fetchMainChainBlockByHash(&b.bestNode.hash) + block, err := b.fetchMainChainBlockByHash(&tip.hash) if err != nil { return nil, err } - parent, err := b.fetchMainChainBlockByHash(&b.bestNode.parentHash) + parent, err := b.fetchMainChainBlockByHash(&tip.parent.hash) if err != nil { return nil, err } @@ -1098,14 +1108,14 @@ func (b *BlockChain) FetchUtxoView(tx *dcrutil.Tx, treeValid bool) (*UtxoViewpoi return nil, err } for i, blockTx := range block.Transactions() { - err := view.connectTransaction(blockTx, b.bestNode.height, + err := view.connectTransaction(blockTx, tip.height, uint32(i), nil) if err != nil { return nil, err } } } - view.SetBestHash(&b.bestNode.hash) + view.SetBestHash(&tip.hash) // Create a set of needed transactions based on those referenced by the // inputs of the passed transaction. Also, add the passed transaction diff --git a/blockchain/validate.go b/blockchain/validate.go index 0948941423..10098ea79f 100644 --- a/blockchain/validate.go +++ b/blockchain/validate.go @@ -881,11 +881,7 @@ func (b *BlockChain) checkBlockHeaderContext(header *wire.BlockHeader, prevNode // Ensure the timestamp for the block header is after the // median time of the last several blocks (medianTimeBlocks). - medianTime, err := b.index.CalcPastMedianTime(prevNode) - if err != nil { - log.Errorf("CalcPastMedianTime: %v", err) - return err - } + medianTime := prevNode.CalcPastMedianTime() if !header.Timestamp.After(medianTime) { str := "block timestamp of %v is not after expected %v" str = fmt.Sprintf(str, header.Timestamp, medianTime) @@ -1131,12 +1127,7 @@ func (b *BlockChain) checkBlockContext(block *dcrutil.Block, prevNode *blockNode return err } if lnFeaturesActive { - medianTime, err := b.index.CalcPastMedianTime(prevNode) - if err != nil { - return err - } - - blockTime = medianTime + blockTime = prevNode.CalcPastMedianTime() } // The height of this block is one more than the referenced @@ -2349,11 +2340,11 @@ func (b *BlockChain) checkConnectBlock(node *blockNode, block, parent *dcrutil.B } // Ensure the view is for the node being checked. - if !utxoView.BestHash().IsEqual(&node.parentHash) { + parentHash := &block.MsgBlock().Header.PrevBlock + if !utxoView.BestHash().IsEqual(parentHash) { return AssertError(fmt.Sprintf("inconsistent view when "+ "checking block connection: best hash is %v instead "+ - "of expected %v", utxoView.BestHash(), - node.parentHash)) + "of expected %v", utxoView.BestHash(), parentHash)) } // Check that the coinbase pays the tax, if applicable. @@ -2451,10 +2442,7 @@ func (b *BlockChain) checkConnectBlock(node *blockNode, block, parent *dcrutil.B // Use the past median time of the *previous* block in order // to determine if the transactions in the current block are // final. - prevMedianTime, err = b.index.CalcPastMedianTime(node.parent) - if err != nil { - return err - } + prevMedianTime = node.parent.CalcPastMedianTime() // Skip the coinbase since it does not have any inputs and thus // lock times do not apply. @@ -2590,9 +2578,15 @@ func (b *BlockChain) CheckConnectBlockTemplate(block *dcrutil.Block) error { prevNode = tip.parent } if prevNode == nil { - str := fmt.Sprintf("previous block must be the current chain "+ - "tip %s or its parent %s, but got %s", tip.hash, - tip.parentHash, parentHash) + var str string + if tip.parent != nil { + str = fmt.Sprintf("previous block must be the current chain tip "+ + "%s or its parent %s, but got %s", tip.hash, tip.parent.hash, + parentHash) + } else { + str = fmt.Sprintf("previous block must be the current chain tip "+ + "%s, but got %s", tip.hash, parentHash) + } return ruleError(ErrInvalidTemplateParent, str) } @@ -2631,10 +2625,7 @@ func (b *BlockChain) CheckConnectBlockTemplate(block *dcrutil.Block) error { // the transactions and spend information for the blocks which would be // disconnected during a reorganize to the point of view of the node // just before the requested node. - detachNodes, attachNodes, err := b.getReorganizeNodes(prevNode) - if err != nil { - return err - } + detachNodes, attachNodes := b.getReorganizeNodes(prevNode) view := NewUtxoViewpoint() view.SetBestHash(&tip.hash) @@ -2659,7 +2650,7 @@ func (b *BlockChain) CheckConnectBlockTemplate(block *dcrutil.Block) error { block.Hash()) } - parent, err := b.fetchMainChainBlockByHash(&n.parentHash) + parent, err := b.fetchMainChainBlockByHash(&n.parent.hash) if err != nil { return err } @@ -2714,15 +2705,15 @@ func (b *BlockChain) CheckConnectBlockTemplate(block *dcrutil.Block) error { parent := prevAttachBlock if parent == nil { var err error - parent, err = b.fetchMainChainBlockByHash(&n.parentHash) + parent, err = b.fetchMainChainBlockByHash(&n.parent.hash) if err != nil { return err } } - if n.parentHash != *parent.Hash() { + if n.parent.hash != *parent.Hash() { panicf("attach block node hash %v (height %v) parent hash %v does "+ "not match previous parent block hash %v", &n.hash, n.height, - &n.parentHash, parent.Hash()) + &n.parent.hash, parent.Hash()) } // Store the loaded block for the next iteration.