Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

blockchain: Infrastructure to manage block index. #1044

Merged
merged 3 commits into from
Feb 23, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 38 additions & 14 deletions blockchain/accept.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,13 +126,37 @@ func (b *BlockChain) maybeAcceptBlock(block *dcrutil.Block, flags BehaviorFlags)
return false, ruleError(ErrMissingParent, str)
}

// 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 false, ruleError(ErrInvalidAncestorBlock, str)
}

// The block must pass all of the validation rules which depend on the
// position of the block within the block chain.
err = b.checkBlockContext(block, prevNode, flags)
if err != nil {
return false, err
}

// Prune stake nodes which are no longer needed before creating a new
// node.
if !dryRun {
b.pruner.pruneChainIfNeeded()
}

// Create a new block node for the block and add it to the block index.
// The block could either be on a side chain or the main chain, but it
// starts off as a side chain regardless.
blockHeader := &block.MsgBlock().Header
newNode := newBlockNode(blockHeader, prevNode)
newNode.populateTicketInfo(stake.FindSpentTicketsInBlock(block.MsgBlock()))
newNode.status = statusDataStored
b.index.AddNode(newNode)

// Insert the block into the database if it's not already there. Even
// though it is possible the block will ultimately fail to connect, it
// has already passed all proof-of-work and validity tests which means
Expand All @@ -142,26 +166,26 @@ func (b *BlockChain) maybeAcceptBlock(block *dcrutil.Block, flags BehaviorFlags)
// expensive connection logic. It also has some other nice properties
// such as making blocks that never become part of the main chain or
// blocks that fail to connect available for further analysis.
//
// Also, store the associated block index entry when not running in dry
// run mode.
err = b.db.Update(func(dbTx database.Tx) error {
return dbMaybeStoreBlock(dbTx, block)
if err := dbMaybeStoreBlock(dbTx, block); err != nil {
return err
}

if !dryRun {
if err := dbPutBlockNode(dbTx, newNode); err != nil {
return err
}
}

return nil
})
if err != nil {
return false, err
}

// Prune stake nodes which are no longer needed before creating a new
// node.
if !dryRun {
b.pruner.pruneChainIfNeeded()
}

// Create a new block node for the block and add it to the in-memory
// block chain (could be either a side chain or the main chain).
blockHeader := &block.MsgBlock().Header
newNode := newBlockNode(blockHeader, prevNode)
newNode.populateTicketInfo(stake.FindSpentTicketsInBlock(block.MsgBlock()))
b.index.AddNode(newNode)

