From 79a0c1f124b04db5d832b6892bc59d218e47813b Mon Sep 17 00:00:00 2001 From: stasatdaglabs <39559713+stasatdaglabs@users.noreply.github.com> Date: Sun, 1 Jul 2018 15:52:56 +0300 Subject: [PATCH] [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. --- blockdag/accept.go | 18 ++--- blockdag/blockindex.go | 12 ++-- blockdag/blockset.go | 130 +++++++++++++++++++++++++++++++++++++ blockdag/chain.go | 58 ++++++++--------- blockdag/chain_test.go | 2 +- blockdag/chainio.go | 2 +- blockdag/chainview.go | 8 +-- blockdag/chainview_test.go | 4 +- blockdag/common_test.go | 2 +- blockdag/utxoviewpoint.go | 46 ++++++++----- blockdag/validate.go | 45 ++++++------- 11 files changed, 235 insertions(+), 92 deletions(-) create mode 100644 blockdag/blockset.go diff --git a/blockdag/accept.go b/blockdag/accept.go index 4716b4b90..3c9331a09 100644 --- a/blockdag/accept.go +++ b/blockdag/accept.go @@ -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 diff --git a/blockdag/blockindex.go b/blockdag/blockindex.go index fd35c54fe..5162eb150 100644 --- a/blockdag/blockindex.go +++ b/blockdag/blockindex.go @@ -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 diff --git a/blockdag/blockset.go b/blockdag/blockset.go new file mode 100644 index 000000000..b880da32e --- /dev/null +++ b/blockdag/blockset.go @@ -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, ",") +} diff --git a/blockdag/chain.go b/blockdag/chain.go index 5a973fd6b..4d5625228 100644 --- a/blockdag/chain.go +++ b/blockdag/chain.go @@ -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) diff --git a/blockdag/chain_test.go b/blockdag/chain_test.go index 0874379ff..2c246a4b6 100644 --- a/blockdag/chain_test.go +++ b/blockdag/chain_test.go @@ -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 diff --git a/blockdag/chainio.go b/blockdag/chainio.go index c7b3f30af..2acb509c8 100644 --- a/blockdag/chainio.go +++ b/blockdag/chainio.go @@ -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) diff --git a/blockdag/chainview.go b/blockdag/chainview.go index 1c7db669d..93ee16c57 100644 --- a/blockdag/chainview.go +++ b/blockdag/chainview.go @@ -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 diff --git a/blockdag/chainview_test.go b/blockdag/chainview_test.go index a8bacae00..f0fcb7dad 100644 --- a/blockdag/chainview_test.go +++ b/blockdag/chainview_test.go @@ -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 diff --git a/blockdag/common_test.go b/blockdag/common_test.go index 08b45370c..88df2ff5c 100644 --- a/blockdag/common_test.go +++ b/blockdag/common_test.go @@ -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. } diff --git a/blockdag/utxoviewpoint.go b/blockdag/utxoviewpoint.go index 36d6edeb7..d93b2a69b 100644 --- a/blockdag/utxoviewpoint.go +++ b/blockdag/utxoviewpoint.go @@ -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 } diff --git a/blockdag/validate.go b/blockdag/validate.go index ba70b5a8d..e41e5bbbc 100644 --- a/blockdag/validate.go +++ b/blockdag/validate.go @@ -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) }