[NOD-700] Convert blockSet to map[*blockNode]struct{} (#604)

* [NOD-700] Convert blockSet to map[*blockNode]struct{}.

* [NOD-700] Rename bluestNode to bluestBlock in bluest().

* [NOD-700] Make IsInSelectedParentChain not use the now-slower containsHash.

* [NOD-700] Rename block to node in blockset.go.

* [NOD-700] Remove containsHash and hashesEqual.

* [NOD-700] Add a comment to IsInSelectedParentChain about how it'll fail if the given blockHash is not within the block index.
This commit is contained in:
stasatdaglabs 2020-01-27 11:48:58 +02:00 committed by Ori Newman
parent a10320ad7b
commit 11de12304e
12 changed files with 91 additions and 134 deletions

View File

@ -148,7 +148,7 @@ func (dag *BlockDAG) newBlockNode(blockHeader *wire.BlockHeader, parents blockSe
// updateParentsChildren updates the node's parents to point to new node
func (node *blockNode) updateParentsChildren() {
for _, parent := range node.parents {
for parent := range node.parents {
parent.children.add(node)
}
}

View File

@ -7,38 +7,38 @@ import (
)
// blockSet implements a basic unsorted set of blocks
type blockSet map[daghash.Hash]*blockNode
type blockSet map[*blockNode]struct{}
// newSet creates a new, empty BlockSet
func newSet() blockSet {
return map[daghash.Hash]*blockNode{}
return map[*blockNode]struct{}{}
}
// setFromSlice converts a slice of blocks into an unordered set represented as map
func setFromSlice(blocks ...*blockNode) blockSet {
// setFromSlice converts a slice of blockNodes into an unordered set represented as map
func setFromSlice(nodes ...*blockNode) blockSet {
set := newSet()
for _, block := range blocks {
set.add(block)
for _, node := range nodes {
set.add(node)
}
return set
}
// add adds a block to this BlockSet
func (bs blockSet) add(block *blockNode) {
bs[*block.hash] = block
// add adds a blockNode to this BlockSet
func (bs blockSet) add(node *blockNode) {
bs[node] = struct{}{}
}
// 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)
// remove removes a blockNode from this BlockSet, if exists
// Does nothing if this set does not contain the blockNode
func (bs blockSet) remove(node *blockNode) {
delete(bs, node)
}
// clone clones thie block set
func (bs blockSet) clone() blockSet {
clone := newSet()
for _, block := range bs {
clone.add(block)
for node := range bs {
clone.add(node)
}
return clone
}
@ -46,29 +46,29 @@ func (bs blockSet) clone() blockSet {
// 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)
for node := range bs {
if !other.contains(node) {
diff.add(node)
}
}
return diff
}
// addSet adds all blocks in other set to this set
// addSet adds all blockNodes in other set to this set
func (bs blockSet) addSet(other blockSet) {
for _, block := range other {
bs.add(block)
for node := range other {
bs.add(node)
}
}
// addSlice adds provided slice to this set
func (bs blockSet) addSlice(slice []*blockNode) {
for _, block := range slice {
bs.add(block)
for _, node := range slice {
bs.add(node)
}
}
// union returns a BlockSet that contains all blocks included in this set,
// union returns a BlockSet that contains all blockNodes included in this set,
// the other set, or both
func (bs blockSet) union(other blockSet) blockSet {
union := bs.clone()
@ -78,39 +78,16 @@ func (bs blockSet) union(other blockSet) blockSet {
return union
}
// contains returns true iff this set contains block
func (bs blockSet) contains(block *blockNode) bool {
_, ok := bs[*block.hash]
// contains returns true iff this set contains node
func (bs blockSet) contains(node *blockNode) bool {
_, ok := bs[node]
return ok
}
// containsHash returns true iff this set contains a block hash
func (bs blockSet) containsHash(hash *daghash.Hash) bool {
_, ok := bs[*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
}
// hashes returns the hashes of the blocks in this set.
// hashes returns the hashes of the blockNodes in this set.
func (bs blockSet) hashes() []*daghash.Hash {
hashes := make([]*daghash.Hash, 0, len(bs))
for _, node := range bs {
for node := range bs {
hashes = append(hashes, node.hash)
}
daghash.Sort(hashes)
@ -119,15 +96,15 @@ func (bs blockSet) hashes() []*daghash.Hash {
func (bs blockSet) String() string {
nodeStrs := make([]string, 0, len(bs))
for _, node := range bs {
for node := range bs {
nodeStrs = append(nodeStrs, node.String())
}
return strings.Join(nodeStrs, ",")
}
// anyChildInSet returns true iff any child of block is contained within this set
func (bs blockSet) anyChildInSet(block *blockNode) bool {
for _, child := range block.children {
// anyChildInSet returns true iff any child of node is contained within this set
func (bs blockSet) anyChildInSet(node *blockNode) bool {
for child := range node.children {
if bs.contains(child) {
return true
}
@ -139,7 +116,7 @@ func (bs blockSet) anyChildInSet(block *blockNode) bool {
func (bs blockSet) bluest() *blockNode {
var bluestNode *blockNode
var maxScore uint64
for _, node := range bs {
for node := range bs {
if bluestNode == nil ||
node.blueScore > maxScore ||
(node.blueScore == maxScore && daghash.Less(node.hash, bluestNode.hash)) {

View File

@ -243,54 +243,3 @@ func TestBlockSetUnion(t *testing.T) {
}
}
}
func TestBlockSetHashesEqual(t *testing.T) {
node1 := &blockNode{hash: &daghash.Hash{10}}
node2 := &blockNode{hash: &daghash.Hash{20}}
tests := []struct {
name string
set blockSet
hashes []*daghash.Hash
expectedResult bool
}{
{
name: "empty set, no hashes",
set: setFromSlice(),
hashes: []*daghash.Hash{},
expectedResult: true,
},
{
name: "empty set, one hash",
set: setFromSlice(),
hashes: []*daghash.Hash{node1.hash},
expectedResult: false,
},
{
name: "set and hashes of different length",
set: setFromSlice(node1, node2),
hashes: []*daghash.Hash{node1.hash},
expectedResult: false,
},
{
name: "set equal to hashes",
set: setFromSlice(node1, node2),
hashes: []*daghash.Hash{node1.hash, node2.hash},
expectedResult: true,
},
{
name: "set equal to hashes, different order",
set: setFromSlice(node1, node2),
hashes: []*daghash.Hash{node2.hash, node1.hash},
expectedResult: true,
},
}
for _, test := range tests {
result := test.set.hashesEqual(test.hashes)
if result != test.expectedResult {
t.Errorf("blockSet.hashesEqual: unexpected result in test '%s'. "+
"Expected: %t, got: %t", test.name, test.expectedResult, result)
}
}
}

View File

@ -154,7 +154,7 @@ func newTestNode(dag *BlockDAG, parents blockSet, blockVersion int32, bits uint3
}
func addNodeAsChildToParents(node *blockNode) {
for _, parent := range node.parents {
for parent := range node.parents {
parent.children.add(node)
}
}

View File

@ -773,7 +773,7 @@ func (dag *BlockDAG) updateFinalityPoint() {
func (dag *BlockDAG) finalizeNodesBelowFinalityPoint(deleteDiffData bool) {
queue := make([]*blockNode, 0, len(dag.lastFinalityPoint.parents))
for _, parent := range dag.lastFinalityPoint.parents {
for parent := range dag.lastFinalityPoint.parents {
queue = append(queue, parent)
}
var blockHashesToDelete []*daghash.Hash
@ -788,7 +788,7 @@ func (dag *BlockDAG) finalizeNodesBelowFinalityPoint(deleteDiffData bool) {
if deleteDiffData {
blockHashesToDelete = append(blockHashesToDelete, current.hash)
}
for _, parent := range current.parents {
for parent := range current.parents {
queue = append(queue, parent)
}
}
@ -1125,7 +1125,7 @@ func (node *blockNode) updateParentsDiffs(dag *BlockDAG, newBlockUTXO UTXOSet) e
return err
}
for _, parent := range node.parents {
for parent := range node.parents {
diffChild, err := dag.utxoDiffStore.diffChildByNode(parent)
if err != nil {
return err
@ -1236,7 +1236,7 @@ func (dag *BlockDAG) restoreUTXO(node *blockNode) (UTXOSet, error) {
// updateTipsUTXO builds and applies new diff UTXOs for all the DAG's tips
func updateTipsUTXO(dag *BlockDAG, virtualUTXO UTXOSet) error {
for _, tip := range dag.virtual.parents {
for tip := range dag.virtual.parents {
tipUTXO, err := dag.restoreUTXO(tip)
if err != nil {
return err
@ -1419,13 +1419,21 @@ func (dag *BlockDAG) acceptingBlock(node *blockNode) (*blockNode, error) {
}
// If the node is a chain-block itself, the accepting block is its chain-child
if dag.IsInSelectedParentChain(node.hash) {
isNodeInSelectedParentChain, err := dag.IsInSelectedParentChain(node.hash)
if err != nil {
return nil, err
}
if isNodeInSelectedParentChain {
if len(node.children) == 0 {
// If the node is the selected tip, it doesn't have an accepting block
return nil, nil
}
for _, child := range node.children {
if dag.IsInSelectedParentChain(child.hash) {
for child := range node.children {
isChildInSelectedParentChain, err := dag.IsInSelectedParentChain(child.hash)
if err != nil {
return nil, err
}
if isChildInSelectedParentChain {
return child, nil
}
}
@ -1469,11 +1477,17 @@ func (dag *BlockDAG) oldestChainBlockWithBlueScoreGreaterThan(blueScore uint64)
}
// IsInSelectedParentChain returns whether or not a block hash is found in the selected
// parent chain.
// parent chain. Note that this method returns an error if the given blockHash does not
// exist within the block index.
//
// This method MUST be called with the DAG lock held
func (dag *BlockDAG) IsInSelectedParentChain(blockHash *daghash.Hash) bool {
return dag.virtual.selectedParentChainSet.containsHash(blockHash)
func (dag *BlockDAG) IsInSelectedParentChain(blockHash *daghash.Hash) (bool, error) {
blockNode := dag.index.LookupNode(blockHash)
if blockNode == nil {
str := fmt.Sprintf("block %s is not in the DAG", blockHash)
return false, errNotInDAG(str)
}
return dag.virtual.selectedParentChainSet.contains(blockNode), nil
}
// SelectedParentChain returns the selected parent chain starting from blockHash (exclusive)
@ -1494,7 +1508,11 @@ func (dag *BlockDAG) SelectedParentChain(blockHash *daghash.Hash) ([]*daghash.Ha
// If blockHash is not in the selected parent chain, go down its selected parent chain
// until we find a block that is in the main selected parent chain.
var removedChainHashes []*daghash.Hash
for !dag.IsInSelectedParentChain(blockHash) {
isBlockInSelectedParentChain, err := dag.IsInSelectedParentChain(blockHash)
if err != nil {
return nil, nil, err
}
for !isBlockInSelectedParentChain {
removedChainHashes = append(removedChainHashes, blockHash)
node := dag.index.LookupNode(blockHash)
@ -1544,7 +1562,7 @@ func (dag *BlockDAG) TipHashes() []*daghash.Hash {
func (dag *BlockDAG) CurrentBits() uint32 {
tips := dag.virtual.tips()
minBits := uint32(math.MaxUint32)
for _, tip := range tips {
for tip := range tips {
if minBits > tip.Header().Bits {
minBits = tip.Header().Bits
}
@ -1665,7 +1683,7 @@ func (dag *BlockDAG) antiPastBetween(lowHash, highHash *daghash.Hash, maxEntries
continue
}
candidateNodes.Push(current)
for _, parent := range current.parents {
for parent := range current.parents {
queue.Push(parent)
}
}

View File

@ -723,7 +723,7 @@ func TestConfirmations(t *testing.T) {
// Check that each of the tips has a 0 confirmations
tips := dag.virtual.tips()
for _, tip := range tips {
for tip := range tips {
tipConfirmations, err := dag.blockConfirmations(tip)
if err != nil {
t.Fatalf("TestConfirmations: confirmations for tip unexpectedly failed: %s", err)
@ -828,7 +828,11 @@ func TestAcceptingBlock(t *testing.T) {
branchingChainTip := prepareAndProcessBlock(t, dag, chainBlocks[len(chainBlocks)-3])
// Make sure that branchingChainTip is not in the selected parent chain
if dag.IsInSelectedParentChain(branchingChainTip.BlockHash()) {
isBranchingChainTipInSelectedParentChain, err := dag.IsInSelectedParentChain(branchingChainTip.BlockHash())
if err != nil {
t.Fatalf("TestAcceptingBlock: IsInSelectedParentChain unexpectedly failed: %s", err)
}
if isBranchingChainTipInSelectedParentChain {
t.Fatalf("TestAcceptingBlock: branchingChainTip wasn't expected to be in the selected parent chain")
}

View File

@ -131,7 +131,7 @@ func (dag *BlockDAG) selectedParentAnticone(node *blockNode) ([]*blockNode, erro
selectedParentPast := newSet()
var queue []*blockNode
// Queueing all parents (other than the selected parent itself) for processing.
for _, parent := range node.parents {
for parent := range node.parents {
if parent == node.selectedParent {
continue
}
@ -144,7 +144,7 @@ func (dag *BlockDAG) selectedParentAnticone(node *blockNode) ([]*blockNode, erro
current, queue = queue[0], queue[1:]
// For each parent of a the current node we check whether it is in the past of the selected parent. If not,
// we add the it to the resulting anticone-set and queue it for further processing.
for _, parent := range current.parents {
for parent := range current.parents {
if anticoneSet.contains(parent) || selectedParentPast.contains(parent) {
continue
}

View File

@ -389,7 +389,11 @@ func dbFetchTxAcceptingBlock(dbTx database.Tx, txID *daghash.TxID, dag *blockdag
if err != nil {
return nil, err
}
if dag.IsInSelectedParentChain(blockHash) {
isBlockInSelectedParentChain, err := dag.IsInSelectedParentChain(blockHash)
if err != nil {
return nil, err
}
if isBlockInSelectedParentChain {
return blockHash, nil
}
}

View File

@ -602,7 +602,7 @@ func validateParents(blockHeader *wire.BlockHeader, parents blockSet) error {
minBlueScore := uint64(math.MaxUint64)
queue := newDownHeap()
visited := newSet()
for _, parent := range parents {
for parent := range parents {
// isFinalized might be false-negative because node finality status is
// updated in a separate goroutine. This is why later the block is
// checked more thoroughly on the finality rules in dag.checkFinalityRules.
@ -612,7 +612,7 @@ func validateParents(blockHeader *wire.BlockHeader, parents blockSet) error {
if parent.blueScore < minBlueScore {
minBlueScore = parent.blueScore
}
for _, grandParent := range parent.parents {
for grandParent := range parent.parents {
if !visited.contains(grandParent) {
queue.Push(grandParent)
visited.add(grandParent)
@ -628,7 +628,7 @@ func validateParents(blockHeader *wire.BlockHeader, parents blockSet) error {
blockHeader.BlockHash()))
}
if current.blueScore > minBlueScore {
for _, parent := range current.parents {
for parent := range current.parents {
if !visited.contains(parent) {
queue.Push(parent)
visited.add(parent)

View File

@ -124,7 +124,8 @@ func TestCheckConnectBlockTemplate(t *testing.T) {
}
blockNode3 := dag.index.LookupNode(blocks[3].Hash())
if blockNode3.children.containsHash(blocks[4].Hash()) {
blockNode4 := dag.index.LookupNode(blocks[4].Hash())
if blockNode3.children.contains(blockNode4) {
t.Errorf("Block 4 wasn't successfully detached as a child from block3")
}

View File

@ -134,7 +134,7 @@ func (v *virtualBlock) SetTips(tips blockSet) {
// This function MUST be called with the view mutex locked (for writes).
func (v *virtualBlock) addTip(newTip *blockNode) *chainUpdates {
updatedTips := v.tips().clone()
for _, parent := range newTip.parents {
for parent := range newTip.parents {
updatedTips.remove(parent)
}

View File

@ -240,7 +240,11 @@ func buildGetBlockVerboseResult(s *Server, block *util.Block, isVerboseTx bool)
selectedParentHashStr = selectedParentHash.String()
}
isChainBlock := s.cfg.DAG.IsInSelectedParentChain(hash)
isChainBlock, err := s.cfg.DAG.IsInSelectedParentChain(hash)
if err != nil {
context := "Could not get whether block is in the selected parent chain"
return nil, internalRPCError(err.Error(), context)
}
result := &rpcmodel.GetBlockVerboseResult{
Hash: hash.String(),