* [DEV-44] Got rid of reorganize and some things related to best/side chains.

This commit is contained in:
stasatdaglabs 2018-07-03 18:08:17 +03:00 committed by Svarog
parent a4eed8bf99
commit c9c81e1a82
11 changed files with 106 additions and 465 deletions

View File

@ -11,22 +11,21 @@ import (
"github.com/daglabs/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
// several validation checks which depend on its position within the block chain
// before adding it. The block is expected to have already gone through
// ProcessBlock before calling this function with it.
// maybeAcceptBlock potentially accepts a block into the block DAG. It
// performs several validation checks which depend on its position within
// the block DAG before adding it. The block is expected to have already
// gone through ProcessBlock before calling this function with it.
//
// The flags are also passed to checkBlockContext and connectBestChain. See
// The flags are also passed to checkBlockContext and connectToDAG. See
// their documentation for how the flags modify their behavior.
//
// This function MUST be called with the chain state lock held (for writes).
func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, flags BehaviorFlags) (bool, error) {
func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, flags BehaviorFlags) error {
// The height of this block is one more than the referenced previous
// block.
parents, err := lookupPreviousNodes(block, b)
if err != nil {
return false, err
return err
}
selectedParent := parents.first()
@ -37,7 +36,7 @@ func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, flags BehaviorFlags)
// position of the block within the block chain.
err = b.checkBlockContext(block, selectedParent, flags)
if err != nil {
return false, err
return err
}
// Insert the block into the database if it's not already there. Even
@ -53,7 +52,7 @@ func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, flags BehaviorFlags)
return dbStoreBlock(dbTx, block)
})
if err != nil {
return false, err
return err
}
// Create a new block node for the block and add it to the node index. Even
@ -66,15 +65,14 @@ func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, flags BehaviorFlags)
b.index.AddNode(newNode)
err = b.index.flushToDB()
if err != nil {
return false, err
return err
}
// Connect the passed block to the chain while respecting proper chain
// selection according to the chain with the most proof of work. This
// also handles validation of the transaction scripts.
isMainChain, err := b.connectBestChain(newNode, parents, block, flags)
// Connect the passed block to the DAG. This also handles validation of the
// transaction scripts.
err = b.connectToDAG(newNode, parents, block, flags)
if err != nil {
return false, err
return err
}
// Notify the caller that the new block was accepted into the block
@ -84,7 +82,7 @@ func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, flags BehaviorFlags)
b.sendNotification(NTBlockAccepted, block)
b.chainLock.Lock()
return isMainChain, nil
return nil
}
func lookupPreviousNodes(block *btcutil.Block, blockChain *BlockChain) (blockSet, error) {

View File

@ -5,7 +5,6 @@
package blockdag
import (
"container/list"
"fmt"
"sync"
"time"
@ -484,65 +483,6 @@ func LockTimeToSequence(isSeconds bool, locktime uint32) uint32 {
locktime>>wire.SequenceLockTimeGranularity
}
// getReorganizeNodes finds the fork point between the main chain and the passed
// node and returns a list of block nodes that would need to be detached from
// the main chain and a list of block nodes that would need to be attached to
// the fork point (which will be the end of the main chain after detaching the
// returned list of block nodes) in order to reorganize the chain such that the
// passed node is the new end of the main chain. The lists will be empty if the
// passed node is not on a side chain.
//
// This function may modify node statuses in the block index without flushing.
//
// This function MUST be called with the chain state lock held (for reads).
func (b *BlockChain) getReorganizeNodes(node *blockNode) (*list.List, *list.List) {
attachNodes := list.New()
detachNodes := list.New()
// Do not reorganize to a known invalid chain. Ancestors deeper than the
// direct parent are checked below but this is a quick check before doing
// more unnecessary work.
if b.index.NodeStatus(node.selectedParent).KnownInvalid() {
b.index.SetStatusFlags(node, statusInvalidAncestor)
return detachNodes, attachNodes
}
// 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
// later.
forkNode := b.bestChain.FindFork(node)
invalidChain := false
for n := node; n != nil && n != forkNode; n = n.selectedParent {
if b.index.NodeStatus(n).KnownInvalid() {
invalidChain = true
break
}
attachNodes.PushFront(n)
}
// If any of the node's ancestors are invalid, unwind attachNodes, marking
// each one as invalid for future reference.
if invalidChain {
var next *list.Element
for e := attachNodes.Front(); e != nil; e = next {
next = e.Next()
n := attachNodes.Remove(e).(*blockNode)
b.index.SetStatusFlags(n, statusInvalidAncestor)
}
return detachNodes, attachNodes
}
// 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.
for n := b.bestChain.SelectedTip(); n != nil && n != forkNode; n = n.selectedParent {
detachNodes.PushBack(n)
}
return detachNodes, attachNodes
}
// connectBlock handles connecting the passed node/block to the end of the main
// (best) chain.
//
@ -793,345 +733,69 @@ func countSpentOutputs(block *btcutil.Block) int {
return numSpent
}
// reorganizeChain reorganizes the block chain by disconnecting the nodes in the
// detachNodes list and connecting the nodes in the attach list. It expects
// that the lists are already in the correct order and are in sync with the
// end of the current best chain. Specifically, nodes that are being
// disconnected must be in reverse order (think of popping them off the end of
// the chain) and nodes the are being attached must be in forwards order
// (think pushing them onto the end of the chain).
//
// This function may modify node statuses in the block index without flushing.
//
// This function MUST be called with the chain state lock held (for writes).
func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error {
// 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
// then they are needed again when doing the actual database updates.
// 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
// database and using that information to unspend all of the spent txos
// and remove the utxos created by the blocks.
view := NewUtxoViewpoint()
view.SetTips(b.bestChain.Tips())
for element := detachNodes.Front(); element != nil; element = element.Next() {
node := element.Value.(*blockNode)
var block *btcutil.Block
err := b.db.View(func(dbTx database.Tx) error {
var err error
block, err = dbFetchBlockByNode(dbTx, node)
return err
})
if err != nil {
return err
}
// Load all of the utxos referenced by the block that aren't
// already in the view.
err = view.fetchInputUtxos(b.db, block)
if err != nil {
return err
}
// Load all of the spent txos for the block from the spend
// journal.
var stxos []spentTxOut
err = b.db.View(func(dbTx database.Tx) error {
stxos, err = dbFetchSpendJournalEntry(dbTx, block)
return err
})
if err != nil {
return err
}
// Store the loaded block and spend journal entry for later.
detachBlocks = append(detachBlocks, block)
detachSpentTxOuts = append(detachSpentTxOuts, stxos)
err = view.disconnectTransactions(b.db, node.parents, block, stxos)
if err != nil {
return err
}
}
// Perform several checks to verify each block that needs to be attached
// to the main chain can be connected without violating any rules and
// without actually connecting the block.
//
// NOTE: These checks could be done directly when connecting a block,
// however the downside to that approach is that if any of these checks
// fail after disconnecting some blocks or attaching others, all of the
// operations have to be rolled back to get the chain back into the
// state it was before the rule violation (or other failure). There are
// at least a couple of ways accomplish that rollback, but both involve
// tweaking the chain and/or database. This approach catches these
// issues before ever modifying the chain.
var validationError error
for element := attachNodes.Front(); element != nil; element = element.Next() {
node := element.Value.(*blockNode)
// If any previous nodes in attachNodes failed validation,
// mark this one as having an invalid ancestor.
if validationError != nil {
b.index.SetStatusFlags(node, statusInvalidAncestor)
continue
}
var block *btcutil.Block
err := b.db.View(func(dbTx database.Tx) error {
var err error
block, err = dbFetchBlockByNode(dbTx, node)
return err
})
if err != nil {
return err
}
// Store the loaded block for later.
attachBlocks = append(attachBlocks, block)
// Skip checks if node has already been fully validated. Although
// checkConnectBlock gets skipped, we still need to update the UTXO
// view.
if b.index.NodeStatus(node).KnownValid() {
err = view.fetchInputUtxos(b.db, block)
if err != nil {
return err
}
err = view.connectTransactions(node, block.Transactions(), nil)
if err != nil {
return err
}
continue
}
// 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(node, block, view, nil)
if err != nil {
// If the block failed validation mark it as invalid, then
// continue to loop through remaining nodes, marking them as
// having an invalid ancestor.
if _, ok := err.(RuleError); ok {
b.index.SetStatusFlags(node, statusValidateFailed)
validationError = err
continue
}
return err
}
b.index.SetStatusFlags(node, statusValid)
}
if validationError != nil {
return validationError
}
// Reset the view for the actual connection code below. This is
// required because the view was previously modified when checking if
// the reorg would be successful and the connection code requires the
// view to be valid from the viewpoint of each block being connected or
// disconnected.
view = NewUtxoViewpoint()
view.SetTips(b.bestChain.Tips())
// Disconnect blocks from the main chain.
for i, element := 0, detachNodes.Front(); element != nil; i, element = i+1, element.Next() {
node := element.Value.(*blockNode)
block := detachBlocks[i]
// Load all of the utxos referenced by the block that aren't
// already in the view.
err := view.fetchInputUtxos(b.db, block)
if err != nil {
return err
}
// Update the view to unspend all of the spent txos and remove
// the utxos created by the block.
err = view.disconnectTransactions(b.db, node.parents, block,
detachSpentTxOuts[i])
if err != nil {
return err
}
// Update the database and chain state.
err = b.disconnectBlock(node, block, view)
if err != nil {
return err
}
}
// Connect the new best chain blocks.
for i, element := 0, attachNodes.Front(); element != nil; i, element = i+1, element.Next() {
node := element.Value.(*blockNode)
block := attachBlocks[i]
// Load all of the utxos referenced by the block that aren't
// already in the view.
err := view.fetchInputUtxos(b.db, block)
if err != nil {
return err
}
// Update the view to mark all utxos referenced by the block
// as spent and add all transactions being created by this block
// to it. Also, provide an stxo slice so the spent txout
// details are generated.
stxos := make([]spentTxOut, 0, countSpentOutputs(block))
err = view.connectTransactions(node, block.Transactions(), &stxos)
if err != nil {
return err
}
// Update the database and chain state.
err = b.connectBlock(node, block, view, stxos)
if err != nil {
return err
}
}
// Log the point where the chain forked and old and new best chain
// heads.
firstAttachNode := attachNodes.Front().Value.(*blockNode)
firstDetachNode := detachNodes.Front().Value.(*blockNode)
lastAttachNode := attachNodes.Back().Value.(*blockNode)
log.Infof("REORGANIZE: Chain forks at %v", firstAttachNode.selectedParent.hash)
log.Infof("REORGANIZE: Old best chain head was %v", firstDetachNode.hash)
log.Infof("REORGANIZE: New best chain head is %v", lastAttachNode.hash)
return nil
}
// connectBestChain handles connecting the passed block to the chain while
// respecting proper chain selection according to the chain with the most
// proof of work. In the typical case, the new block simply extends the main
// chain. However, it may also be extending (or creating) a side chain (fork)
// which may or may not end up becoming the main chain depending on which fork
// cumulatively has the most proof of work. It returns whether or not the block
// ended up on the main chain (either due to extending the main chain or causing
// a reorganization to become the main chain).
// connectToDAG handles connecting the passed block to the DAG.
//
// The flags modify the behavior of this function as follows:
// - BFFastAdd: Avoids several expensive transaction validation operations.
// This is useful when using checkpoints.
//
// This function MUST be called with the chain state lock held (for writes).
func (b *BlockChain) connectBestChain(node *blockNode, parentNodes blockSet, block *btcutil.Block, flags BehaviorFlags) (bool, error) {
fastAdd := flags&BFFastAdd == BFFastAdd
func (b *BlockChain) connectToDAG(node *blockNode, parentNodes blockSet, block *btcutil.Block, flags BehaviorFlags) error {
// Skip checks if node has already been fully validated.
fastAdd := flags&BFFastAdd == BFFastAdd || b.index.NodeStatus(node).KnownValid()
// We are extending the main (best) chain with a new block. This is the
// most common case.
parentHashes := block.MsgBlock().Header.PrevBlocks
if b.bestChain.Tips().hashesEqual(parentHashes) {
// Skip checks if node 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.
view := NewUtxoViewpoint()
view.SetTips(parentNodes)
stxos := make([]spentTxOut, 0, countSpentOutputs(block))
if !fastAdd {
err := b.checkConnectBlock(node, block, view, &stxos)
if err == nil {
b.index.SetStatusFlags(node, statusValid)
} else if _, ok := err.(RuleError); ok {
b.index.SetStatusFlags(node, statusValidateFailed)
} else {
return false, err
}
// Intentionally ignore errors writing updated node status to DB. If
// it fails to write, it's not the end of the world. If the block is
// valid, we flush in connectBlock and if the block is invalid, the
// worst that can happen is we revalidate the block after a restart.
if writeErr := b.index.flushToDB(); writeErr != nil {
log.Warnf("Error flushing block index changes to disk: %v",
writeErr)
}
if err != nil {
return false, err
}
}
// In the fast add case the code to check the block connection
// was skipped, so the utxo view needs to load the referenced
// utxos, spend them, and add the new utxos being created by
// this block.
if fastAdd {
err := view.fetchInputUtxos(b.db, block)
if err != nil {
return false, err
}
err = view.connectTransactions(node, block.Transactions(), &stxos)
if err != nil {
return false, err
}
}
// Connect the block to the main chain.
err := b.connectBlock(node, block, view, stxos)
if err != nil {
return false, err
}
return true, nil
}
if fastAdd {
log.Warnf("fastAdd set in the side chain case? %v\n",
block.Hash())
}
// We're extending (or creating) a side chain, but the cumulative
// work for this new side chain is not enough to make it the new chain.
if node.workSum.Cmp(b.bestChain.SelectedTip().workSum) <= 0 {
// Log information about how the block is forking the chain.
fork := b.bestChain.FindFork(node)
if fork.hash.IsEqual(block.MsgBlock().Header.SelectedPrevBlock()) {
log.Infof("FORK: Block %v forks the chain at height %d"+
"/block %v, but does not cause a reorganize",
node.hash, fork.height, fork.hash)
// Perform several checks to verify the block can be connected
// to the DAG without violating any rules and without actually
// connecting the block.
view := NewUtxoViewpoint()
view.SetTips(parentNodes)
stxos := make([]spentTxOut, 0, countSpentOutputs(block))
if !fastAdd {
err := b.checkConnectBlock(node, block, view, &stxos)
if err == nil {
b.index.SetStatusFlags(node, statusValid)
} else if _, ok := err.(RuleError); ok {
b.index.SetStatusFlags(node, statusValidateFailed)
} else {
log.Infof("EXTEND FORK: Block %v extends a side chain "+
"which forks the chain at height %d/block %v",
node.hash, fork.height, fork.hash)
return err
}
return false, nil
// Intentionally ignore errors writing updated node status to DB. If
// it fails to write, it's not the end of the world. If the block is
// valid, we flush in connectBlock and if the block is invalid, the
// worst that can happen is we revalidate the block after a restart.
if writeErr := b.index.flushToDB(); writeErr != nil {
log.Warnf("Error flushing block index changes to disk: %v",
writeErr)
}
if err != nil {
return err
}
}
// We're extending (or creating) a side chain and the cumulative work
// for this new side chain is more than the old best chain, so this side
// chain needs to become the main chain. In order to accomplish that,
// find the common ancestor of both sides of the fork, disconnect the
// 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 := b.getReorganizeNodes(node)
// Reorganize the chain.
log.Infof("REORGANIZE: Block %v is causing a reorganize.", node.hash)
err := b.reorganizeChain(detachNodes, attachNodes)
// Either getReorganizeNodes or reorganizeChain could have made unsaved
// changes to the block index, so flush regardless of whether there was an
// error. The index would only be dirty if the block failed to connect, so
// we can ignore any errors writing.
if writeErr := b.index.flushToDB(); writeErr != nil {
log.Warnf("Error flushing block index changes to disk: %v", writeErr)
// In the fast add case the code to check the block connection
// was skipped, so the utxo view needs to load the referenced
// utxos, spend them, and add the new utxos being created by
// this block.
if fastAdd {
err := view.fetchInputUtxos(b.db, block)
if err != nil {
return err
}
err = view.connectTransactions(node, block.Transactions(), &stxos)
if err != nil {
return err
}
}
return err == nil, err
// Connect the block to the DAG.
err := b.connectBlock(node, block, view, stxos)
if err != nil {
return err
}
return nil
}
// isCurrent returns whether or not the chain believes it is current. Several

View File

@ -49,7 +49,7 @@ func TestHaveBlock(t *testing.T) {
chain.TstSetCoinbaseMaturity(1)
for i := 1; i < len(blocks); i++ {
_, isOrphan, err := chain.ProcessBlock(blocks[i], BFNone)
isOrphan, err := chain.ProcessBlock(blocks[i], BFNone)
if err != nil {
t.Errorf("ProcessBlock fail on block %v: %v\n", i, err)
return
@ -62,7 +62,7 @@ func TestHaveBlock(t *testing.T) {
}
// Insert an orphan block.
_, isOrphan, err := chain.ProcessBlock(btcutil.NewBlock(&Block100000),
isOrphan, err := chain.ProcessBlock(btcutil.NewBlock(&Block100000),
BFNone)
if err != nil {
t.Errorf("Unable to process block: %v", err)

View File

@ -59,13 +59,12 @@ func ExampleBlockChain_ProcessBlock() {
// cause an error by trying to process the genesis block which already
// exists.
genesisBlock := btcutil.NewBlock(dagconfig.MainNetParams.GenesisBlock)
isMainChain, isOrphan, err := chain.ProcessBlock(genesisBlock,
isOrphan, err := chain.ProcessBlock(genesisBlock,
blockdag.BFNone)
if err != nil {
fmt.Printf("Failed to process block: %v\n", err)
return
}
fmt.Printf("Block accepted. Is it on the main chain?: %v", isMainChain)
fmt.Printf("Block accepted. Is it an orphan?: %v", isOrphan)
// Output:

View File

@ -156,7 +156,7 @@ func TestFullBlocks(t *testing.T) {
t.Logf("Testing block %s (hash %s, height %d)",
item.Name, block.Hash(), blockHeight)
isMainChain, isOrphan, err := chain.ProcessBlock(block,
isOrphan, err := chain.ProcessBlock(block,
blockdag.BFNone)
if err != nil {
t.Fatalf("block %q (hash %s, height %d) should "+
@ -164,14 +164,6 @@ func TestFullBlocks(t *testing.T) {
block.Hash(), blockHeight, err)
}
// Ensure the main chain and orphan flags match the values
// specified in the test.
if isMainChain != item.IsMainChain {
t.Fatalf("block %q (hash %s, height %d) unexpected main "+
"chain flag -- got %v, want %v", item.Name,
block.Hash(), blockHeight, isMainChain,
item.IsMainChain)
}
if isOrphan != item.IsOrphan {
t.Fatalf("block %q (hash %s, height %d) unexpected "+
"orphan flag -- got %v, want %v", item.Name,
@ -190,7 +182,7 @@ func TestFullBlocks(t *testing.T) {
t.Logf("Testing block %s (hash %s, height %d)",
item.Name, block.Hash(), blockHeight)
_, _, err := chain.ProcessBlock(block, blockdag.BFNone)
_, err := chain.ProcessBlock(block, blockdag.BFNone)
if err == nil {
t.Fatalf("block %q (hash %s, height %d) should not "+
"have been accepted", item.Name, block.Hash(),
@ -247,7 +239,7 @@ func TestFullBlocks(t *testing.T) {
t.Logf("Testing block %s (hash %s, height %d)",
item.Name, block.Hash(), blockHeight)
_, isOrphan, err := chain.ProcessBlock(block, blockdag.BFNone)
isOrphan, err := chain.ProcessBlock(block, blockdag.BFNone)
if err != nil {
// Ensure the error code is of the expected type.
if _, ok := err.(blockdag.RuleError); !ok {

View File

@ -64,11 +64,10 @@ type TestInstance interface {
// the blockchain either by extending the main chain, on a side chain, or as an
// orphan.
type AcceptedBlock struct {
Name string
Block *wire.MsgBlock
Height int32
IsMainChain bool
IsOrphan bool
Name string
Block *wire.MsgBlock
Height int32
IsOrphan bool
}
// Ensure AcceptedBlock implements the TestInstance interface.
@ -836,10 +835,9 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) {
//
// expectTipBlock creates a test instance that expects the provided
// block to be the current tip of the block chain.
acceptBlock := func(blockName string, block *wire.MsgBlock, isMainChain, isOrphan bool) TestInstance {
acceptBlock := func(blockName string, block *wire.MsgBlock, isOrphan bool) TestInstance {
blockHeight := g.blockHeights[blockName]
return AcceptedBlock{blockName, block, blockHeight, isMainChain,
isOrphan}
return AcceptedBlock{blockName, block, blockHeight, isOrphan}
}
rejectBlock := func(blockName string, block *wire.MsgBlock, code blockdag.ErrorCode) TestInstance {
blockHeight := g.blockHeights[blockName]
@ -882,12 +880,12 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) {
// test instance for the current tip.
accepted := func() {
tests = append(tests, []TestInstance{
acceptBlock(g.tipName, g.tip, true, false),
acceptBlock(g.tipName, g.tip, false),
})
}
acceptedToSideChainWithExpectedTip := func(tipName string) {
tests = append(tests, []TestInstance{
acceptBlock(g.tipName, g.tip, false, false),
acceptBlock(g.tipName, g.tip, false),
expectTipBlock(tipName, g.blocksByName[tipName]),
})
}
@ -920,7 +918,7 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) {
g.nextBlock(blockName, nil)
g.saveTipCoinbaseOut()
testInstances = append(testInstances, acceptBlock(g.tipName,
g.tip, true, false))
g.tip, false))
}
tests = append(tests, testInstances)
@ -1037,8 +1035,8 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) {
b13 := g.nextBlock("b13", outs[4])
b14 := g.nextBlock("b14", outs[5], additionalCoinbase(1))
tests = append(tests, []TestInstance{
acceptBlock("b13", b13, false, true),
acceptBlock("b14", b14, false, true),
acceptBlock("b13", b13, true),
acceptBlock("b14", b14, true),
rejectBlock("b12", b12, blockdag.ErrBadCoinbaseValue),
expectTipBlock("b13", b13),
})
@ -2081,7 +2079,7 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) {
g.assertTipBlockSize(maxBlockSize)
g.saveTipCoinbaseOut()
testInstances = append(testInstances, acceptBlock(g.tipName,
g.tip, true, false))
g.tip, false))
// Use the next available spendable output. First use up any
// remaining spendable outputs that were already popped into the
@ -2105,7 +2103,7 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) {
chain2TipName = fmt.Sprintf("bralt%d", i)
g.nextBlock(chain2TipName, nil)
testInstances = append(testInstances, acceptBlock(g.tipName,
g.tip, false, false))
g.tip, false))
}
testInstances = append(testInstances, expectTipBlock(chain1TipName,
g.blocksByName[chain1TipName]))

View File

@ -39,7 +39,7 @@ func TestNotifications(t *testing.T) {
chain.Subscribe(callback)
}
_, _, err = chain.ProcessBlock(blocks[1], BFNone)
_, err = chain.ProcessBlock(blocks[1], BFNone)
if err != nil {
t.Fatalf("ProcessBlock fail on block 1: %v\n", err)
}

View File

@ -115,7 +115,7 @@ func (b *BlockChain) processOrphans(hash *daghash.Hash, flags BehaviorFlags) err
i--
// Potentially accept the block into the block chain.
_, err := b.maybeAcceptBlock(orphan.block, flags)
err := b.maybeAcceptBlock(orphan.block, flags)
if err != nil {
return err
}
@ -132,14 +132,13 @@ func (b *BlockChain) processOrphans(hash *daghash.Hash, flags BehaviorFlags) err
// ProcessBlock is the main workhorse for handling insertion of new blocks into
// the block chain. It includes functionality such as rejecting duplicate
// blocks, ensuring blocks follow all rules, orphan handling, and insertion into
// the block chain along with best chain selection and reorganization.
// the block DAG.
//
// When no errors occurred during processing, the first return value indicates
// whether or not the block is on the main chain and the second indicates
// whether or not the block is an orphan.
//
// This function is safe for concurrent access.
func (b *BlockChain) ProcessBlock(block *btcutil.Block, flags BehaviorFlags) (bool, bool, error) {
func (b *BlockChain) ProcessBlock(block *btcutil.Block, flags BehaviorFlags) (bool, error) {
b.chainLock.Lock()
defer b.chainLock.Unlock()
@ -151,23 +150,23 @@ func (b *BlockChain) ProcessBlock(block *btcutil.Block, flags BehaviorFlags) (bo
// The block must not already exist in the main chain or side chains.
exists, err := b.blockExists(blockHash)
if err != nil {
return false, false, err
return false, err
}
if exists {
str := fmt.Sprintf("already have block %v", blockHash)
return false, false, ruleError(ErrDuplicateBlock, str)
return false, ruleError(ErrDuplicateBlock, str)
}
// The block must not already exist as an orphan.
if _, exists := b.orphans[*blockHash]; exists {
str := fmt.Sprintf("already have block (orphan) %v", blockHash)
return false, false, ruleError(ErrDuplicateBlock, str)
return false, ruleError(ErrDuplicateBlock, str)
}
// Perform preliminary sanity checks on the block and its transactions.
err = checkBlockSanity(block, b.chainParams.PowLimit, b.timeSource, flags)
if err != nil {
return false, false, err
return false, err
}
// Find the previous checkpoint and perform some additional checks based
@ -179,7 +178,7 @@ func (b *BlockChain) ProcessBlock(block *btcutil.Block, flags BehaviorFlags) (bo
blockHeader := &block.MsgBlock().Header
checkpointNode, err := b.findPreviousCheckpoint()
if err != nil {
return false, false, err
return false, err
}
if checkpointNode != nil {
// Ensure the block timestamp is after the checkpoint timestamp.
@ -188,7 +187,7 @@ func (b *BlockChain) ProcessBlock(block *btcutil.Block, flags BehaviorFlags) (bo
str := fmt.Sprintf("block %v has timestamp %v before "+
"last checkpoint timestamp %v", blockHash,
blockHeader.Timestamp, checkpointTime)
return false, false, ruleError(ErrCheckpointTimeTooOld, str)
return false, ruleError(ErrCheckpointTimeTooOld, str)
}
if !fastAdd {
// Even though the checks prior to now have already ensured the
@ -205,7 +204,7 @@ func (b *BlockChain) ProcessBlock(block *btcutil.Block, flags BehaviorFlags) (bo
str := fmt.Sprintf("block target difficulty of %064x "+
"is too low when compared to the previous "+
"checkpoint", currentTarget)
return false, false, ruleError(ErrDifficultyTooLow, str)
return false, ruleError(ErrDifficultyTooLow, str)
}
}
}
@ -215,7 +214,7 @@ func (b *BlockChain) ProcessBlock(block *btcutil.Block, flags BehaviorFlags) (bo
for _, prevBlock := range blockHeader.PrevBlocks {
prevBlockExists, err := b.blockExists(&prevBlock)
if err != nil {
return false, false, err
return false, err
}
if !prevBlockExists {
@ -227,14 +226,14 @@ func (b *BlockChain) ProcessBlock(block *btcutil.Block, flags BehaviorFlags) (bo
}
if !allPrevBlocksExist {
return false, true, nil
return true, nil
}
// The block has passed all context independent checks and appears sane
// enough to potentially accept it into the block chain.
isMainChain, err := b.maybeAcceptBlock(block, flags)
// enough to potentially accept it into the block DAG.
err = b.maybeAcceptBlock(block, flags)
if err != nil {
return false, false, err
return false, err
}
// Accept any orphan blocks that depend on this block (they are
@ -242,10 +241,10 @@ func (b *BlockChain) ProcessBlock(block *btcutil.Block, flags BehaviorFlags) (bo
// there are no more.
err = b.processOrphans(blockHash, flags)
if err != nil {
return false, false, err
return false, err
}
log.Debugf("Accepted block %v", blockHash)
return isMainChain, false, nil
return false, nil
}

View File

@ -97,15 +97,11 @@ func TestCheckConnectBlockTemplate(t *testing.T) {
}
for i := 1; i <= 3; i++ {
isMainChain, _, err := chain.ProcessBlock(blocks[i], BFNone)
_, err := chain.ProcessBlock(blocks[i], BFNone)
if err != nil {
t.Fatalf("CheckConnectBlockTemplate: Received unexpected error "+
"processing block %d: %v", i, err)
}
if !isMainChain {
t.Fatalf("CheckConnectBlockTemplate: Expected block %d to connect "+
"to main chain", i)
}
}
// Block 3 should fail to connect since it's already inserted.

View File

@ -129,15 +129,11 @@ func (bi *blockImporter) processBlock(serializedBlock []byte) (bool, error) {
// Ensure the blocks follows all of the chain rules and match up to the
// known checkpoints.
isMainChain, isOrphan, err := bi.chain.ProcessBlock(block,
isOrphan, err := bi.chain.ProcessBlock(block,
blockdag.BFFastAdd)
if err != nil {
return false, err
}
if !isMainChain {
return false, fmt.Errorf("import file contains an block that "+
"does not extend the main chain: %v", blockHash)
}
if isOrphan {
return false, fmt.Errorf("import file contains an orphan "+
"block: %v", blockHash)

View File

@ -543,9 +543,8 @@ func (sm *SyncManager) handleBlockMsg(bmsg *blockMsg) {
delete(state.requestedBlocks, *blockHash)
delete(sm.requestedBlocks, *blockHash)
// Process the block to include validation, best chain selection, orphan
// handling, etc.
_, isOrphan, err := sm.chain.ProcessBlock(bmsg.block, behaviorFlags)
// Process the block to include validation, orphan handling, etc.
isOrphan, err := sm.chain.ProcessBlock(bmsg.block, behaviorFlags)
if err != nil {
// When the error is a rule error, it means the block was simply
// rejected as opposed to something actually going wrong, so log
@ -1127,7 +1126,7 @@ out:
msg.reply <- peerID
case processBlockMsg:
_, isOrphan, err := sm.chain.ProcessBlock(
isOrphan, err := sm.chain.ProcessBlock(
msg.block, msg.flags)
if err != nil {
msg.reply <- processBlockResponse{