blockchain: Store side chain blocks in database.

This modifies the blockchain code to store all blocks that have passed
proof-of-work and contextual validity tests in the database even if they
may ultimately fail to connect.

This eliminates the need to store those blocks in memory, allows them to
be available as orphans later even if they were never part of the main
chain, and helps pave the way toward being able to separate the download
logic from the connection logic.
This commit is contained in:
Dave Collins 2017-01-29 11:20:10 -06:00
parent ecd348b2a7
commit 6a54323258
No known key found for this signature in database
GPG key ID: B8904D9D9C93D1F2
2 changed files with 55 additions and 35 deletions

View file

@ -4,7 +4,10 @@
package blockchain
import "github.com/btcsuite/btcutil"
import (
"github.com/btcsuite/btcd/database"
"github.com/btcsuite/btcutil"
)
// maybeAcceptBlock potentially accepts a block into the block chain and, if
// accepted, returns whether or not it is on the main chain. It performs
@ -46,6 +49,22 @@ func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, flags BehaviorFlags)
return false, err
}
// 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
// it would be prohibitively expensive for an attacker to fill up the
// disk with a bunch of blocks that fail to connect. This is necessary
// since it allows block download to be decoupled from the much more
// 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.
err = b.db.Update(func(dbTx database.Tx) error {
return dbMaybeStoreBlock(dbTx, block)
})
if err != nil {
return false, err
}
// 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

View file

@ -203,7 +203,6 @@ type BlockChain struct {
orphans map[chainhash.Hash]*orphanBlock
prevOrphans map[chainhash.Hash][]*orphanBlock
oldestOrphan *orphanBlock
blockCache map[chainhash.Hash]*btcutil.Block
// These fields are related to checkpoint handling. They are protected
// by the chain lock.
@ -1027,12 +1026,6 @@ func (b *BlockChain) connectBlock(node *blockNode, block *btcutil.Block, view *U
return err
}
// Insert the block into the database if it's not already there.
err = dbMaybeStoreBlock(dbTx, block)
if err != nil {
return err
}
// Allow the index manager to call each of the currently active
// optional indexes with the block being connected so they can
// update themselves accordingly.
@ -1183,9 +1176,8 @@ func (b *BlockChain) disconnectBlock(node *blockNode, block *btcutil.Block, view
// now that the modifications have been committed to the database.
view.commit()
// Put block in the side chain cache.
// Mark block as being in a side chain.
node.inMainChain = false
b.blockCache[*node.hash] = block
// This node's parent is now the end of the best chain.
b.bestNode = node.parent
@ -1233,15 +1225,6 @@ func countSpentOutputs(block *btcutil.Block) int {
//
// This function MUST be called with the chain state lock held (for writes).
func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List, flags BehaviorFlags) error {
// Ensure all of the needed side chain blocks are in the cache.
for e := attachNodes.Front(); e != nil; e = e.Next() {
n := e.Value.(*blockNode)
if _, exists := b.blockCache[*n.hash]; !exists {
return AssertError(fmt.Sprintf("block %v is missing "+
"from the side chain block cache", n.hash))
}
}
// All of the blocks to detach and related spend journal entries needed
// to unspend transaction outputs in the blocks being disconnected must
// be loaded from the database during the reorg check phase below and
@ -1249,6 +1232,7 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List, flags
// Rather than doing two loads, cache the loaded data into these slices.
detachBlocks := make([]*btcutil.Block, 0, detachNodes.Len())
detachSpentTxOuts := make([][]spentTxOut, 0, detachNodes.Len())
attachBlocks := make([]*btcutil.Block, 0, attachNodes.Len())
// Disconnect all of the blocks back to the point of the fork. This
// entails loading the blocks and their associated spent txos from the
@ -1264,6 +1248,9 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List, flags
block, err = dbFetchBlockByHash(dbTx, n.hash)
return err
})
if err != nil {
return err
}
// Load all of the utxos referenced by the block that aren't
// already in the view.
@ -1307,13 +1294,35 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List, flags
// issues before ever modifying the chain.
for e := attachNodes.Front(); e != nil; e = e.Next() {
n := e.Value.(*blockNode)
block := b.blockCache[*n.hash]
var block *btcutil.Block
err := b.db.View(func(dbTx database.Tx) error {
// NOTE: This block is not in the main chain, so the
// block has to be loaded directly from the database
// instead of using the dbFetchBlockByHash function.
blockBytes, err := dbTx.FetchBlock(n.hash)
if err != nil {
return err
}
block, err = btcutil.NewBlockFromBytes(blockBytes)
if err != nil {
return err
}
block.SetHeight(n.height)
return nil
})
if err != nil {
return err
}
// Store the loaded block for later.
attachBlocks = append(attachBlocks, block)
// Notice the spent txout details are not requested here and
// thus will not be generated. This is done because the state
// is not being immediately written to the database, so it is
// not needed.
err := b.checkConnectBlock(n, block, view, nil)
err = b.checkConnectBlock(n, block, view, nil)
if err != nil {
return err
}
@ -1360,9 +1369,9 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List, flags
}
// Connect the new best chain blocks.
for e := attachNodes.Front(); e != nil; e = e.Next() {
for i, e := 0, attachNodes.Front(); e != nil; i, e = i+1, e.Next() {
n := e.Value.(*blockNode)
block := b.blockCache[*n.hash]
block := attachBlocks[i]
// Load all of the utxos referenced by the block that aren't
// already in the view.
@ -1386,7 +1395,6 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List, flags
if err != nil {
return err
}
delete(b.blockCache, *n.hash)
}
// Log the point where the chain forked.
@ -1481,21 +1489,16 @@ func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, fla
}
// We're extending (or creating) a side chain which may or may not
// become the main chain, but in either case we need the block stored
// for future processing, so add the block to the side chain holding
// cache.
if !dryRun {
log.Debugf("Adding block %v to side chain cache", node.hash)
}
b.blockCache[*node.hash] = block
// become the main chain, but in either case the entry is needed in the
// index for future processing.
b.index[*node.hash] = node
// Connect the parent node to this node.
node.inMainChain = false
node.parent.children = append(node.parent.children, node)
// Remove the block from the side chain cache and disconnect it from the
// parent node when the function returns when running in dry run mode.
// Disconnect it from the parent node when the function returns when
// running in dry run mode.
if dryRun {
defer func() {
children := node.parent.children
@ -1503,7 +1506,6 @@ func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, fla
node.parent.children = children
delete(b.index, *node.hash)
delete(b.blockCache, *node.hash)
}()
}
@ -1735,7 +1737,6 @@ func New(config *Config) (*BlockChain, error) {
depNodes: make(map[chainhash.Hash][]*blockNode),
orphans: make(map[chainhash.Hash]*orphanBlock),
prevOrphans: make(map[chainhash.Hash][]*orphanBlock),
blockCache: make(map[chainhash.Hash]*btcutil.Block),
warningCaches: newThresholdCaches(vbNumBits),
deploymentCaches: newThresholdCaches(chaincfg.DefinedDeployments),
}