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:
parent
ecd348b2a7
commit
6a54323258
2 changed files with 55 additions and 35 deletions
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue