mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-06-07 06:36:46 +00:00
[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:
parent
4b5e99e486
commit
79a0c1f124
@ -24,18 +24,18 @@ import (
|
|||||||
func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, flags BehaviorFlags) (bool, error) {
|
func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, flags BehaviorFlags) (bool, error) {
|
||||||
// The height of this block is one more than the referenced previous
|
// The height of this block is one more than the referenced previous
|
||||||
// block.
|
// block.
|
||||||
nodes, err := lookupPreviousNodes(block, b)
|
parents, err := lookupPreviousNodes(block, b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
firstNode := nodes[0] // TODO: (Stas) This is wrong. Modified only to satisfy compilation.
|
selectedParent := parents.first()
|
||||||
blockHeight := firstNode.height + 1
|
blockHeight := selectedParent.height + 1
|
||||||
block.SetHeight(blockHeight)
|
block.SetHeight(blockHeight)
|
||||||
|
|
||||||
// The block must pass all of the validation rules which depend on the
|
// The block must pass all of the validation rules which depend on the
|
||||||
// position of the block within the block chain.
|
// position of the block within the block chain.
|
||||||
err = b.checkBlockContext(block, firstNode, flags)
|
err = b.checkBlockContext(block, selectedParent, flags)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
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
|
// if the block ultimately gets connected to the main chain, it starts out
|
||||||
// on a side chain.
|
// on a side chain.
|
||||||
blockHeader := &block.MsgBlock().Header
|
blockHeader := &block.MsgBlock().Header
|
||||||
newNode := newBlockNode(blockHeader, nodes)
|
newNode := newBlockNode(blockHeader, parents)
|
||||||
newNode.status = statusDataStored
|
newNode.status = statusDataStored
|
||||||
|
|
||||||
b.index.AddNode(newNode)
|
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
|
// Connect the passed block to the chain while respecting proper chain
|
||||||
// selection according to the chain with the most proof of work. This
|
// selection according to the chain with the most proof of work. This
|
||||||
// also handles validation of the transaction scripts.
|
// 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 {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@ -87,11 +87,11 @@ func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, flags BehaviorFlags)
|
|||||||
return isMainChain, nil
|
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
|
header := block.MsgBlock().Header
|
||||||
prevHashes := header.PrevBlocks
|
prevHashes := header.PrevBlocks
|
||||||
|
|
||||||
nodes := make([]*blockNode, len(prevHashes))
|
nodes := newSet()
|
||||||
for _, prevHash := range prevHashes {
|
for _, prevHash := range prevHashes {
|
||||||
node := blockChain.index.LookupNode(&prevHash)
|
node := blockChain.index.LookupNode(&prevHash)
|
||||||
if node == nil {
|
if node == nil {
|
||||||
@ -102,7 +102,7 @@ func lookupPreviousNodes(block *btcutil.Block, blockChain *BlockChain) ([]*block
|
|||||||
return nil, ruleError(ErrInvalidAncestorBlock, str)
|
return nil, ruleError(ErrInvalidAncestorBlock, str)
|
||||||
}
|
}
|
||||||
|
|
||||||
nodes = append(nodes, node)
|
nodes.add(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nodes, nil
|
return nodes, nil
|
||||||
|
@ -72,7 +72,7 @@ type blockNode struct {
|
|||||||
// padding adds up.
|
// padding adds up.
|
||||||
|
|
||||||
// parents is the parent blocks for this node.
|
// parents is the parent blocks for this node.
|
||||||
parents []*blockNode
|
parents blockSet
|
||||||
|
|
||||||
// selectedParent is the selected parent for this node.
|
// selectedParent is the selected parent for this node.
|
||||||
selectedParent *blockNode
|
selectedParent *blockNode
|
||||||
@ -80,11 +80,11 @@ type blockNode struct {
|
|||||||
// hash is the double sha 256 of the block.
|
// hash is the double sha 256 of the block.
|
||||||
hash daghash.Hash
|
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.
|
// this node.
|
||||||
workSum *big.Int
|
workSum *big.Int
|
||||||
|
|
||||||
// height is the position in the block chain.
|
// height is the position in the block DAG.
|
||||||
height int32
|
height int32
|
||||||
|
|
||||||
// Some fields from block headers to aid in best chain selection and
|
// 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.
|
// 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
|
// This function is NOT safe for concurrent access. It must only be called when
|
||||||
// initially creating a node.
|
// initially creating a node.
|
||||||
func initBlockNode(node *blockNode, blockHeader *wire.BlockHeader, parents []*blockNode) {
|
func initBlockNode(node *blockNode, blockHeader *wire.BlockHeader, parents blockSet) {
|
||||||
*node = blockNode{
|
*node = blockNode{
|
||||||
hash: blockHeader.BlockHash(),
|
hash: blockHeader.BlockHash(),
|
||||||
parents: parents,
|
parents: parents,
|
||||||
@ -120,7 +120,7 @@ func initBlockNode(node *blockNode, blockHeader *wire.BlockHeader, parents []*bl
|
|||||||
merkleRoot: blockHeader.MerkleRoot,
|
merkleRoot: blockHeader.MerkleRoot,
|
||||||
}
|
}
|
||||||
if len(parents) > 0 {
|
if len(parents) > 0 {
|
||||||
node.selectedParent = parents[0]
|
node.selectedParent = parents.first()
|
||||||
node.height = node.selectedParent.height + 1
|
node.height = node.selectedParent.height + 1
|
||||||
node.workSum = node.workSum.Add(node.selectedParent.workSum, node.workSum)
|
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
|
// 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
|
// nodes, calculating the height and workSum from the respective fields on the
|
||||||
// parent. This function is NOT safe for concurrent access.
|
// 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
|
var node blockNode
|
||||||
initBlockNode(&node, blockHeader, parents)
|
initBlockNode(&node, blockHeader, parents)
|
||||||
return &node
|
return &node
|
||||||
|
130
blockdag/blockset.go
Normal file
130
blockdag/blockset.go
Normal 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, ",")
|
||||||
|
}
|
@ -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
|
// database and using that information to unspend all of the spent txos
|
||||||
// and remove the utxos created by the blocks.
|
// and remove the utxos created by the blocks.
|
||||||
view := NewUtxoViewpoint()
|
view := NewUtxoViewpoint()
|
||||||
view.SetBestHash(&b.bestChain.SelectedTip().hash)
|
view.SetTips(b.bestChain.Tips())
|
||||||
for e := detachNodes.Front(); e != nil; e = e.Next() {
|
for element := detachNodes.Front(); element != nil; element = element.Next() {
|
||||||
n := e.Value.(*blockNode)
|
node := element.Value.(*blockNode)
|
||||||
var block *btcutil.Block
|
var block *btcutil.Block
|
||||||
err := b.db.View(func(dbTx database.Tx) error {
|
err := b.db.View(func(dbTx database.Tx) error {
|
||||||
var err error
|
var err error
|
||||||
block, err = dbFetchBlockByNode(dbTx, n)
|
block, err = dbFetchBlockByNode(dbTx, node)
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -854,7 +854,7 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error
|
|||||||
detachBlocks = append(detachBlocks, block)
|
detachBlocks = append(detachBlocks, block)
|
||||||
detachSpentTxOuts = append(detachSpentTxOuts, stxos)
|
detachSpentTxOuts = append(detachSpentTxOuts, stxos)
|
||||||
|
|
||||||
err = view.disconnectTransactions(b.db, block, stxos)
|
err = view.disconnectTransactions(b.db, node.parents, block, stxos)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
// tweaking the chain and/or database. This approach catches these
|
||||||
// issues before ever modifying the chain.
|
// issues before ever modifying the chain.
|
||||||
var validationError error
|
var validationError error
|
||||||
for e := attachNodes.Front(); e != nil; e = e.Next() {
|
for element := attachNodes.Front(); element != nil; element = element.Next() {
|
||||||
n := e.Value.(*blockNode)
|
node := element.Value.(*blockNode)
|
||||||
|
|
||||||
// If any previous nodes in attachNodes failed validation,
|
// If any previous nodes in attachNodes failed validation,
|
||||||
// mark this one as having an invalid ancestor.
|
// mark this one as having an invalid ancestor.
|
||||||
if validationError != nil {
|
if validationError != nil {
|
||||||
b.index.SetStatusFlags(n, statusInvalidAncestor)
|
b.index.SetStatusFlags(node, statusInvalidAncestor)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
var block *btcutil.Block
|
var block *btcutil.Block
|
||||||
err := b.db.View(func(dbTx database.Tx) error {
|
err := b.db.View(func(dbTx database.Tx) error {
|
||||||
var err error
|
var err error
|
||||||
block, err = dbFetchBlockByNode(dbTx, n)
|
block, err = dbFetchBlockByNode(dbTx, node)
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
if err != nil {
|
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
|
// Skip checks if node has already been fully validated. Although
|
||||||
// checkConnectBlock gets skipped, we still need to update the UTXO
|
// checkConnectBlock gets skipped, we still need to update the UTXO
|
||||||
// view.
|
// view.
|
||||||
if b.index.NodeStatus(n).KnownValid() {
|
if b.index.NodeStatus(node).KnownValid() {
|
||||||
err = view.fetchInputUtxos(b.db, block)
|
err = view.fetchInputUtxos(b.db, block)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = view.connectTransactions(block, nil)
|
err = view.connectTransactions(node, block.Transactions(), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
// thus will not be generated. This is done because the state
|
||||||
// is not being immediately written to the database, so it is
|
// is not being immediately written to the database, so it is
|
||||||
// not needed.
|
// not needed.
|
||||||
err = b.checkConnectBlock(n, block, view, nil)
|
err = b.checkConnectBlock(node, block, view, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If the block failed validation mark it as invalid, then
|
// If the block failed validation mark it as invalid, then
|
||||||
// continue to loop through remaining nodes, marking them as
|
// continue to loop through remaining nodes, marking them as
|
||||||
// having an invalid ancestor.
|
// having an invalid ancestor.
|
||||||
if _, ok := err.(RuleError); ok {
|
if _, ok := err.(RuleError); ok {
|
||||||
b.index.SetStatusFlags(n, statusValidateFailed)
|
b.index.SetStatusFlags(node, statusValidateFailed)
|
||||||
validationError = err
|
validationError = err
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
b.index.SetStatusFlags(n, statusValid)
|
b.index.SetStatusFlags(node, statusValid)
|
||||||
}
|
}
|
||||||
|
|
||||||
if validationError != nil {
|
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
|
// view to be valid from the viewpoint of each block being connected or
|
||||||
// disconnected.
|
// disconnected.
|
||||||
view = NewUtxoViewpoint()
|
view = NewUtxoViewpoint()
|
||||||
view.SetBestHash(&b.bestChain.SelectedTip().hash)
|
view.SetTips(b.bestChain.Tips())
|
||||||
|
|
||||||
// Disconnect blocks from the main chain.
|
// Disconnect blocks from the main chain.
|
||||||
for i, e := 0, detachNodes.Front(); e != nil; i, e = i+1, e.Next() {
|
for i, element := 0, detachNodes.Front(); element != nil; i, element = i+1, element.Next() {
|
||||||
n := e.Value.(*blockNode)
|
node := element.Value.(*blockNode)
|
||||||
block := detachBlocks[i]
|
block := detachBlocks[i]
|
||||||
|
|
||||||
// Load all of the utxos referenced by the block that aren't
|
// 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
|
// Update the view to unspend all of the spent txos and remove
|
||||||
// the utxos created by the block.
|
// the utxos created by the block.
|
||||||
err = view.disconnectTransactions(b.db, block,
|
err = view.disconnectTransactions(b.db, node.parents, block,
|
||||||
detachSpentTxOuts[i])
|
detachSpentTxOuts[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the database and chain state.
|
// Update the database and chain state.
|
||||||
err = b.disconnectBlock(n, block, view)
|
err = b.disconnectBlock(node, block, view)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect the new best chain blocks.
|
// Connect the new best chain blocks.
|
||||||
for i, e := 0, attachNodes.Front(); e != nil; i, e = i+1, e.Next() {
|
for i, element := 0, attachNodes.Front(); element != nil; i, element = i+1, element.Next() {
|
||||||
n := e.Value.(*blockNode)
|
node := element.Value.(*blockNode)
|
||||||
block := attachBlocks[i]
|
block := attachBlocks[i]
|
||||||
|
|
||||||
// Load all of the utxos referenced by the block that aren't
|
// 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
|
// to it. Also, provide an stxo slice so the spent txout
|
||||||
// details are generated.
|
// details are generated.
|
||||||
stxos := make([]spentTxOut, 0, countSpentOutputs(block))
|
stxos := make([]spentTxOut, 0, countSpentOutputs(block))
|
||||||
err = view.connectTransactions(block, &stxos)
|
err = view.connectTransactions(node, block.Transactions(), &stxos)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the database and chain state.
|
// Update the database and chain state.
|
||||||
err = b.connectBlock(n, block, view, stxos)
|
err = b.connectBlock(node, block, view, stxos)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -1024,13 +1024,13 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error
|
|||||||
// This is useful when using checkpoints.
|
// This is useful when using checkpoints.
|
||||||
//
|
//
|
||||||
// This function MUST be called with the chain state lock held (for writes).
|
// 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
|
fastAdd := flags&BFFastAdd == BFFastAdd
|
||||||
|
|
||||||
// We are extending the main (best) chain with a new block. This is the
|
// We are extending the main (best) chain with a new block. This is the
|
||||||
// most common case.
|
// most common case.
|
||||||
parentHash := block.MsgBlock().Header.SelectedPrevBlock()
|
parentHashes := block.MsgBlock().Header.PrevBlocks
|
||||||
if parentHash.IsEqual(&b.bestChain.SelectedTip().hash) {
|
if b.bestChain.Tips().hashesEqual(parentHashes) {
|
||||||
// Skip checks if node has already been fully validated.
|
// Skip checks if node has already been fully validated.
|
||||||
fastAdd = fastAdd || b.index.NodeStatus(node).KnownValid()
|
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
|
// to the main chain without violating any rules and without
|
||||||
// actually connecting the block.
|
// actually connecting the block.
|
||||||
view := NewUtxoViewpoint()
|
view := NewUtxoViewpoint()
|
||||||
view.SetBestHash(parentHash)
|
view.SetTips(parentNodes)
|
||||||
stxos := make([]spentTxOut, 0, countSpentOutputs(block))
|
stxos := make([]spentTxOut, 0, countSpentOutputs(block))
|
||||||
if !fastAdd {
|
if !fastAdd {
|
||||||
err := b.checkConnectBlock(node, block, view, &stxos)
|
err := b.checkConnectBlock(node, block, view, &stxos)
|
||||||
@ -1073,7 +1073,7 @@ func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, fla
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
err = view.connectTransactions(block, &stxos)
|
err = view.connectTransactions(node, block.Transactions(), &stxos)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
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 {
|
if node.workSum.Cmp(b.bestChain.SelectedTip().workSum) <= 0 {
|
||||||
// Log information about how the block is forking the chain.
|
// Log information about how the block is forking the chain.
|
||||||
fork := b.bestChain.FindFork(node)
|
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"+
|
log.Infof("FORK: Block %v forks the chain at height %d"+
|
||||||
"/block %v, but does not cause a reorganize",
|
"/block %v, but does not cause a reorganize",
|
||||||
node.hash, fork.height, fork.hash)
|
node.hash, fork.height, fork.hash)
|
||||||
|
@ -147,7 +147,7 @@ func TestCalcSequenceLock(t *testing.T) {
|
|||||||
})
|
})
|
||||||
utxoView := NewUtxoViewpoint()
|
utxoView := NewUtxoViewpoint()
|
||||||
utxoView.AddTxOuts(targetTx, int32(numBlocksToActivate)-4)
|
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
|
// 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
|
// transactions created in the tests. It has an age of 4 blocks. Note
|
||||||
|
@ -1162,7 +1162,7 @@ func (b *BlockChain) initChainState() error {
|
|||||||
// Initialize the block node for the block, connect it,
|
// Initialize the block node for the block, connect it,
|
||||||
// and add it to the block index.
|
// and add it to the block index.
|
||||||
node := &blockNodes[i]
|
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
|
node.status = status
|
||||||
b.index.addNode(node)
|
b.index.addNode(node)
|
||||||
|
|
||||||
|
@ -96,19 +96,19 @@ func (c *chainView) tip() *blockNode {
|
|||||||
// an empty slice if there is no tip.
|
// an empty slice if there is no tip.
|
||||||
//
|
//
|
||||||
// This function is safe for concurrent access.
|
// This function is safe for concurrent access.
|
||||||
func (c *chainView) Tips() []*blockNode {
|
func (c *chainView) Tips() blockSet {
|
||||||
c.mtx.Lock()
|
c.mtx.Lock()
|
||||||
tip := c.tip()
|
tip := c.tip()
|
||||||
c.mtx.Unlock()
|
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.
|
// SelecedTip returns the current selected tip block node for the chain view.
|
||||||
// It will return nil if there is no tip.
|
// It will return nil if there is no tip.
|
||||||
//
|
//
|
||||||
// This function is safe for concurrent access.
|
// This function is safe for concurrent access.
|
||||||
func (view *chainView) SelectedTip() *blockNode {
|
func (c *chainView) SelectedTip() *blockNode {
|
||||||
return view.Tips()[0]
|
return c.Tips().first()
|
||||||
}
|
}
|
||||||
|
|
||||||
// setTip sets the chain view to use the provided block node as the current tip
|
// setTip sets the chain view to use the provided block node as the current tip
|
||||||
|
@ -10,8 +10,8 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/daglabs/btcd/wire"
|
|
||||||
"github.com/daglabs/btcd/dagconfig/daghash"
|
"github.com/daglabs/btcd/dagconfig/daghash"
|
||||||
|
"github.com/daglabs/btcd/wire"
|
||||||
)
|
)
|
||||||
|
|
||||||
// testNoncePrng provides a deterministic prng for the nonce in generated fake
|
// 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 {
|
if tip != nil {
|
||||||
header.PrevBlocks = []daghash.Hash{tip.hash} // TODO: (Stas) This is wrong. Modified only to satisfy compilation.
|
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]
|
tip = nodes[i]
|
||||||
}
|
}
|
||||||
return nodes
|
return nodes
|
||||||
|
@ -380,5 +380,5 @@ func newFakeNode(parent *blockNode, blockVersion int32, bits uint32, timestamp t
|
|||||||
Bits: bits,
|
Bits: bits,
|
||||||
Timestamp: timestamp,
|
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.
|
||||||
}
|
}
|
||||||
|
@ -119,20 +119,32 @@ func (entry *UtxoEntry) Clone() *UtxoEntry {
|
|||||||
// The unspent outputs are needed by other transactions for things such as
|
// The unspent outputs are needed by other transactions for things such as
|
||||||
// script validation and double spend prevention.
|
// script validation and double spend prevention.
|
||||||
type UtxoViewpoint struct {
|
type UtxoViewpoint struct {
|
||||||
entries map[wire.OutPoint]*UtxoEntry
|
entries map[wire.OutPoint]*UtxoEntry
|
||||||
bestHash daghash.Hash
|
tips blockSet
|
||||||
}
|
}
|
||||||
|
|
||||||
// BestHash returns the hash of the best block in the chain the view currently
|
// Tips returns the hashes of the tips in the DAG the view currently
|
||||||
// respresents.
|
// represents.
|
||||||
func (view *UtxoViewpoint) BestHash() *daghash.Hash {
|
func (view *UtxoViewpoint) Tips() blockSet {
|
||||||
return &view.bestHash
|
return view.tips
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetBestHash sets the hash of the best block in the chain the view currently
|
// SetTips sets the hashes of the tips in the DAG the view currently
|
||||||
// respresents.
|
// represents.
|
||||||
func (view *UtxoViewpoint) SetBestHash(hash *daghash.Hash) {
|
func (view *UtxoViewpoint) SetTips(tips blockSet) {
|
||||||
view.bestHash = *hash
|
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
|
// 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.
|
// 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
|
// In addition, when the 'stxos' argument is not nil, it will be updated to
|
||||||
// append an entry for each spent txout.
|
// append an entry for each spent txout.
|
||||||
func (view *UtxoViewpoint) connectTransactions(block *btcutil.Block, stxos *[]spentTxOut) error {
|
func (view *UtxoViewpoint) connectTransactions(block *blockNode, transactions []*btcutil.Tx, stxos *[]spentTxOut) error {
|
||||||
for _, tx := range block.Transactions() {
|
for _, tx := range transactions {
|
||||||
err := view.connectTransaction(tx, block.Height(), stxos)
|
err := view.connectTransaction(tx, block.height, stxos)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
// Update the best hash for view to include this block since all of its
|
||||||
// transactions have been connected.
|
// transactions have been connected.
|
||||||
view.SetBestHash(block.Hash())
|
view.AddBlock(block)
|
||||||
return nil
|
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
|
// 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
|
// using the provided spent txo information, and setting the best hash for the
|
||||||
// view to the block before the passed block.
|
// 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.
|
// Sanity check the correct number of stxos are provided.
|
||||||
if len(stxos) != countSpentOutputs(block) {
|
if len(stxos) != countSpentOutputs(block) {
|
||||||
return AssertError("disconnectTransactions called with bad " +
|
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.
|
// transactions for the current block have been disconnected.
|
||||||
view.SetBestHash(block.MsgBlock().Header.SelectedPrevBlock())
|
view.SetTips(parents)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -649,13 +649,13 @@ func checkSerializedHeight(coinbaseTx *btcutil.Tx, wantHeight int32) error {
|
|||||||
// the checkpoints are not performed.
|
// the checkpoints are not performed.
|
||||||
//
|
//
|
||||||
// This function MUST be called with the chain state lock held (for writes).
|
// 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
|
fastAdd := flags&BFFastAdd == BFFastAdd
|
||||||
if !fastAdd {
|
if !fastAdd {
|
||||||
// Ensure the difficulty specified in the block header matches
|
// Ensure the difficulty specified in the block header matches
|
||||||
// the calculated difficulty based on the previous block and
|
// the calculated difficulty based on the previous block and
|
||||||
// difficulty retarget rules.
|
// difficulty retarget rules.
|
||||||
expectedDifficulty, err := b.calcNextRequiredDifficulty(prevNode,
|
expectedDifficulty, err := b.calcNextRequiredDifficulty(selectedParent,
|
||||||
header.Timestamp)
|
header.Timestamp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -669,7 +669,7 @@ func (b *BlockChain) checkBlockHeaderContext(header *wire.BlockHeader, prevNode
|
|||||||
|
|
||||||
// Ensure the timestamp for the block header is after the
|
// Ensure the timestamp for the block header is after the
|
||||||
// median time of the last several blocks (medianTimeBlocks).
|
// median time of the last several blocks (medianTimeBlocks).
|
||||||
medianTime := prevNode.CalcPastMedianTime()
|
medianTime := selectedParent.CalcPastMedianTime()
|
||||||
if !header.Timestamp.After(medianTime) {
|
if !header.Timestamp.After(medianTime) {
|
||||||
str := "block timestamp of %v is not after expected %v"
|
str := "block timestamp of %v is not after expected %v"
|
||||||
str = fmt.Sprintf(str, header.Timestamp, medianTime)
|
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
|
// The height of this block is one more than the referenced previous
|
||||||
// block.
|
// block.
|
||||||
blockHeight := prevNode.height + 1
|
blockHeight := selectedParent.height + 1
|
||||||
|
|
||||||
// Ensure chain matches up to predetermined checkpoints.
|
// Ensure chain matches up to predetermined checkpoints.
|
||||||
blockHash := header.BlockHash()
|
blockHash := header.BlockHash()
|
||||||
@ -731,10 +731,10 @@ func (b *BlockChain) checkBlockHeaderContext(header *wire.BlockHeader, prevNode
|
|||||||
// for how the flags modify its behavior.
|
// for how the flags modify its behavior.
|
||||||
//
|
//
|
||||||
// This function MUST be called with the chain state lock held (for writes).
|
// 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.
|
// Perform all block header related validation checks.
|
||||||
header := &block.MsgBlock().Header
|
header := &block.MsgBlock().Header
|
||||||
err := b.checkBlockHeaderContext(header, prevNode, flags)
|
err := b.checkBlockHeaderContext(header, selectedParent, flags)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
// Obtain the latest state of the deployed CSV soft-fork in
|
||||||
// order to properly guard the new validation behavior based on
|
// order to properly guard the new validation behavior based on
|
||||||
// the current BIP 9 version bits state.
|
// the current BIP 9 version bits state.
|
||||||
csvState, err := b.deploymentState(prevNode, dagconfig.DeploymentCSV)
|
csvState, err := b.deploymentState(selectedParent, dagconfig.DeploymentCSV)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -754,12 +754,12 @@ func (b *BlockChain) checkBlockContext(block *btcutil.Block, prevNode *blockNode
|
|||||||
// timestamps for all lock-time based checks.
|
// timestamps for all lock-time based checks.
|
||||||
blockTime := header.Timestamp
|
blockTime := header.Timestamp
|
||||||
if csvState == ThresholdActive {
|
if csvState == ThresholdActive {
|
||||||
blockTime = prevNode.CalcPastMedianTime()
|
blockTime = selectedParent.CalcPastMedianTime()
|
||||||
}
|
}
|
||||||
|
|
||||||
// The height of this block is one more than the referenced
|
// The height of this block is one more than the referenced
|
||||||
// previous block.
|
// previous block.
|
||||||
blockHeight := prevNode.height + 1
|
blockHeight := selectedParent.height + 1
|
||||||
|
|
||||||
// Ensure all transactions in the block are finalized.
|
// Ensure all transactions in the block are finalized.
|
||||||
for _, tx := range block.Transactions() {
|
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.
|
// Ensure the view is for the node being checked.
|
||||||
parentHash := block.MsgBlock().Header.SelectedPrevBlock()
|
parentHashes := block.MsgBlock().Header.PrevBlocks
|
||||||
if !view.BestHash().IsEqual(parentHash) {
|
if !view.Tips().hashesEqual(parentHashes) {
|
||||||
return AssertError(fmt.Sprintf("inconsistent view when "+
|
return AssertError(fmt.Sprintf("inconsistent view when "+
|
||||||
"checking block connection: best hash is %v instead "+
|
"checking block connection: tips are %v instead "+
|
||||||
"of expected %v", view.BestHash(), parentHash))
|
"of expected %v", view.Tips(), parentHashes))
|
||||||
}
|
}
|
||||||
|
|
||||||
// BIP0030 added a rule to prevent blocks which contain duplicate
|
// 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.
|
// transactions have been connected.
|
||||||
view.SetBestHash(&node.hash)
|
view.AddBlock(node)
|
||||||
|
|
||||||
return nil
|
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
|
// This only checks whether the block can be connected to the tip of the
|
||||||
// current chain.
|
// current chain.
|
||||||
tip := b.bestChain.SelectedTip()
|
tips := b.bestChain.Tips()
|
||||||
header := block.MsgBlock().Header
|
header := block.MsgBlock().Header
|
||||||
if tip.hash != *header.SelectedPrevBlock() {
|
prevHashes := header.PrevBlocks
|
||||||
str := fmt.Sprintf("previous block must be the current chain tip %v, "+
|
if tips.hashesEqual(prevHashes) {
|
||||||
"instead got %v", tip.hash, header.SelectedPrevBlock())
|
str := fmt.Sprintf("previous blocks must be the currents tips %v, "+
|
||||||
|
"instead got %v", tips, prevHashes)
|
||||||
return ruleError(ErrPrevBlockNotBest, str)
|
return ruleError(ErrPrevBlockNotBest, str)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1223,7 +1224,7 @@ func (b *BlockChain) CheckConnectBlockTemplate(block *btcutil.Block) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = b.checkBlockContext(block, tip, flags)
|
err = b.checkBlockContext(block, b.bestChain.SelectedTip(), flags)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
// Leave the spent txouts entry nil in the state since the information
|
||||||
// is not needed and thus extra work can be avoided.
|
// is not needed and thus extra work can be avoided.
|
||||||
view := NewUtxoViewpoint()
|
view := NewUtxoViewpoint()
|
||||||
view.SetBestHash(&tip.hash)
|
view.SetTips(tips)
|
||||||
newNode := newBlockNode(&header, []*blockNode{tip}) // TODO: (Stas) This is wrong. Modified only to satisfy compilation.
|
newNode := newBlockNode(&header, b.bestChain.Tips())
|
||||||
return b.checkConnectBlock(newNode, block, view, nil)
|
return b.checkConnectBlock(newNode, block, view, nil)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user