// Remove the node from the block index and disconnect it from the
// parent node when running in dry run mode.
if dryRun {
Expand Down
121 changes: 106 additions & 15 deletions blockchain/blockindex.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,50 @@ import (
"github.com/decred/dcrd/wire"
)

// blockStatus is a bit field representing the validation state of the block.
type blockStatus byte

// The following constants specify possible status bit flags for a block.
//
// NOTE: This section specifically does not use iota since the block status is
// serialized and must be stable for long-term storage.
const (
// statusNone indicates that the block has no validation state flags set.
statusNone blockStatus = 0

// statusDataStored indicates that the block's payload is stored on disk.
statusDataStored blockStatus = 1 << 0

// statusValid indicates that the block has been fully validated.
statusValid blockStatus = 1 << 1

// statusValidateFailed indicates that the block has failed validation.
statusValidateFailed blockStatus = 1 << 2

// statusInvalidAncestor indicates that one of the ancestors of the block
// has failed validation, thus the block is also invalid.
statusInvalidAncestor = 1 << 3
)

// HaveData returns whether the full block data is stored in the database. This
// will return false for a block node where only the header is downloaded or
// stored.
func (status blockStatus) HaveData() bool {
return status&statusDataStored != 0
}

// KnownValid returns whether the block is known to be valid. This will return
// false for a valid block that has not been fully validated yet.
func (status blockStatus) KnownValid() bool {
return status&statusValid != 0
}

// KnownInvalid returns whether the block is known to be invalid. This will
// return false for invalid blocks that have not been proven invalid yet.
func (status blockStatus) KnownInvalid() bool {
return status&(statusValidateFailed|statusInvalidAncestor) != 0
}

// blockNode represents a block within the block chain and is primarily used to
// aid in selecting the best chain to be the main chain. The main chain is
// stored into the block database.
Expand Down Expand Up @@ -75,6 +119,13 @@ type blockNode struct {
extraData [32]byte
stakeVersion uint32

// status is a bitfield representing the validation state of the block.
// This field, unlike the other fields, may be changed after the block
// node is created, so it must only be accessed or updated using the
// concurrent-safe NodeStatus, SetStatusFlags, and UnsetStatusFlags
// methods on blockIndex once the node has been added to the index.
status blockStatus

// 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
Expand All @@ -90,14 +141,15 @@ type blockNode struct {
votes []stake.VoteVersionTuple
}

// newBlockNode returns a new block node for the given block header and parent
// node. The workSum is calculated based on the parent, or, in the case no
// parent is provided, it will just be the work for the passed block.
func newBlockNode(blockHeader *wire.BlockHeader, parent *blockNode) *blockNode {
// Make a copy of the hash so the node doesn't keep a reference to part
// of the full block/block header preventing it from being garbage
// collected.
node := blockNode{
// initBlockNode initializes a block node from the given header, initialization
// vector for the ticket lottery, and parent node. The workSum is calculated
// based on the parent, or, in the case no parent is provided, it will just be
// the work for the passed block.
//
// This function is NOT safe for concurrent access. It must only be called when
// initially creating a node.
func initBlockNode(node *blockNode, blockHeader *wire.BlockHeader, parent *blockNode) {
*node = blockNode{
hash: blockHeader.BlockHash(),
parentHash: blockHeader.PrevBlock,
workSum: CalcWork(blockHeader.Bits),
Expand All @@ -123,7 +175,14 @@ func newBlockNode(blockHeader *wire.BlockHeader, parent *blockNode) *blockNode {
node.parent = parent
node.workSum = node.workSum.Add(parent.workSum, node.workSum)
}
}

// newBlockNode returns a new block node for the given block header and parent
// node. The workSum is calculated based on the parent, or, in the case no
// parent is provided, it will just be the work for the passed block.
func newBlockNode(blockHeader *wire.BlockHeader, parent *blockNode) *blockNode {
var node blockNode
initBlockNode(&node, blockHeader, parent)
return &node
}

Expand Down Expand Up @@ -255,16 +314,18 @@ func (bi *blockIndex) HaveBlock(hash *chainhash.Hash) bool {
// 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) {
// Load the block from the db.
block, err := dbFetchBlockByHash(dbTx, hash)
// 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
}

// Create the new block node for the block.
blockHeader := block.MsgBlock().Header
node := newBlockNode(&blockHeader, nil)
node.populateTicketInfo(stake.FindSpentTicketsInBlock(block.MsgBlock()))
// Load the block node for the provided hash and height from the
// database.
node, err := dbFetchBlockNode(dbTx, hash, uint32(height))
if err != nil {
return nil, err
}
node.inMainChain = true

// Add the node to the chain.
Expand All @@ -273,7 +334,7 @@ func (bi *blockIndex) loadBlockNode(dbTx database.Tx, hash *chainhash.Hash) (*bl
// 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 := &blockHeader.PrevBlock
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
Expand Down Expand Up @@ -474,6 +535,36 @@ func (bi *blockIndex) LookupNode(hash *chainhash.Hash) *blockNode {
return node
}

// NodeStatus returns the status associated with the provided node.
//
// This function is safe for concurrent access.
func (bi *blockIndex) NodeStatus(node *blockNode) blockStatus {
bi.RLock()
status := node.status
bi.RUnlock()
return status
}

// SetStatusFlags sets the provided status flags for the given block node
// regardless of their previous state. It does not unset any flags.
//
// This function is safe for concurrent access.
func (bi *blockIndex) SetStatusFlags(node *blockNode, flags blockStatus) {
bi.Lock()
node.status |= flags
bi.Unlock()
}

// UnsetStatusFlags unsets the provided status flags for the given block node
// regardless of their previous state.
//
// This function is safe for concurrent access.
func (bi *blockIndex) UnsetStatusFlags(node *blockNode, flags blockStatus) {
bi.Lock()
node.status &^= flags
bi.Unlock()
}

// CalcPastMedianTime calculates the median time of the previous few blocks
// prior to, and including, the passed block node.
//
Expand Down
43 changes: 27 additions & 16 deletions blockchain/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,12 @@ func (b *BlockChain) getReorganizeNodes(node *blockNode) (*list.List, *list.List
return detachNodes, attachNodes, nil
}

// 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
}

// Find the fork point (if any) adding each block to the list of nodes
// to attach to the main tree. Push them onto the list in reverse order
// so they are attached in the appropriate order when iterating the list
Expand Down Expand Up @@ -799,20 +805,6 @@ func (b *BlockChain) pushMainChainBlockCache(block *dcrutil.Block) {
b.mainchainBlockCacheLock.Unlock()
}

// dbMaybeStoreBlock stores the provided block in the database if it's not
// already there.
func dbMaybeStoreBlock(dbTx database.Tx, block *dcrutil.Block) error {
hasBlock, err := dbTx.HasBlock(block.Hash())
if err != nil {
return err
}
if hasBlock {
return nil
}

return dbTx.StoreBlock(block)
}

// connectBlock handles connecting the passed node/block to the end of the main
// (best) chain.
//
Expand Down Expand Up @@ -879,6 +871,16 @@ func (b *BlockChain) connectBlock(node *blockNode, block, parent *dcrutil.Block,
return err
}

// Add the block to the block index. Ultimately the block index
// should track modified nodes and persist all of them prior
// this point as opposed to unconditionally peristing the node
// again. However, this is needed for now in lieu of that to
// ensure the updated status is written to the database.
err = dbPutBlockNode(dbTx, node)
if err != nil {
return err
}

// Add the block hash and height to the main chain index.
err = dbPutMainChainIndex(dbTx, block.Hash(), node.height)
if err != nil {
Expand Down Expand Up @@ -1616,6 +1618,10 @@ func (b *BlockChain) connectBestChain(node *blockNode, block, parent *dcrutil.Bl
// We are extending the main (best) chain with a new block. This is the
// most common case.
if node.parentHash == b.bestNode.hash {
// Skip expensive checks if the block has already been fully
// validated.
fastAdd = fastAdd || b.index.NodeStatus(node).KnownValid()

// Perform several checks to verify the block can be connected
// to the main chain without violating any rules and without
// actually connecting the block.
Expand All @@ -1627,8 +1633,12 @@ func (b *BlockChain) connectBestChain(node *blockNode, block, parent *dcrutil.Bl
err := b.checkConnectBlock(node, block, parent, view,
&stxos)
if err != nil {
if _, ok := err.(RuleError); ok {
b.index.SetStatusFlags(node, statusValidateFailed)
}
return false, err
}
b.index.SetStatusFlags(node, statusValid)
}

// Don't connect the block if performing a dry run.
Expand Down Expand Up @@ -1994,8 +2004,9 @@ func New(config *Config) (*BlockChain, error) {
b.subsidyCache = NewSubsidyCache(b.bestNode.height, b.chainParams)
b.pruner = newChainPruner(&b)

log.Infof("Blockchain database version %v loaded",
b.dbInfo.version)
log.Infof("Blockchain database version info: chain: %d, compression: "+
"%d, block index: %d", b.dbInfo.version, b.dbInfo.compVer,
b.dbInfo.bidxVer)

log.Infof("Chain state: height %d, hash %v, total transactions %d, "+
"work %v, stake version %v", b.bestNode.height, b.bestNode.hash,
Expand Down
Loading