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.