[DEV-43] Change UTXOViewpoint.BestHash() to Tips() and update all related logic (#15)

* [DEV-43] Changed BestHash to Tips, fixed logic inside utxoviewpoint.go.

* [DEV-43] Fixed broken references.

* [DEV-43] Replaced blockNode slices and hash slices with BlockSets.

* [DEV-43] Did some renaming, unexported blockSet, and rewrote AppendTip as AddBlock.

* [DEV-43] Removed explicit contains check from AddBlock.
This commit is contained in:
stasatdaglabs 2018-07-01 15:52:56 +03:00 committed by Svarog
parent 4b5e99e486
commit 79a0c1f124
11 changed files with 235 additions and 92 deletions

View File

@ -24,18 +24,18 @@ import (
func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, flags BehaviorFlags) (bool, error) {
// The height of this block is one more than the referenced previous
// block.
nodes, err := lookupPreviousNodes(block, b)
parents, err := lookupPreviousNodes(block, b)
if err != nil {
return false, err
}
firstNode := nodes[0] // TODO: (Stas) This is wrong. Modified only to satisfy compilation.
blockHeight := firstNode.height + 1
selectedParent := parents.first()
blockHeight := selectedParent.height + 1
block.SetHeight(blockHeight)
// 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, firstNode, flags)
err = b.checkBlockContext(block, selectedParent, flags)
if err != nil {
return false, err
}
@ -60,7 +60,7 @@ func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, flags BehaviorFlags)
// if the block ultimately gets connected to the main chain, it starts out
// on a side chain.
blockHeader := &block.MsgBlock().Header
newNode := newBlockNode(blockHeader, nodes)
newNode := newBlockNode(blockHeader, parents)
newNode.status = statusDataStored
b.index.AddNode(newNode)
@ -72,7 +72,7 @@ func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, flags BehaviorFlags)
// 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, block, flags)
isMainChain, err := b.connectBestChain(newNode, parents, block, flags)
if err != nil {
return false, err
}
@ -87,11 +87,11 @@ func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, flags BehaviorFlags)
return isMainChain, nil
}
func lookupPreviousNodes(block *btcutil.Block, blockChain *BlockChain) ([]*blockNode, error) {
func lookupPreviousNodes(block *btcutil.Block, blockChain *BlockChain) (blockSet, error) {
header := block.MsgBlock().Header
prevHashes := header.PrevBlocks
nodes := make([]*blockNode, len(prevHashes))
nodes := newSet()
for _, prevHash := range prevHashes {
node := blockChain.index.LookupNode(&prevHash)
if node == nil {
@ -102,7 +102,7 @@ func lookupPreviousNodes(block *btcutil.Block, blockChain *BlockChain) ([]*block
return nil, ruleError(ErrInvalidAncestorBlock, str)
}
nodes = append(nodes, node)
nodes.add(node)
}
return nodes, nil

View File

@ -72,7 +72,7 @@ type blockNode struct {
// padding adds up.
// parents is the parent blocks for this node.
parents []*blockNode
parents blockSet
// selectedParent is the selected parent for this node.
selectedParent *blockNode
@ -80,11 +80,11 @@ type blockNode struct {
// hash is the double sha 256 of the block.
hash daghash.Hash
// workSum is the total amount of work in the chain up to and including
// workSum is the total amount of work in the DAG up to and including
// this node.
workSum *big.Int
// height is the position in the block chain.
// height is the position in the block DAG.
height int32
// Some fields from block headers to aid in best chain selection and
@ -108,7 +108,7 @@ type blockNode struct {
// calculating the height and workSum from the respective fields on the first parent.
// 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, parents []*blockNode) {
func initBlockNode(node *blockNode, blockHeader *wire.BlockHeader, parents blockSet) {
*node = blockNode{
hash: blockHeader.BlockHash(),
parents: parents,
@ -120,7 +120,7 @@ func initBlockNode(node *blockNode, blockHeader *wire.BlockHeader, parents []*bl
merkleRoot: blockHeader.MerkleRoot,
}
if len(parents) > 0 {
node.selectedParent = parents[0]
node.selectedParent = parents.first()
node.height = node.selectedParent.height + 1
node.workSum = node.workSum.Add(node.selectedParent.workSum, node.workSum)
}
@ -129,7 +129,7 @@ func initBlockNode(node *blockNode, blockHeader *wire.BlockHeader, parents []*bl
// newBlockNode returns a new block node for the given block header and parent
// nodes, calculating the height and workSum from the respective fields on the
// parent. This function is NOT safe for concurrent access.
func newBlockNode(blockHeader *wire.BlockHeader, parents []*blockNode) *blockNode {
func newBlockNode(blockHeader *wire.BlockHeader, parents blockSet) *blockNode {
var node blockNode
initBlockNode(&node, blockHeader, parents)
return &node

130
blockdag/blockset.go Normal file
View File

@ -0,0 +1,130 @@
package blockdag
import (
"strings"
"github.com/daglabs/btcd/dagconfig/daghash"
)
// blockSet implements a basic unsorted set of blocks
type blockSet map[daghash.Hash]*blockNode
// newSet creates a new, empty BlockSet
func newSet() blockSet {
return map[daghash.Hash]*blockNode{}
}
// setFromSlice converts a slice of blocks into an unordered set represented as map
func setFromSlice(blocks ...*blockNode) blockSet {
set := newSet()
for _, block := range blocks {
set[block.hash] = block
}
return set
}
// toSlice converts a set of blocks into a slice
func (bs blockSet) toSlice() []*blockNode {
slice := []*blockNode{}
for _, block := range bs {
slice = append(slice, block)
}
return slice
}
// add adds a block to this BlockSet
func (bs blockSet) add(block *blockNode) {
bs[block.hash] = block
}
// remove removes a block from this BlockSet, if exists
// Does nothing if this set does not contain the block
func (bs blockSet) remove(block *blockNode) {
delete(bs, block.hash)
}
// clone clones thie block set
func (bs blockSet) clone() blockSet {
clone := newSet()
for _, block := range bs {
clone.add(block)
}
return clone
}
// subtract returns the difference between the BlockSet and another BlockSet
func (bs blockSet) subtract(other blockSet) blockSet {
diff := newSet()
for _, block := range bs {
if !other.contains(block) {
diff.add(block)
}
}
return diff
}
// addSet adds all blocks in other set to this set
func (bs blockSet) addSet(other blockSet) {
for _, block := range other {
bs.add(block)
}
}
// addSlice adds provided slice to this set
func (bs blockSet) addSlice(slice []*blockNode) {
for _, block := range slice {
bs.add(block)
}
}
// union returns a BlockSet that contains all blocks included in this set,
// the other set, or both
func (bs blockSet) union(other blockSet) blockSet {
union := bs.clone()
union.addSet(other)
return union
}
// contains returns true iff this set contains block
func (bs blockSet) contains(block *blockNode) bool {
_, ok := bs[block.hash]
return ok
}
// hashesEqual returns true if the given hashes are equal to the hashes
// of the blocks in this set.
// NOTE: The given hash slice must not contain duplicates.
func (bs blockSet) hashesEqual(hashes []daghash.Hash) bool {
if len(hashes) != len(bs) {
return false
}
for _, hash := range hashes {
if _, wasFound := bs[hash]; !wasFound {
return false
}
}
return true
}
// first returns the first block in this set or nil if this set is empty.
func (bs blockSet) first() *blockNode {
for _, block := range bs {
return block
}
return nil
}
func (bs blockSet) String() string {
ids := []string{}
for hash := range bs {
ids = append(ids, hash.String())
}
return strings.Join(ids, ",")
}

View File

@ -819,13 +819,13 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error
// database and using that information to unspend all of the spent txos
// and remove the utxos created by the blocks.
view := NewUtxoViewpoint()
view.SetBestHash(&b.bestChain.SelectedTip().hash)
for e := detachNodes.Front(); e != nil; e = e.Next() {
n := e.Value.(*blockNode)
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, n)
block, err = dbFetchBlockByNode(dbTx, node)
return err
})
if err != nil {
@ -854,7 +854,7 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error
detachBlocks = append(detachBlocks, block)
detachSpentTxOuts = append(detachSpentTxOuts, stxos)
err = view.disconnectTransactions(b.db, block, stxos)
err = view.disconnectTransactions(b.db, node.parents, block, stxos)
if err != nil {
return err
}
@ -873,20 +873,20 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error
// tweaking the chain and/or database. This approach catches these
// issues before ever modifying the chain.
var validationError error
for e := attachNodes.Front(); e != nil; e = e.Next() {
n := e.Value.(*blockNode)
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(n, statusInvalidAncestor)
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, n)
block, err = dbFetchBlockByNode(dbTx, node)
return err
})
if err != nil {
@ -899,12 +899,12 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error
// 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(n).KnownValid() {
if b.index.NodeStatus(node).KnownValid() {
err = view.fetchInputUtxos(b.db, block)
if err != nil {
return err
}
err = view.connectTransactions(block, nil)
err = view.connectTransactions(node, block.Transactions(), nil)
if err != nil {
return err
}
@ -915,19 +915,19 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error
// 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(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(n, statusValidateFailed)
b.index.SetStatusFlags(node, statusValidateFailed)
validationError = err
continue
}
return err
}
b.index.SetStatusFlags(n, statusValid)
b.index.SetStatusFlags(node, statusValid)
}
if validationError != nil {
@ -940,11 +940,11 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error
// view to be valid from the viewpoint of each block being connected or
// disconnected.
view = NewUtxoViewpoint()
view.SetBestHash(&b.bestChain.SelectedTip().hash)
view.SetTips(b.bestChain.Tips())
// Disconnect blocks from the main chain.
for i, e := 0, detachNodes.Front(); e != nil; i, e = i+1, e.Next() {
n := e.Value.(*blockNode)
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
@ -956,22 +956,22 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error
// Update the view to unspend all of the spent txos and remove
// the utxos created by the block.
err = view.disconnectTransactions(b.db, 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(n, block, view)
err = b.disconnectBlock(node, block, view)
if err != nil {
return err
}
}
// Connect the new best chain blocks.
for i, e := 0, attachNodes.Front(); e != nil; i, e = i+1, e.Next() {
n := e.Value.(*blockNode)
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
@ -986,13 +986,13 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error
// to it. Also, provide an stxo slice so the spent txout
// details are generated.
stxos := make([]spentTxOut, 0, countSpentOutputs(block))
err = view.connectTransactions(block, &stxos)
err = view.connectTransactions(node, block.Transactions(), &stxos)
if err != nil {
return err
}
// Update the database and chain state.
err = b.connectBlock(n, block, view, stxos)
err = b.connectBlock(node, block, view, stxos)
if err != nil {
return err
}
@ -1024,13 +1024,13 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error
// 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, block *btcutil.Block, flags BehaviorFlags) (bool, error) {
func (b *BlockChain) connectBestChain(node *blockNode, parentNodes blockSet, block *btcutil.Block, flags BehaviorFlags) (bool, error) {
fastAdd := flags&BFFastAdd == BFFastAdd
// We are extending the main (best) chain with a new block. This is the
// most common case.
parentHash := block.MsgBlock().Header.SelectedPrevBlock()
if parentHash.IsEqual(&b.bestChain.SelectedTip().hash) {
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()
@ -1038,7 +1038,7 @@ func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, fla
// to the main chain without violating any rules and without
// actually connecting the block.
view := NewUtxoViewpoint()
view.SetBestHash(parentHash)
view.SetTips(parentNodes)
stxos := make([]spentTxOut, 0, countSpentOutputs(block))
if !fastAdd {
err := b.checkConnectBlock(node, block, view, &stxos)
@ -1073,7 +1073,7 @@ func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, fla
if err != nil {
return false, err
}
err = view.connectTransactions(block, &stxos)
err = view.connectTransactions(node, block.Transactions(), &stxos)
if err != nil {
return false, err
}
@ -1097,7 +1097,7 @@ func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, fla
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(parentHash) {
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)

View File

@ -147,7 +147,7 @@ func TestCalcSequenceLock(t *testing.T) {
})
utxoView := NewUtxoViewpoint()
utxoView.AddTxOuts(targetTx, int32(numBlocksToActivate)-4)
utxoView.SetBestHash(&node.hash)
utxoView.SetTips(setFromSlice(node))
// Create a utxo that spends the fake utxo created above for use in the
// transactions created in the tests. It has an age of 4 blocks. Note

View File

@ -1162,7 +1162,7 @@ func (b *BlockChain) initChainState() error {
// Initialize the block node for the block, connect it,
// and add it to the block index.
node := &blockNodes[i]
initBlockNode(node, header, []*blockNode{parent}) // TODO: (Stas) This is wrong. Modified only to satisfy compilation.
initBlockNode(node, header, setFromSlice(parent)) // TODO: (Stas) This is wrong. Modified only to satisfy compilation.
node.status = status
b.index.addNode(node)

View File

@ -96,19 +96,19 @@ func (c *chainView) tip() *blockNode {
// an empty slice if there is no tip.
//
// This function is safe for concurrent access.
func (c *chainView) Tips() []*blockNode {
func (c *chainView) Tips() blockSet {
c.mtx.Lock()
tip := c.tip()
c.mtx.Unlock()
return []*blockNode{tip} // TODO: (Stas) This is wrong. Modified only to satisfy compilation.
return setFromSlice(tip) // TODO: (Stas) This is wrong. Modified only to satisfy compilation.
}
// SelecedTip returns the current selected tip block node for the chain view.
// It will return nil if there is no tip.
//
// This function is safe for concurrent access.
func (view *chainView) SelectedTip() *blockNode {
return view.Tips()[0]
func (c *chainView) SelectedTip() *blockNode {
return c.Tips().first()
}
// setTip sets the chain view to use the provided block node as the current tip

View File

@ -10,8 +10,8 @@ import (
"reflect"
"testing"
"github.com/daglabs/btcd/wire"
"github.com/daglabs/btcd/dagconfig/daghash"
"github.com/daglabs/btcd/wire"
)
// testNoncePrng provides a deterministic prng for the nonce in generated fake
@ -31,7 +31,7 @@ func chainedNodes(parent *blockNode, numNodes int) []*blockNode {
if tip != nil {
header.PrevBlocks = []daghash.Hash{tip.hash} // TODO: (Stas) This is wrong. Modified only to satisfy compilation.
}
nodes[i] = newBlockNode(&header, []*blockNode{tip}) // TODO: (Stas) This is wrong. Modified only to satisfy compilation.
nodes[i] = newBlockNode(&header, setFromSlice(tip)) // TODO: (Stas) This is wrong. Modified only to satisfy compilation.
tip = nodes[i]
}
return nodes

View File

@ -380,5 +380,5 @@ func newFakeNode(parent *blockNode, blockVersion int32, bits uint32, timestamp t
Bits: bits,
Timestamp: timestamp,
}
return newBlockNode(header, []*blockNode{parent}) // TODO: (Stas) This is wrong. Modified only to satisfy compilation.
return newBlockNode(header, setFromSlice(parent)) // TODO: (Stas) This is wrong. Modified only to satisfy compilation.
}

View File

@ -119,20 +119,32 @@ func (entry *UtxoEntry) Clone() *UtxoEntry {
// The unspent outputs are needed by other transactions for things such as
// script validation and double spend prevention.
type UtxoViewpoint struct {
entries map[wire.OutPoint]*UtxoEntry
bestHash daghash.Hash
entries map[wire.OutPoint]*UtxoEntry
tips blockSet
}
// BestHash returns the hash of the best block in the chain the view currently
// respresents.
func (view *UtxoViewpoint) BestHash() *daghash.Hash {
return &view.bestHash
// Tips returns the hashes of the tips in the DAG the view currently
// represents.
func (view *UtxoViewpoint) Tips() blockSet {
return view.tips
}
// SetBestHash sets the hash of the best block in the chain the view currently
// respresents.
func (view *UtxoViewpoint) SetBestHash(hash *daghash.Hash) {
view.bestHash = *hash
// SetTips sets the hashes of the tips in the DAG the view currently
// represents.
func (view *UtxoViewpoint) SetTips(tips blockSet) {
view.tips = tips
}
// AddBlock removes all the parents of block from the tips and adds
// the given block to the tips.
func (view *UtxoViewpoint) AddBlock(block *blockNode) {
updatedTips := view.tips.clone()
for _, parent := range block.parents {
updatedTips.remove(parent)
}
updatedTips.add(block)
view.tips = updatedTips
}
// LookupEntry returns information about a given transaction output according to
@ -264,9 +276,9 @@ func (view *UtxoViewpoint) connectTransaction(tx *btcutil.Tx, blockHeight int32,
// spend as spent, and setting the best hash for the view to the passed block.
// In addition, when the 'stxos' argument is not nil, it will be updated to
// append an entry for each spent txout.
func (view *UtxoViewpoint) connectTransactions(block *btcutil.Block, stxos *[]spentTxOut) error {
for _, tx := range block.Transactions() {
err := view.connectTransaction(tx, block.Height(), stxos)
func (view *UtxoViewpoint) connectTransactions(block *blockNode, transactions []*btcutil.Tx, stxos *[]spentTxOut) error {
for _, tx := range transactions {
err := view.connectTransaction(tx, block.height, stxos)
if err != nil {
return err
}
@ -274,7 +286,7 @@ func (view *UtxoViewpoint) connectTransactions(block *btcutil.Block, stxos *[]sp
// Update the best hash for view to include this block since all of its
// transactions have been connected.
view.SetBestHash(block.Hash())
view.AddBlock(block)
return nil
}
@ -308,7 +320,7 @@ func (view *UtxoViewpoint) fetchEntryByHash(db database.DB, hash *daghash.Hash)
// created by the passed block, restoring all utxos the transactions spent by
// using the provided spent txo information, and setting the best hash for the
// view to the block before the passed block.
func (view *UtxoViewpoint) disconnectTransactions(db database.DB, block *btcutil.Block, stxos []spentTxOut) error {
func (view *UtxoViewpoint) disconnectTransactions(db database.DB, parents blockSet, block *btcutil.Block, stxos []spentTxOut) error {
// Sanity check the correct number of stxos are provided.
if len(stxos) != countSpentOutputs(block) {
return AssertError("disconnectTransactions called with bad " +
@ -432,9 +444,9 @@ func (view *UtxoViewpoint) disconnectTransactions(db database.DB, block *btcutil
}
}
// Update the best hash for view to the previous block since all of the
// Update the tips for view to the block's parents since all of the
// transactions for the current block have been disconnected.
view.SetBestHash(block.MsgBlock().Header.SelectedPrevBlock())
view.SetTips(parents)
return nil
}

View File

@ -649,13 +649,13 @@ func checkSerializedHeight(coinbaseTx *btcutil.Tx, wantHeight int32) error {
// the checkpoints are not performed.
//
// This function MUST be called with the chain state lock held (for writes).
func (b *BlockChain) checkBlockHeaderContext(header *wire.BlockHeader, prevNode *blockNode, flags BehaviorFlags) error {
func (b *BlockChain) checkBlockHeaderContext(header *wire.BlockHeader, selectedParent *blockNode, flags BehaviorFlags) error {
fastAdd := flags&BFFastAdd == BFFastAdd
if !fastAdd {
// Ensure the difficulty specified in the block header matches
// the calculated difficulty based on the previous block and
// difficulty retarget rules.
expectedDifficulty, err := b.calcNextRequiredDifficulty(prevNode,
expectedDifficulty, err := b.calcNextRequiredDifficulty(selectedParent,
header.Timestamp)
if err != nil {
return err
@ -669,7 +669,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 := prevNode.CalcPastMedianTime()
medianTime := selectedParent.CalcPastMedianTime()
if !header.Timestamp.After(medianTime) {
str := "block timestamp of %v is not after expected %v"
str = fmt.Sprintf(str, header.Timestamp, medianTime)
@ -679,7 +679,7 @@ func (b *BlockChain) checkBlockHeaderContext(header *wire.BlockHeader, prevNode
// The height of this block is one more than the referenced previous
// block.
blockHeight := prevNode.height + 1
blockHeight := selectedParent.height + 1
// Ensure chain matches up to predetermined checkpoints.
blockHash := header.BlockHash()
@ -731,10 +731,10 @@ func (b *BlockChain) checkBlockHeaderContext(header *wire.BlockHeader, prevNode
// for how the flags modify its behavior.
//
// This function MUST be called with the chain state lock held (for writes).
func (b *BlockChain) checkBlockContext(block *btcutil.Block, prevNode *blockNode, flags BehaviorFlags) error {
func (b *BlockChain) checkBlockContext(block *btcutil.Block, selectedParent *blockNode, flags BehaviorFlags) error {
// Perform all block header related validation checks.
header := &block.MsgBlock().Header
err := b.checkBlockHeaderContext(header, prevNode, flags)
err := b.checkBlockHeaderContext(header, selectedParent, flags)
if err != nil {
return err
}
@ -744,7 +744,7 @@ func (b *BlockChain) checkBlockContext(block *btcutil.Block, prevNode *blockNode
// Obtain the latest state of the deployed CSV soft-fork in
// order to properly guard the new validation behavior based on
// the current BIP 9 version bits state.
csvState, err := b.deploymentState(prevNode, dagconfig.DeploymentCSV)
csvState, err := b.deploymentState(selectedParent, dagconfig.DeploymentCSV)
if err != nil {
return err
}
@ -754,12 +754,12 @@ func (b *BlockChain) checkBlockContext(block *btcutil.Block, prevNode *blockNode
// timestamps for all lock-time based checks.
blockTime := header.Timestamp
if csvState == ThresholdActive {
blockTime = prevNode.CalcPastMedianTime()
blockTime = selectedParent.CalcPastMedianTime()
}
// The height of this block is one more than the referenced
// previous block.
blockHeight := prevNode.height + 1
blockHeight := selectedParent.height + 1
// Ensure all transactions in the block are finalized.
for _, tx := range block.Transactions() {
@ -973,11 +973,11 @@ func (b *BlockChain) checkConnectBlock(node *blockNode, block *btcutil.Block, vi
}
// Ensure the view is for the node being checked.
parentHash := block.MsgBlock().Header.SelectedPrevBlock()
if !view.BestHash().IsEqual(parentHash) {
parentHashes := block.MsgBlock().Header.PrevBlocks
if !view.Tips().hashesEqual(parentHashes) {
return AssertError(fmt.Sprintf("inconsistent view when "+
"checking block connection: best hash is %v instead "+
"of expected %v", view.BestHash(), parentHash))
"checking block connection: tips are %v instead "+
"of expected %v", view.Tips(), parentHashes))
}
// BIP0030 added a rule to prevent blocks which contain duplicate
@ -1189,9 +1189,9 @@ func (b *BlockChain) checkConnectBlock(node *blockNode, block *btcutil.Block, vi
}
}
// Update the best hash for view to include this block since all of its
// Update the view tips to include this block since all of its
// transactions have been connected.
view.SetBestHash(&node.hash)
view.AddBlock(node)
return nil
}
@ -1210,11 +1210,12 @@ func (b *BlockChain) CheckConnectBlockTemplate(block *btcutil.Block) error {
// This only checks whether the block can be connected to the tip of the
// current chain.
tip := b.bestChain.SelectedTip()
tips := b.bestChain.Tips()
header := block.MsgBlock().Header
if tip.hash != *header.SelectedPrevBlock() {
str := fmt.Sprintf("previous block must be the current chain tip %v, "+
"instead got %v", tip.hash, header.SelectedPrevBlock())
prevHashes := header.PrevBlocks
if tips.hashesEqual(prevHashes) {
str := fmt.Sprintf("previous blocks must be the currents tips %v, "+
"instead got %v", tips, prevHashes)
return ruleError(ErrPrevBlockNotBest, str)
}
@ -1223,7 +1224,7 @@ func (b *BlockChain) CheckConnectBlockTemplate(block *btcutil.Block) error {
return err
}
err = b.checkBlockContext(block, tip, flags)
err = b.checkBlockContext(block, b.bestChain.SelectedTip(), flags)
if err != nil {
return err
}
@ -1231,7 +1232,7 @@ func (b *BlockChain) CheckConnectBlockTemplate(block *btcutil.Block) error {
// Leave the spent txouts entry nil in the state since the information
// is not needed and thus extra work can be avoided.
view := NewUtxoViewpoint()
view.SetBestHash(&tip.hash)
newNode := newBlockNode(&header, []*blockNode{tip}) // TODO: (Stas) This is wrong. Modified only to satisfy compilation.
view.SetTips(tips)
newNode := newBlockNode(&header, b.bestChain.Tips())
return b.checkConnectBlock(newNode, block, view, nil)
}