mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-03-30 15:08:33 +00:00
[NOD-1063] Optimize deep reachability tree insertions (#773)
* [NOD-1055] Give higher priority for requesting missing ancestors when sending a getdata message (#767) * [NOD-1063] Remove the remainingInterval field. * [NOD-1063] Add helper functions to reachabilityTreeNode. * [NOD-1063] Add reachabilityReindexRoot. * [NOD-1063] Start implementing findNextReachabilityReindexRoot. * [NOD-1063] Implement findCommonAncestor. * [NOD-1063] Implement findReachabilityTreeAncestorInChildren. * [NOD-1063] Add reachabilityReindexWindow. * [NOD-1063] Fix findReachabilityTreeAncestorInChildren. * [NOD-1063] Remove BlockDAG reference in findReachabilityTreeAncestorInChildren. * [NOD-1063] Extract updateReachabilityReindexRoot to a separate function. * [NOD-1063] Add reachabilityReindexSlack. * [NOD-1063] Implement splitReindexRootChildrenAroundChosen. * [NOD-1063] Implement calcReachabilityTreeNodeSizes. * [NOD-1063] Implement propagateChildIntervals. * [NOD-1063] Extract tightenReachabilityTreeIntervalsBeforeChosenReindexRootChild and tightenReachabilityTreeIntervalsAfterChosenReindexRootChild to separate functions. * [NOD-1063] Implement expandReachabilityTreeIntervalInChosenReindexRootChild. * [NOD-1063] Finished implementing concentrateReachabilityTreeIntervalAroundReindexRootChild. * [NOD-1063] Begin implementing reindexIntervalsBeforeReindexRoot. * [NOD-1063] Implement top-level logic of reindexIntervalsBeforeReindexRoot. * [NOD-1063] Implement reclaimIntervalBeforeChosenChild. * [NOD-1063] Add a debug log for reindexIntervalsBeforeReindexRoot. * [NOD-1063] Rename reindexIntervalsBeforeReindexRoot to reindexIntervalsEarlierThanReindexRoot. * [NOD-1063] Implement reclaimIntervalAfterChosenChild. * [NOD-1063] Add a debug log for updateReachabilityReindexRoot. * [NOD-1063] Convert modifiedTreeNodes from slices to sets. * [NOD-1063] Fix findCommonAncestor. * [NOD-1063] Fix reindexIntervalsEarlierThanReindexRoot.` * [NOD-1063] Remove redundant nil conditions. * [NOD-1063] Make map[*reachabilityTreeNode]struct{} into a type alias with a copyAllFrom method. * [NOD-1063] Remove setInterval. * [NOD-1063] Create a new struct to hold reachability stuff called reachabilityTree. * [NOD-1063] Rename functions under reachabilityTree. * [NOD-1063] Move reachabilityStore into reachabilityTree. * [NOD-1063] Move the rest of the functions in reachability.go into the reachabilityTree struct. * [NOD-1063] Update newReachabilityTree to take an instance of reachabilityStore. * [NOD-1063] Fix merge errors. * [NOD-1063] Fix merge errors. * [NOD-1063] Pass a reference to the dag into reachabilityTree. * [NOD-1063] Use Wrapf instead of Errorf. * [NOD-1063] Merge assignments. * [NOD-1063] Disambiguate a varaible name. * [NOD-1063] Add a test case for intervalBefore. * [NOD-1063] Simplify splitChildrenAroundChosenChild. * [NOD-1063] Fold temporary variables into newReachabilityInterval. * [NOD-1063] Fold more temporary variables into newReachabilityInterval. * [NOD-1063] Fix a bug in expandIntervalInReindexRootChosenChild. * [NOD-1063] Remove blockNode from futureCoveringBlock. * [NOD-1063] Get rid of futureCoveringBlock. * [NOD-1063] Use findIndex directly in findAncestorAmongChildren. * [NOD-1063] Make findIndex a bit nicer to use. Also rename it to findAncestorIndexOfNode. * [NOD-1063] Rename childIntervalAllocationRange to intervalRangeForChildAllocation. * [NOD-1063] Optimize findCommonAncestor. * [NOD-1063] In reindexIntervalsBeforeChosenChild, use chosenChild.interval.start - 1 instead of childrenBeforeChosen[len(childrenBeforeChosen)-1].interval.end + 1. * [NOD-1063] Rename reindexIntervalsBeforeChosenChild to reindexIntervalsBeforeNode. * [NOD-1063] Add a comment explain what "the chosen child" is. * [NOD-1063] In concentrateIntervalAroundReindexRootChosenChild, rename modifiedTreeNodes to allModifiedTreeNodes. * [NOD-1063] Extract propagateIntervals to a function. * [NOD-1063] Extract interval "contains" logic to a separate function. * [NOD-1063] Simplify "looping up" logic in reclaimIntervalXXXChosenChild. * [NOD-1063] Add comments to reclaimIntervalXXXChosenChild. * [NOD-1063] Rename copyAllFrom to addAll. * [NOD-1063] Rename reachabilityStore (the variable) to just store. * [NOD-1063] Fix an error message. * [NOD-1063] Reword a comment. * [NOD-1063] Don't return -1 from findAncestorIndexOfNode. * [NOD-1063] Extract slackReachabilityIntervalForReclaiming to a constant. * [NOD-1063] Add a missing condition. * [NOD-1063] Call isAncestorOf directly in insertNode. * [NOD-1063] Rename chosenReindexRootChild to reindexRootChosenChild. * [NOD-1063] Rename treeNodeSet to orderedTreeNodeSet. * [NOD-1063] Add a disclaimer to orderedTreeNodeSet. * [NOD-1063] Implement StoreReachabilityReindexRoot and FetchReachabilityReindexRoot. * [NOD-1063] Move storing the reindex root to within reachabilityTree. * [NOD-1063] Remove isAncestorOf from reachabilityInterval. * [NOD-1063] Add a comment about graph theory conventions. * [NOD-1063] Fix tests. * [NOD-1063] Change inclusion in isAncestorOf functions. * [NOD-1063] Rename a test. * [NOD-1063] Implement TestIsInFuture. * [NOD-1063] Fix error messages in TestIsInFuture. * [NOD-1063] Fix error messages in TestIsInFuture. * [NOD-1063] Rename isInSelectedParentChain to isInSelectedParentChainOf. * [NOD-1063] Rename isInFuture to isInPast. * [NOD-1063] Expand on a comment. * [NOD-1063] Rename modifiedTreeNodes. * [NOD-1063] Implement test: TestReindexIntervalsEarlierThanReindexRoot. * [NOD-1063] Implement test: TestUpdateReindexRoot. * [NOD-1063] Explain a check. * [NOD-1063] Use a method instead of calling reachabilityStore.loaded directly. * [NOD-1063] Lowercasified an error message. * [NOD-1063] Fix failing test. Co-authored-by: Ori Newman <orinewman1@gmail.com>
This commit is contained in:
parent
a86255ba51
commit
57b1653383
@ -151,9 +151,10 @@ type BlockDAG struct {
|
||||
|
||||
lastFinalityPoint *blockNode
|
||||
|
||||
utxoDiffStore *utxoDiffStore
|
||||
reachabilityStore *reachabilityStore
|
||||
multisetStore *multisetStore
|
||||
utxoDiffStore *utxoDiffStore
|
||||
multisetStore *multisetStore
|
||||
|
||||
reachabilityTree *reachabilityTree
|
||||
|
||||
recentBlockProcessingTimestamps []time.Time
|
||||
startTime time.Time
|
||||
@ -700,7 +701,7 @@ func (dag *BlockDAG) saveChangesFromBlock(block *util.Block, virtualUTXODiff *UT
|
||||
return err
|
||||
}
|
||||
|
||||
err = dag.reachabilityStore.flushToDB(dbTx)
|
||||
err = dag.reachabilityTree.storeState(dbTx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -760,7 +761,7 @@ func (dag *BlockDAG) saveChangesFromBlock(block *util.Block, virtualUTXODiff *UT
|
||||
dag.index.clearDirtyEntries()
|
||||
dag.utxoDiffStore.clearDirtyEntries()
|
||||
dag.utxoDiffStore.clearOldEntries()
|
||||
dag.reachabilityStore.clearDirtyEntries()
|
||||
dag.reachabilityTree.store.clearDirtyEntries()
|
||||
dag.multisetStore.clearNewEntries()
|
||||
|
||||
return nil
|
||||
@ -816,19 +817,14 @@ func (dag *BlockDAG) LastFinalityPointHash() *daghash.Hash {
|
||||
return dag.lastFinalityPoint.hash
|
||||
}
|
||||
|
||||
// isInSelectedParentChain returns whether aNode is in the selected parent chain of bNode.
|
||||
func (dag *BlockDAG) isInSelectedParentChain(aNode, bNode *blockNode) (bool, error) {
|
||||
aTreeNode, err := dag.reachabilityStore.treeNodeByBlockNode(aNode)
|
||||
if err != nil {
|
||||
return false, err
|
||||
// isInSelectedParentChainOf returns whether `node` is in the selected parent chain of `other`.
|
||||
func (dag *BlockDAG) isInSelectedParentChainOf(node *blockNode, other *blockNode) (bool, error) {
|
||||
// By definition, a node is not in the selected parent chain of itself.
|
||||
if node == other {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
bTreeNode, err := dag.reachabilityStore.treeNodeByBlockNode(bNode)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return aTreeNode.interval.isAncestorOf(bTreeNode.interval), nil
|
||||
return dag.reachabilityTree.isReachabilityTreeAncestorOf(node, other)
|
||||
}
|
||||
|
||||
// checkFinalityViolation checks the new block does not violate the finality rules
|
||||
@ -848,7 +844,7 @@ func (dag *BlockDAG) checkFinalityViolation(newNode *blockNode) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
isInSelectedChain, err := dag.isInSelectedParentChain(dag.lastFinalityPoint, newNode.selectedParent)
|
||||
isInSelectedChain, err := dag.isInSelectedParentChainOf(dag.lastFinalityPoint, newNode.selectedParent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -995,10 +991,10 @@ func (dag *BlockDAG) applyDAGChanges(node *blockNode, newBlockPastUTXO UTXOSet,
|
||||
newBlockMultiset *secp256k1.MultiSet, selectedParentAnticone []*blockNode) (
|
||||
virtualUTXODiff *UTXODiff, chainUpdates *chainUpdates, err error) {
|
||||
|
||||
// Add the block to the reachability structures
|
||||
err = dag.updateReachability(node, selectedParentAnticone)
|
||||
// Add the block to the reachability tree
|
||||
err = dag.reachabilityTree.addBlock(node, selectedParentAnticone)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "failed updating reachability")
|
||||
return nil, nil, errors.Wrap(err, "failed adding block to the reachability tree")
|
||||
}
|
||||
|
||||
dag.multisetStore.setMultiset(node, newBlockMultiset)
|
||||
@ -1785,7 +1781,7 @@ func (dag *BlockDAG) antiPastBetween(lowHash, highHash *daghash.Hash, maxEntries
|
||||
continue
|
||||
}
|
||||
visited.add(current)
|
||||
isCurrentAncestorOfLowNode, err := dag.isAncestorOf(current, lowNode)
|
||||
isCurrentAncestorOfLowNode, err := dag.isInPast(current, lowNode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -1811,6 +1807,10 @@ func (dag *BlockDAG) antiPastBetween(lowHash, highHash *daghash.Hash, maxEntries
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
func (dag *BlockDAG) isInPast(this *blockNode, other *blockNode) (bool, error) {
|
||||
return dag.reachabilityTree.isInPast(this, other)
|
||||
}
|
||||
|
||||
// AntiPastHashesBetween returns the hashes of the blocks between the
|
||||
// lowHash's antiPast and highHash's antiPast, or up to the provided
|
||||
// max number of block hashes.
|
||||
@ -2064,8 +2064,8 @@ func New(config *Config) (*BlockDAG, error) {
|
||||
|
||||
dag.virtual = newVirtualBlock(dag, nil)
|
||||
dag.utxoDiffStore = newUTXODiffStore(dag)
|
||||
dag.reachabilityStore = newReachabilityStore(dag)
|
||||
dag.multisetStore = newMultisetStore(dag)
|
||||
dag.reachabilityTree = newReachabilityTree(dag)
|
||||
|
||||
// Initialize the DAG state from the passed database. When the db
|
||||
// does not yet contain any DAG state, both it and the DAG state
|
||||
|
@ -209,7 +209,7 @@ func (dag *BlockDAG) initDAGState() error {
|
||||
}
|
||||
|
||||
log.Debugf("Loading reachability data...")
|
||||
err = dag.reachabilityStore.init(dbaccess.NoTx())
|
||||
err = dag.reachabilityTree.init(dbaccess.NoTx())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -236,7 +236,8 @@ func (dag *BlockDAG) initDAGState() error {
|
||||
var ok bool
|
||||
dag.lastFinalityPoint, ok = dag.index.LookupNode(dagState.LastFinalityPoint)
|
||||
if !ok {
|
||||
return errors.Errorf("block %s does not exist in the DAG", dagState.LastFinalityPoint)
|
||||
return errors.Errorf("finality point block %s "+
|
||||
"does not exist in the DAG", dagState.LastFinalityPoint)
|
||||
}
|
||||
dag.finalizeNodesBelowFinalityPoint(false)
|
||||
|
||||
|
@ -57,7 +57,7 @@ func (dag *BlockDAG) ghostdag(newNode *blockNode) (selectedParentAnticone []*blo
|
||||
// newNode is always in the future of blueCandidate, so there's
|
||||
// no point in checking it.
|
||||
if chainBlock != newNode {
|
||||
if isAncestorOfBlueCandidate, err := dag.isAncestorOf(chainBlock, blueCandidate); err != nil {
|
||||
if isAncestorOfBlueCandidate, err := dag.isInPast(chainBlock, blueCandidate); err != nil {
|
||||
return nil, err
|
||||
} else if isAncestorOfBlueCandidate {
|
||||
break
|
||||
@ -66,7 +66,7 @@ func (dag *BlockDAG) ghostdag(newNode *blockNode) (selectedParentAnticone []*blo
|
||||
|
||||
for _, block := range chainBlock.blues {
|
||||
// Skip blocks that exist in the past of blueCandidate.
|
||||
if isAncestorOfBlueCandidate, err := dag.isAncestorOf(block, blueCandidate); err != nil {
|
||||
if isAncestorOfBlueCandidate, err := dag.isInPast(block, blueCandidate); err != nil {
|
||||
return nil, err
|
||||
} else if isAncestorOfBlueCandidate {
|
||||
continue
|
||||
@ -148,7 +148,7 @@ func (dag *BlockDAG) selectedParentAnticone(node *blockNode) ([]*blockNode, erro
|
||||
if anticoneSet.contains(parent) || selectedParentPast.contains(parent) {
|
||||
continue
|
||||
}
|
||||
isAncestorOfSelectedParent, err := dag.isAncestorOf(parent, node.selectedParent)
|
||||
isAncestorOfSelectedParent, err := dag.isInPast(parent, node.selectedParent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -349,7 +349,7 @@ func TestGHOSTDAGErrors(t *testing.T) {
|
||||
block3 := prepareAndProcessBlockByParentMsgBlocks(t, dag, block1, block2)
|
||||
|
||||
// Clear the reachability store
|
||||
dag.reachabilityStore.loaded = map[daghash.Hash]*reachabilityData{}
|
||||
dag.reachabilityTree.store.loaded = map[daghash.Hash]*reachabilityData{}
|
||||
|
||||
dbTx, err := dbaccess.NewTx()
|
||||
if err != nil {
|
||||
@ -377,7 +377,7 @@ func TestGHOSTDAGErrors(t *testing.T) {
|
||||
if err == nil {
|
||||
t.Fatalf("TestGHOSTDAGErrors: ghostdag unexpectedly succeeded")
|
||||
}
|
||||
expectedErrSubstring := "Couldn't find reachability data"
|
||||
expectedErrSubstring := "couldn't find reachability data"
|
||||
if !strings.Contains(err.Error(), expectedErrSubstring) {
|
||||
t.Fatalf("TestGHOSTDAGErrors: ghostdag returned wrong error. "+
|
||||
"Want: %s, got: %s", expectedErrSubstring, err)
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,8 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
@ -11,19 +13,19 @@ func TestAddChild(t *testing.T) {
|
||||
// root -> a -> b -> c...
|
||||
// Create the root node of a new reachability tree
|
||||
root := newReachabilityTreeNode(&blockNode{})
|
||||
root.setInterval(newReachabilityInterval(1, 100))
|
||||
root.interval = newReachabilityInterval(1, 100)
|
||||
|
||||
// Add a chain of child nodes just before a reindex occurs (2^6=64 < 100)
|
||||
currentTip := root
|
||||
for i := 0; i < 6; i++ {
|
||||
node := newReachabilityTreeNode(&blockNode{})
|
||||
modifiedNodes, err := currentTip.addChild(node)
|
||||
modifiedNodes, err := currentTip.addChild(node, root)
|
||||
if err != nil {
|
||||
t.Fatalf("TestAddChild: addChild failed: %s", err)
|
||||
}
|
||||
|
||||
// Expect only the node and its parent to be affected
|
||||
expectedModifiedNodes := []*reachabilityTreeNode{currentTip, node}
|
||||
expectedModifiedNodes := newModifiedTreeNodes(currentTip, node)
|
||||
if !reflect.DeepEqual(modifiedNodes, expectedModifiedNodes) {
|
||||
t.Fatalf("TestAddChild: unexpected modifiedNodes. "+
|
||||
"want: %s, got: %s", expectedModifiedNodes, modifiedNodes)
|
||||
@ -34,7 +36,7 @@ func TestAddChild(t *testing.T) {
|
||||
|
||||
// Add another node to the tip of the chain to trigger a reindex (100 < 2^7=128)
|
||||
lastChild := newReachabilityTreeNode(&blockNode{})
|
||||
modifiedNodes, err := currentTip.addChild(lastChild)
|
||||
modifiedNodes, err := currentTip.addChild(lastChild, root)
|
||||
if err != nil {
|
||||
t.Fatalf("TestAddChild: addChild failed: %s", err)
|
||||
}
|
||||
@ -45,14 +47,18 @@ func TestAddChild(t *testing.T) {
|
||||
t.Fatalf("TestAddChild: unexpected amount of modifiedNodes.")
|
||||
}
|
||||
|
||||
// Expect the tip to have an interval of 1 and remaining interval of 0
|
||||
// Expect the tip to have an interval of 1 and remaining interval of 0 both before and after
|
||||
tipInterval := lastChild.interval.size()
|
||||
if tipInterval != 1 {
|
||||
t.Fatalf("TestAddChild: unexpected tip interval size: want: 1, got: %d", tipInterval)
|
||||
}
|
||||
tipRemainingInterval := lastChild.remainingInterval.size()
|
||||
if tipRemainingInterval != 0 {
|
||||
t.Fatalf("TestAddChild: unexpected tip interval size: want: 0, got: %d", tipRemainingInterval)
|
||||
tipRemainingIntervalBefore := lastChild.remainingIntervalBefore().size()
|
||||
if tipRemainingIntervalBefore != 0 {
|
||||
t.Fatalf("TestAddChild: unexpected tip interval before size: want: 0, got: %d", tipRemainingIntervalBefore)
|
||||
}
|
||||
tipRemainingIntervalAfter := lastChild.remainingIntervalAfter().size()
|
||||
if tipRemainingIntervalAfter != 0 {
|
||||
t.Fatalf("TestAddChild: unexpected tip interval after size: want: 0, got: %d", tipRemainingIntervalAfter)
|
||||
}
|
||||
|
||||
// Expect all nodes to be descendant nodes of root
|
||||
@ -68,19 +74,19 @@ func TestAddChild(t *testing.T) {
|
||||
// root -> a, b, c...
|
||||
// Create the root node of a new reachability tree
|
||||
root = newReachabilityTreeNode(&blockNode{})
|
||||
root.setInterval(newReachabilityInterval(1, 100))
|
||||
root.interval = newReachabilityInterval(1, 100)
|
||||
|
||||
// Add child nodes to root just before a reindex occurs (2^6=64 < 100)
|
||||
childNodes := make([]*reachabilityTreeNode, 6)
|
||||
for i := 0; i < len(childNodes); i++ {
|
||||
childNodes[i] = newReachabilityTreeNode(&blockNode{})
|
||||
modifiedNodes, err := root.addChild(childNodes[i])
|
||||
modifiedNodes, err := root.addChild(childNodes[i], root)
|
||||
if err != nil {
|
||||
t.Fatalf("TestAddChild: addChild failed: %s", err)
|
||||
}
|
||||
|
||||
// Expect only the node and the root to be affected
|
||||
expectedModifiedNodes := []*reachabilityTreeNode{root, childNodes[i]}
|
||||
expectedModifiedNodes := newModifiedTreeNodes(root, childNodes[i])
|
||||
if !reflect.DeepEqual(modifiedNodes, expectedModifiedNodes) {
|
||||
t.Fatalf("TestAddChild: unexpected modifiedNodes. "+
|
||||
"want: %s, got: %s", expectedModifiedNodes, modifiedNodes)
|
||||
@ -89,7 +95,7 @@ func TestAddChild(t *testing.T) {
|
||||
|
||||
// Add another node to the root to trigger a reindex (100 < 2^7=128)
|
||||
lastChild = newReachabilityTreeNode(&blockNode{})
|
||||
modifiedNodes, err = root.addChild(lastChild)
|
||||
modifiedNodes, err = root.addChild(lastChild, root)
|
||||
if err != nil {
|
||||
t.Fatalf("TestAddChild: addChild failed: %s", err)
|
||||
}
|
||||
@ -100,14 +106,18 @@ func TestAddChild(t *testing.T) {
|
||||
t.Fatalf("TestAddChild: unexpected amount of modifiedNodes.")
|
||||
}
|
||||
|
||||
// Expect the last-added child to have an interval of 1 and remaining interval of 0
|
||||
// Expect the last-added child to have an interval of 1 and remaining interval of 0 both before and after
|
||||
lastChildInterval := lastChild.interval.size()
|
||||
if lastChildInterval != 1 {
|
||||
t.Fatalf("TestAddChild: unexpected lastChild interval size: want: 1, got: %d", lastChildInterval)
|
||||
}
|
||||
lastChildRemainingInterval := lastChild.remainingInterval.size()
|
||||
if lastChildRemainingInterval != 0 {
|
||||
t.Fatalf("TestAddChild: unexpected lastChild interval size: want: 0, got: %d", lastChildRemainingInterval)
|
||||
lastChildRemainingIntervalBefore := lastChild.remainingIntervalBefore().size()
|
||||
if lastChildRemainingIntervalBefore != 0 {
|
||||
t.Fatalf("TestAddChild: unexpected lastChild interval before size: want: 0, got: %d", lastChildRemainingIntervalBefore)
|
||||
}
|
||||
lastChildRemainingIntervalAfter := lastChild.remainingIntervalAfter().size()
|
||||
if lastChildRemainingIntervalAfter != 0 {
|
||||
t.Fatalf("TestAddChild: unexpected lastChild interval after size: want: 0, got: %d", lastChildRemainingIntervalAfter)
|
||||
}
|
||||
|
||||
// Expect all nodes to be descendant nodes of root
|
||||
@ -125,7 +135,7 @@ func TestReachabilityTreeNodeIsAncestorOf(t *testing.T) {
|
||||
descendants := make([]*reachabilityTreeNode, numberOfDescendants)
|
||||
for i := 0; i < numberOfDescendants; i++ {
|
||||
node := newReachabilityTreeNode(&blockNode{})
|
||||
_, err := currentTip.addChild(node)
|
||||
_, err := currentTip.addChild(node, root)
|
||||
if err != nil {
|
||||
t.Fatalf("TestReachabilityTreeNodeIsAncestorOf: addChild failed: %s", err)
|
||||
}
|
||||
@ -140,65 +150,65 @@ func TestReachabilityTreeNodeIsAncestorOf(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if root.isAncestorOf(root) {
|
||||
t.Fatalf("TestReachabilityTreeNodeIsAncestorOf: root is not expected to be a descendant of root")
|
||||
if !root.isAncestorOf(root) {
|
||||
t.Fatalf("TestReachabilityTreeNodeIsAncestorOf: root is expected to be an ancestor of root")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntervalIsAncestorOf(t *testing.T) {
|
||||
func TestIntervalContains(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
this, other *reachabilityInterval
|
||||
isThisAncestorOfOther bool
|
||||
name string
|
||||
this, other *reachabilityInterval
|
||||
thisContainsOther bool
|
||||
}{
|
||||
{
|
||||
name: "this == other",
|
||||
this: newReachabilityInterval(10, 100),
|
||||
other: newReachabilityInterval(10, 100),
|
||||
isThisAncestorOfOther: false,
|
||||
name: "this == other",
|
||||
this: newReachabilityInterval(10, 100),
|
||||
other: newReachabilityInterval(10, 100),
|
||||
thisContainsOther: true,
|
||||
},
|
||||
{
|
||||
name: "this.start == other.start && this.end < other.end",
|
||||
this: newReachabilityInterval(10, 90),
|
||||
other: newReachabilityInterval(10, 100),
|
||||
isThisAncestorOfOther: false,
|
||||
name: "this.start == other.start && this.end < other.end",
|
||||
this: newReachabilityInterval(10, 90),
|
||||
other: newReachabilityInterval(10, 100),
|
||||
thisContainsOther: false,
|
||||
},
|
||||
{
|
||||
name: "this.start == other.start && this.end > other.end",
|
||||
this: newReachabilityInterval(10, 100),
|
||||
other: newReachabilityInterval(10, 90),
|
||||
isThisAncestorOfOther: true,
|
||||
name: "this.start == other.start && this.end > other.end",
|
||||
this: newReachabilityInterval(10, 100),
|
||||
other: newReachabilityInterval(10, 90),
|
||||
thisContainsOther: true,
|
||||
},
|
||||
{
|
||||
name: "this.start > other.start && this.end == other.end",
|
||||
this: newReachabilityInterval(20, 100),
|
||||
other: newReachabilityInterval(10, 100),
|
||||
isThisAncestorOfOther: false,
|
||||
name: "this.start > other.start && this.end == other.end",
|
||||
this: newReachabilityInterval(20, 100),
|
||||
other: newReachabilityInterval(10, 100),
|
||||
thisContainsOther: false,
|
||||
},
|
||||
{
|
||||
name: "this.start < other.start && this.end == other.end",
|
||||
this: newReachabilityInterval(10, 100),
|
||||
other: newReachabilityInterval(20, 100),
|
||||
isThisAncestorOfOther: true,
|
||||
name: "this.start < other.start && this.end == other.end",
|
||||
this: newReachabilityInterval(10, 100),
|
||||
other: newReachabilityInterval(20, 100),
|
||||
thisContainsOther: true,
|
||||
},
|
||||
{
|
||||
name: "this.start > other.start && this.end < other.end",
|
||||
this: newReachabilityInterval(20, 90),
|
||||
other: newReachabilityInterval(10, 100),
|
||||
isThisAncestorOfOther: false,
|
||||
name: "this.start > other.start && this.end < other.end",
|
||||
this: newReachabilityInterval(20, 90),
|
||||
other: newReachabilityInterval(10, 100),
|
||||
thisContainsOther: false,
|
||||
},
|
||||
{
|
||||
name: "this.start < other.start && this.end > other.end",
|
||||
this: newReachabilityInterval(10, 100),
|
||||
other: newReachabilityInterval(20, 90),
|
||||
isThisAncestorOfOther: true,
|
||||
name: "this.start < other.start && this.end > other.end",
|
||||
this: newReachabilityInterval(10, 100),
|
||||
other: newReachabilityInterval(20, 90),
|
||||
thisContainsOther: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
if isAncestorOf := test.this.isAncestorOf(test.other); isAncestorOf != test.isThisAncestorOfOther {
|
||||
t.Errorf("test.this.isAncestorOf(test.other) is expected to be %t but got %t",
|
||||
test.isThisAncestorOfOther, isAncestorOf)
|
||||
if thisContainsOther := test.this.contains(test.other); thisContainsOther != test.thisContainsOther {
|
||||
t.Errorf("test.this.contains(test.other) is expected to be %t but got %t",
|
||||
test.thisContainsOther, thisContainsOther)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -431,140 +441,140 @@ func TestSplitWithExponentialBias(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsInFuture(t *testing.T) {
|
||||
blocks := futureCoveringBlockSet{
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(2, 3)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(4, 67)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(67, 77)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(657, 789)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1000, 1000)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1920, 1921)}},
|
||||
func TestHasAncestorOf(t *testing.T) {
|
||||
treeNodes := futureCoveringTreeNodeSet{
|
||||
&reachabilityTreeNode{interval: newReachabilityInterval(2, 3)},
|
||||
&reachabilityTreeNode{interval: newReachabilityInterval(4, 67)},
|
||||
&reachabilityTreeNode{interval: newReachabilityInterval(67, 77)},
|
||||
&reachabilityTreeNode{interval: newReachabilityInterval(657, 789)},
|
||||
&reachabilityTreeNode{interval: newReachabilityInterval(1000, 1000)},
|
||||
&reachabilityTreeNode{interval: newReachabilityInterval(1920, 1921)},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
block *futureCoveringBlock
|
||||
treeNode *reachabilityTreeNode
|
||||
expectedResult bool
|
||||
}{
|
||||
{
|
||||
block: &futureCoveringBlock{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1, 1)}},
|
||||
treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1, 1)},
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
block: &futureCoveringBlock{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(5, 7)}},
|
||||
treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(5, 7)},
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
block: &futureCoveringBlock{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(67, 76)}},
|
||||
treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(67, 76)},
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
block: &futureCoveringBlock{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(78, 100)}},
|
||||
treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(78, 100)},
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
block: &futureCoveringBlock{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1980, 2000)}},
|
||||
treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1980, 2000)},
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
block: &futureCoveringBlock{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1920, 1920)}},
|
||||
treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1920, 1920)},
|
||||
expectedResult: true,
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
result := blocks.isInFuture(test.block)
|
||||
result := treeNodes.hasAncestorOf(test.treeNode)
|
||||
if result != test.expectedResult {
|
||||
t.Errorf("TestIsInFuture: unexpected result in test #%d. Want: %t, got: %t",
|
||||
t.Errorf("TestHasAncestorOf: unexpected result in test #%d. Want: %t, got: %t",
|
||||
i, test.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInsertBlock(t *testing.T) {
|
||||
blocks := futureCoveringBlockSet{
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1, 3)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(4, 67)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(67, 77)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(657, 789)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1000, 1000)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1920, 1921)}},
|
||||
func TestInsertNode(t *testing.T) {
|
||||
treeNodes := futureCoveringTreeNodeSet{
|
||||
&reachabilityTreeNode{interval: newReachabilityInterval(1, 3)},
|
||||
&reachabilityTreeNode{interval: newReachabilityInterval(4, 67)},
|
||||
&reachabilityTreeNode{interval: newReachabilityInterval(67, 77)},
|
||||
&reachabilityTreeNode{interval: newReachabilityInterval(657, 789)},
|
||||
&reachabilityTreeNode{interval: newReachabilityInterval(1000, 1000)},
|
||||
&reachabilityTreeNode{interval: newReachabilityInterval(1920, 1921)},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
toInsert []*futureCoveringBlock
|
||||
expectedResult futureCoveringBlockSet
|
||||
toInsert []*reachabilityTreeNode
|
||||
expectedResult futureCoveringTreeNodeSet
|
||||
}{
|
||||
{
|
||||
toInsert: []*futureCoveringBlock{
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(5, 7)}},
|
||||
toInsert: []*reachabilityTreeNode{
|
||||
{interval: newReachabilityInterval(5, 7)},
|
||||
},
|
||||
expectedResult: futureCoveringBlockSet{
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1, 3)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(4, 67)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(67, 77)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(657, 789)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1000, 1000)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1920, 1921)}},
|
||||
expectedResult: futureCoveringTreeNodeSet{
|
||||
&reachabilityTreeNode{interval: newReachabilityInterval(1, 3)},
|
||||
&reachabilityTreeNode{interval: newReachabilityInterval(4, 67)},
|
||||
&reachabilityTreeNode{interval: newReachabilityInterval(67, 77)},
|
||||
&reachabilityTreeNode{interval: newReachabilityInterval(657, 789)},
|
||||
&reachabilityTreeNode{interval: newReachabilityInterval(1000, 1000)},
|
||||
&reachabilityTreeNode{interval: newReachabilityInterval(1920, 1921)},
|
||||
},
|
||||
},
|
||||
{
|
||||
toInsert: []*futureCoveringBlock{
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(65, 78)}},
|
||||
toInsert: []*reachabilityTreeNode{
|
||||
{interval: newReachabilityInterval(65, 78)},
|
||||
},
|
||||
expectedResult: futureCoveringBlockSet{
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1, 3)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(4, 67)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(65, 78)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(657, 789)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1000, 1000)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1920, 1921)}},
|
||||
expectedResult: futureCoveringTreeNodeSet{
|
||||
&reachabilityTreeNode{interval: newReachabilityInterval(1, 3)},
|
||||
&reachabilityTreeNode{interval: newReachabilityInterval(4, 67)},
|
||||
&reachabilityTreeNode{interval: newReachabilityInterval(65, 78)},
|
||||
&reachabilityTreeNode{interval: newReachabilityInterval(657, 789)},
|
||||
&reachabilityTreeNode{interval: newReachabilityInterval(1000, 1000)},
|
||||
&reachabilityTreeNode{interval: newReachabilityInterval(1920, 1921)},
|
||||
},
|
||||
},
|
||||
{
|
||||
toInsert: []*futureCoveringBlock{
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(88, 97)}},
|
||||
toInsert: []*reachabilityTreeNode{
|
||||
{interval: newReachabilityInterval(88, 97)},
|
||||
},
|
||||
expectedResult: futureCoveringBlockSet{
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1, 3)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(4, 67)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(67, 77)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(88, 97)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(657, 789)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1000, 1000)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1920, 1921)}},
|
||||
expectedResult: futureCoveringTreeNodeSet{
|
||||
&reachabilityTreeNode{interval: newReachabilityInterval(1, 3)},
|
||||
&reachabilityTreeNode{interval: newReachabilityInterval(4, 67)},
|
||||
&reachabilityTreeNode{interval: newReachabilityInterval(67, 77)},
|
||||
&reachabilityTreeNode{interval: newReachabilityInterval(88, 97)},
|
||||
&reachabilityTreeNode{interval: newReachabilityInterval(657, 789)},
|
||||
&reachabilityTreeNode{interval: newReachabilityInterval(1000, 1000)},
|
||||
&reachabilityTreeNode{interval: newReachabilityInterval(1920, 1921)},
|
||||
},
|
||||
},
|
||||
{
|
||||
toInsert: []*futureCoveringBlock{
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(88, 97)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(3000, 3010)}},
|
||||
toInsert: []*reachabilityTreeNode{
|
||||
{interval: newReachabilityInterval(88, 97)},
|
||||
{interval: newReachabilityInterval(3000, 3010)},
|
||||
},
|
||||
expectedResult: futureCoveringBlockSet{
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1, 3)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(4, 67)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(67, 77)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(88, 97)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(657, 789)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1000, 1000)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1920, 1921)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(3000, 3010)}},
|
||||
expectedResult: futureCoveringTreeNodeSet{
|
||||
&reachabilityTreeNode{interval: newReachabilityInterval(1, 3)},
|
||||
&reachabilityTreeNode{interval: newReachabilityInterval(4, 67)},
|
||||
&reachabilityTreeNode{interval: newReachabilityInterval(67, 77)},
|
||||
&reachabilityTreeNode{interval: newReachabilityInterval(88, 97)},
|
||||
&reachabilityTreeNode{interval: newReachabilityInterval(657, 789)},
|
||||
&reachabilityTreeNode{interval: newReachabilityInterval(1000, 1000)},
|
||||
&reachabilityTreeNode{interval: newReachabilityInterval(1920, 1921)},
|
||||
&reachabilityTreeNode{interval: newReachabilityInterval(3000, 3010)},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
// Create a clone of blocks so that we have a clean start for every test
|
||||
blocksClone := make(futureCoveringBlockSet, len(blocks))
|
||||
for i, block := range blocks {
|
||||
blocksClone[i] = block
|
||||
// Create a clone of treeNodes so that we have a clean start for every test
|
||||
treeNodesClone := make(futureCoveringTreeNodeSet, len(treeNodes))
|
||||
for i, treeNode := range treeNodes {
|
||||
treeNodesClone[i] = treeNode
|
||||
}
|
||||
|
||||
for _, block := range test.toInsert {
|
||||
blocksClone.insertBlock(block)
|
||||
for _, treeNode := range test.toInsert {
|
||||
treeNodesClone.insertNode(treeNode)
|
||||
}
|
||||
if !reflect.DeepEqual(blocksClone, test.expectedResult) {
|
||||
t.Errorf("TestInsertBlock: unexpected result in test #%d. Want: %s, got: %s",
|
||||
i, test.expectedResult, blocksClone)
|
||||
if !reflect.DeepEqual(treeNodesClone, test.expectedResult) {
|
||||
t.Errorf("TestInsertNode: unexpected result in test #%d. Want: %s, got: %s",
|
||||
i, test.expectedResult, treeNodesClone)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -665,14 +675,14 @@ func TestSplitWithExponentialBiasErrors(t *testing.T) {
|
||||
func TestReindexIntervalErrors(t *testing.T) {
|
||||
// Create a treeNode and give it size = 100
|
||||
treeNode := newReachabilityTreeNode(&blockNode{})
|
||||
treeNode.setInterval(newReachabilityInterval(0, 99))
|
||||
treeNode.interval = newReachabilityInterval(0, 99)
|
||||
|
||||
// Add a chain of 100 child treeNodes to treeNode
|
||||
var err error
|
||||
currentTreeNode := treeNode
|
||||
for i := 0; i < 100; i++ {
|
||||
childTreeNode := newReachabilityTreeNode(&blockNode{})
|
||||
_, err = currentTreeNode.addChild(childTreeNode)
|
||||
_, err = currentTreeNode.addChild(childTreeNode, treeNode)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
@ -704,12 +714,12 @@ func BenchmarkReindexInterval(b *testing.B) {
|
||||
// its first child gets half of the interval, so a reindex
|
||||
// from the root should happen after adding subTreeSize
|
||||
// nodes.
|
||||
root.setInterval(newReachabilityInterval(0, subTreeSize*2))
|
||||
root.interval = newReachabilityInterval(0, subTreeSize*2)
|
||||
|
||||
currentTreeNode := root
|
||||
for i := 0; i < subTreeSize; i++ {
|
||||
childTreeNode := newReachabilityTreeNode(&blockNode{})
|
||||
_, err := currentTreeNode.addChild(childTreeNode)
|
||||
_, err := currentTreeNode.addChild(childTreeNode, root)
|
||||
if err != nil {
|
||||
b.Fatalf("addChild: %s", err)
|
||||
}
|
||||
@ -717,50 +727,47 @@ func BenchmarkReindexInterval(b *testing.B) {
|
||||
currentTreeNode = childTreeNode
|
||||
}
|
||||
|
||||
remainingIntervalBefore := *root.remainingInterval
|
||||
originalRemainingInterval := *root.remainingIntervalAfter()
|
||||
// After we added subTreeSize nodes, adding the next
|
||||
// node should lead to a reindex from root.
|
||||
fullReindexTriggeringNode := newReachabilityTreeNode(&blockNode{})
|
||||
b.StartTimer()
|
||||
_, err := currentTreeNode.addChild(fullReindexTriggeringNode)
|
||||
_, err := currentTreeNode.addChild(fullReindexTriggeringNode, root)
|
||||
b.StopTimer()
|
||||
if err != nil {
|
||||
b.Fatalf("addChild: %s", err)
|
||||
}
|
||||
|
||||
if *root.remainingInterval == remainingIntervalBefore {
|
||||
if *root.remainingIntervalAfter() == originalRemainingInterval {
|
||||
b.Fatal("Expected a reindex from root, but it didn't happen")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFutureCoveringBlockSetString(t *testing.T) {
|
||||
func TestFutureCoveringTreeNodeSetString(t *testing.T) {
|
||||
treeNodeA := newReachabilityTreeNode(&blockNode{})
|
||||
treeNodeA.setInterval(newReachabilityInterval(123, 456))
|
||||
treeNodeA.interval = newReachabilityInterval(123, 456)
|
||||
treeNodeB := newReachabilityTreeNode(&blockNode{})
|
||||
treeNodeB.setInterval(newReachabilityInterval(457, 789))
|
||||
futureCoveringSet := futureCoveringBlockSet{
|
||||
&futureCoveringBlock{treeNode: treeNodeA},
|
||||
&futureCoveringBlock{treeNode: treeNodeB},
|
||||
}
|
||||
treeNodeB.interval = newReachabilityInterval(457, 789)
|
||||
futureCoveringSet := futureCoveringTreeNodeSet{treeNodeA, treeNodeB}
|
||||
|
||||
str := futureCoveringSet.String()
|
||||
expectedStr := "[123,456][457,789]"
|
||||
if str != expectedStr {
|
||||
t.Fatalf("TestFutureCoveringBlockSetString: unexpected "+
|
||||
t.Fatalf("TestFutureCoveringTreeNodeSetString: unexpected "+
|
||||
"string. Want: %s, got: %s", expectedStr, str)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReachabilityTreeNodeString(t *testing.T) {
|
||||
treeNodeA := newReachabilityTreeNode(&blockNode{})
|
||||
treeNodeA.setInterval(newReachabilityInterval(100, 199))
|
||||
treeNodeA.interval = newReachabilityInterval(100, 199)
|
||||
treeNodeB1 := newReachabilityTreeNode(&blockNode{})
|
||||
treeNodeB1.setInterval(newReachabilityInterval(100, 150))
|
||||
treeNodeB1.interval = newReachabilityInterval(100, 150)
|
||||
treeNodeB2 := newReachabilityTreeNode(&blockNode{})
|
||||
treeNodeB2.setInterval(newReachabilityInterval(150, 199))
|
||||
treeNodeB2.interval = newReachabilityInterval(150, 199)
|
||||
treeNodeC := newReachabilityTreeNode(&blockNode{})
|
||||
treeNodeC.setInterval(newReachabilityInterval(100, 149))
|
||||
treeNodeC.interval = newReachabilityInterval(100, 149)
|
||||
treeNodeA.children = []*reachabilityTreeNode{treeNodeB1, treeNodeB2}
|
||||
treeNodeB2.children = []*reachabilityTreeNode{treeNodeC}
|
||||
|
||||
@ -771,3 +778,237 @@ func TestReachabilityTreeNodeString(t *testing.T) {
|
||||
"string. Want: %s, got: %s", expectedStr, str)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsInPast(t *testing.T) {
|
||||
// Create a new database and DAG instance to run tests against.
|
||||
dag, teardownFunc, err := DAGSetup("TestIsInPast", true, Config{
|
||||
DAGParams: &dagconfig.SimnetParams,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("TestIsInPast: Failed to setup DAG instance: %v", err)
|
||||
}
|
||||
defer teardownFunc()
|
||||
|
||||
// Add a chain of two blocks above the genesis. This will be the
|
||||
// selected parent chain.
|
||||
blockA := PrepareAndProcessBlockForTest(t, dag, []*daghash.Hash{dag.genesis.hash}, nil)
|
||||
blockB := PrepareAndProcessBlockForTest(t, dag, []*daghash.Hash{blockA.BlockHash()}, nil)
|
||||
|
||||
// Add another block above the genesis
|
||||
blockC := PrepareAndProcessBlockForTest(t, dag, []*daghash.Hash{dag.genesis.hash}, nil)
|
||||
nodeC, ok := dag.index.LookupNode(blockC.BlockHash())
|
||||
if !ok {
|
||||
t.Fatalf("TestIsInPast: block C is not in the block index")
|
||||
}
|
||||
|
||||
// Add a block whose parents are the two tips
|
||||
blockD := PrepareAndProcessBlockForTest(t, dag, []*daghash.Hash{blockB.BlockHash(), blockC.BlockHash()}, nil)
|
||||
nodeD, ok := dag.index.LookupNode(blockD.BlockHash())
|
||||
if !ok {
|
||||
t.Fatalf("TestIsInPast: block C is not in the block index")
|
||||
}
|
||||
|
||||
// Make sure that node C is in the past of node D
|
||||
isInFuture, err := dag.reachabilityTree.isInPast(nodeC, nodeD)
|
||||
if err != nil {
|
||||
t.Fatalf("TestIsInPast: isInPast unexpectedly failed: %s", err)
|
||||
}
|
||||
if !isInFuture {
|
||||
t.Fatalf("TestIsInPast: node C is unexpectedly not the past of node D")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateReindexRoot(t *testing.T) {
|
||||
// Create a new database and DAG instance to run tests against.
|
||||
dag, teardownFunc, err := DAGSetup("TestUpdateReindexRoot", true, Config{
|
||||
DAGParams: &dagconfig.SimnetParams,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to setup DAG instance: %v", err)
|
||||
}
|
||||
defer teardownFunc()
|
||||
|
||||
// Set the reindex window to a low number to make this test run fast
|
||||
originalReachabilityReindexWindow := reachabilityReindexWindow
|
||||
reachabilityReindexWindow = 10
|
||||
defer func() {
|
||||
reachabilityReindexWindow = originalReachabilityReindexWindow
|
||||
}()
|
||||
|
||||
// Add two blocks on top of the genesis block
|
||||
chain1RootBlock := PrepareAndProcessBlockForTest(t, dag, []*daghash.Hash{dag.genesis.hash}, nil)
|
||||
chain2RootBlock := PrepareAndProcessBlockForTest(t, dag, []*daghash.Hash{dag.genesis.hash}, nil)
|
||||
|
||||
// Add chain of reachabilityReindexWindow - 1 blocks above chain1RootBlock and
|
||||
// chain2RootBlock, respectively. This should not move the reindex root
|
||||
chain1RootBlockTipHash := chain1RootBlock.BlockHash()
|
||||
chain2RootBlockTipHash := chain2RootBlock.BlockHash()
|
||||
genesisTreeNode, err := dag.reachabilityTree.store.treeNodeByBlockHash(dag.genesis.hash)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get tree node: %s", err)
|
||||
}
|
||||
for i := uint64(0); i < reachabilityReindexWindow-1; i++ {
|
||||
chain1Block := PrepareAndProcessBlockForTest(t, dag, []*daghash.Hash{chain1RootBlockTipHash}, nil)
|
||||
chain1RootBlockTipHash = chain1Block.BlockHash()
|
||||
|
||||
chain2Block := PrepareAndProcessBlockForTest(t, dag, []*daghash.Hash{chain2RootBlockTipHash}, nil)
|
||||
chain2RootBlockTipHash = chain2Block.BlockHash()
|
||||
|
||||
if dag.reachabilityTree.reindexRoot != genesisTreeNode {
|
||||
t.Fatalf("reindex root unexpectedly moved")
|
||||
}
|
||||
}
|
||||
|
||||
// Add another block over chain1. This will move the reindex root to chain1RootBlock
|
||||
PrepareAndProcessBlockForTest(t, dag, []*daghash.Hash{chain1RootBlockTipHash}, nil)
|
||||
|
||||
// Make sure that chain1RootBlock is now the reindex root
|
||||
chain1RootTreeNode, err := dag.reachabilityTree.store.treeNodeByBlockHash(chain1RootBlock.BlockHash())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get tree node: %s", err)
|
||||
}
|
||||
if dag.reachabilityTree.reindexRoot != chain1RootTreeNode {
|
||||
t.Fatalf("chain1RootBlock is not the reindex root after reindex")
|
||||
}
|
||||
|
||||
// Make sure that tight intervals have been applied to chain2. Since
|
||||
// we added reachabilityReindexWindow-1 blocks to chain2, the size
|
||||
// of the interval at its root should be equal to reachabilityReindexWindow
|
||||
chain2RootTreeNode, err := dag.reachabilityTree.store.treeNodeByBlockHash(chain2RootBlock.BlockHash())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get tree node: %s", err)
|
||||
}
|
||||
if chain2RootTreeNode.interval.size() != reachabilityReindexWindow {
|
||||
t.Fatalf("got unexpected chain2RootNode interval. Want: %d, got: %d",
|
||||
chain2RootTreeNode.interval.size(), reachabilityReindexWindow)
|
||||
}
|
||||
|
||||
// Make sure that the rest of the interval has been allocated to
|
||||
// chain1RootNode, minus slack from both sides
|
||||
expectedChain1RootIntervalSize := genesisTreeNode.interval.size() - 1 -
|
||||
chain2RootTreeNode.interval.size() - 2*reachabilityReindexSlack
|
||||
if chain1RootTreeNode.interval.size() != expectedChain1RootIntervalSize {
|
||||
t.Fatalf("got unexpected chain1RootNode interval. Want: %d, got: %d",
|
||||
chain1RootTreeNode.interval.size(), expectedChain1RootIntervalSize)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReindexIntervalsEarlierThanReindexRoot(t *testing.T) {
|
||||
// Create a new database and DAG instance to run tests against.
|
||||
dag, teardownFunc, err := DAGSetup("TestReindexIntervalsEarlierThanReindexRoot", true, Config{
|
||||
DAGParams: &dagconfig.SimnetParams,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to setup DAG instance: %v", err)
|
||||
}
|
||||
defer teardownFunc()
|
||||
|
||||
// Set the reindex window and slack to low numbers to make this test
|
||||
// run fast
|
||||
originalReachabilityReindexWindow := reachabilityReindexWindow
|
||||
originalReachabilityReindexSlack := reachabilityReindexSlack
|
||||
reachabilityReindexWindow = 10
|
||||
reachabilityReindexSlack = 5
|
||||
defer func() {
|
||||
reachabilityReindexWindow = originalReachabilityReindexWindow
|
||||
reachabilityReindexSlack = originalReachabilityReindexSlack
|
||||
}()
|
||||
|
||||
// Add three children to the genesis: leftBlock, centerBlock, rightBlock
|
||||
leftBlock := PrepareAndProcessBlockForTest(t, dag, []*daghash.Hash{dag.genesis.hash}, nil)
|
||||
centerBlock := PrepareAndProcessBlockForTest(t, dag, []*daghash.Hash{dag.genesis.hash}, nil)
|
||||
rightBlock := PrepareAndProcessBlockForTest(t, dag, []*daghash.Hash{dag.genesis.hash}, nil)
|
||||
|
||||
// Add a chain of reachabilityReindexWindow blocks above centerBlock.
|
||||
// This will move the reindex root to centerBlock
|
||||
centerTipHash := centerBlock.BlockHash()
|
||||
for i := uint64(0); i < reachabilityReindexWindow; i++ {
|
||||
block := PrepareAndProcessBlockForTest(t, dag, []*daghash.Hash{centerTipHash}, nil)
|
||||
centerTipHash = block.BlockHash()
|
||||
}
|
||||
|
||||
// Make sure that centerBlock is now the reindex root
|
||||
centerTreeNode, err := dag.reachabilityTree.store.treeNodeByBlockHash(centerBlock.BlockHash())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get tree node: %s", err)
|
||||
}
|
||||
if dag.reachabilityTree.reindexRoot != centerTreeNode {
|
||||
t.Fatalf("centerBlock is not the reindex root after reindex")
|
||||
}
|
||||
|
||||
// Get the current interval for leftBlock. The reindex should have
|
||||
// resulted in a tight interval there
|
||||
leftTreeNode, err := dag.reachabilityTree.store.treeNodeByBlockHash(leftBlock.BlockHash())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get tree node: %s", err)
|
||||
}
|
||||
if leftTreeNode.interval.size() != 1 {
|
||||
t.Fatalf("leftBlock interval not tight after reindex")
|
||||
}
|
||||
|
||||
// Get the current interval for rightBlock. The reindex should have
|
||||
// resulted in a tight interval there
|
||||
rightTreeNode, err := dag.reachabilityTree.store.treeNodeByBlockHash(rightBlock.BlockHash())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get tree node: %s", err)
|
||||
}
|
||||
if rightTreeNode.interval.size() != 1 {
|
||||
t.Fatalf("rightBlock interval not tight after reindex")
|
||||
}
|
||||
|
||||
// Get the current interval for centerBlock. Its interval should be:
|
||||
// genesisInterval - 1 - leftInterval - leftSlack - rightInterval - rightSlack
|
||||
genesisTreeNode, err := dag.reachabilityTree.store.treeNodeByBlockHash(dag.genesis.hash)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get tree node: %s", err)
|
||||
}
|
||||
expectedCenterInterval := genesisTreeNode.interval.size() - 1 -
|
||||
leftTreeNode.interval.size() - reachabilityReindexSlack -
|
||||
rightTreeNode.interval.size() - reachabilityReindexSlack
|
||||
if centerTreeNode.interval.size() != expectedCenterInterval {
|
||||
t.Fatalf("unexpected centerBlock interval. Want: %d, got: %d",
|
||||
expectedCenterInterval, centerTreeNode.interval.size())
|
||||
}
|
||||
|
||||
// Add a chain of reachabilityReindexWindow - 1 blocks above leftBlock.
|
||||
// Each addition will trigger a low-than-reindex-root reindex. We
|
||||
// expect the centerInterval to shrink by 1 each time, but its child
|
||||
// to remain unaffected
|
||||
treeChildOfCenterBlock := centerTreeNode.children[0]
|
||||
treeChildOfCenterBlockOriginalIntervalSize := treeChildOfCenterBlock.interval.size()
|
||||
leftTipHash := leftBlock.BlockHash()
|
||||
for i := uint64(0); i < reachabilityReindexWindow-1; i++ {
|
||||
block := PrepareAndProcessBlockForTest(t, dag, []*daghash.Hash{leftTipHash}, nil)
|
||||
leftTipHash = block.BlockHash()
|
||||
|
||||
expectedCenterInterval--
|
||||
if centerTreeNode.interval.size() != expectedCenterInterval {
|
||||
t.Fatalf("unexpected centerBlock interval. Want: %d, got: %d",
|
||||
expectedCenterInterval, centerTreeNode.interval.size())
|
||||
}
|
||||
|
||||
if treeChildOfCenterBlock.interval.size() != treeChildOfCenterBlockOriginalIntervalSize {
|
||||
t.Fatalf("the interval of centerBlock's child unexpectedly changed")
|
||||
}
|
||||
}
|
||||
|
||||
// Add a chain of reachabilityReindexWindow - 1 blocks above rightBlock.
|
||||
// Each addition will trigger a low-than-reindex-root reindex. We
|
||||
// expect the centerInterval to shrink by 1 each time, but its child
|
||||
// to remain unaffected
|
||||
rightTipHash := rightBlock.BlockHash()
|
||||
for i := uint64(0); i < reachabilityReindexWindow-1; i++ {
|
||||
block := PrepareAndProcessBlockForTest(t, dag, []*daghash.Hash{rightTipHash}, nil)
|
||||
rightTipHash = block.BlockHash()
|
||||
|
||||
expectedCenterInterval--
|
||||
if centerTreeNode.interval.size() != expectedCenterInterval {
|
||||
t.Fatalf("unexpected centerBlock interval. Want: %d, got: %d",
|
||||
expectedCenterInterval, centerTreeNode.interval.size())
|
||||
}
|
||||
|
||||
if treeChildOfCenterBlock.interval.size() != treeChildOfCenterBlockOriginalIntervalSize {
|
||||
t.Fatalf("the interval of centerBlock's child unexpectedly changed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import (
|
||||
|
||||
type reachabilityData struct {
|
||||
treeNode *reachabilityTreeNode
|
||||
futureCoveringSet futureCoveringBlockSet
|
||||
futureCoveringSet futureCoveringTreeNodeSet
|
||||
}
|
||||
|
||||
type reachabilityStore struct {
|
||||
@ -41,11 +41,11 @@ func (store *reachabilityStore) setTreeNode(treeNode *reachabilityTreeNode) {
|
||||
store.setBlockAsDirty(node.hash)
|
||||
}
|
||||
|
||||
func (store *reachabilityStore) setFutureCoveringSet(node *blockNode, futureCoveringSet futureCoveringBlockSet) error {
|
||||
func (store *reachabilityStore) setFutureCoveringSet(node *blockNode, futureCoveringSet futureCoveringTreeNodeSet) error {
|
||||
// load the reachability data from DB to store.loaded
|
||||
_, exists := store.reachabilityDataByHash(node.hash)
|
||||
if !exists {
|
||||
return reachabilityNotFoundError(node)
|
||||
return reachabilityNotFoundError(node.hash)
|
||||
}
|
||||
|
||||
store.loaded[*node.hash].futureCoveringSet = futureCoveringSet
|
||||
@ -57,22 +57,26 @@ func (store *reachabilityStore) setBlockAsDirty(blockHash *daghash.Hash) {
|
||||
store.dirty[*blockHash] = struct{}{}
|
||||
}
|
||||
|
||||
func reachabilityNotFoundError(node *blockNode) error {
|
||||
return errors.Errorf("Couldn't find reachability data for block %s", node.hash)
|
||||
func reachabilityNotFoundError(hash *daghash.Hash) error {
|
||||
return errors.Errorf("couldn't find reachability data for block %s", hash)
|
||||
}
|
||||
|
||||
func (store *reachabilityStore) treeNodeByBlockNode(node *blockNode) (*reachabilityTreeNode, error) {
|
||||
reachabilityData, exists := store.reachabilityDataByHash(node.hash)
|
||||
func (store *reachabilityStore) treeNodeByBlockHash(hash *daghash.Hash) (*reachabilityTreeNode, error) {
|
||||
reachabilityData, exists := store.reachabilityDataByHash(hash)
|
||||
if !exists {
|
||||
return nil, reachabilityNotFoundError(node)
|
||||
return nil, reachabilityNotFoundError(hash)
|
||||
}
|
||||
return reachabilityData.treeNode, nil
|
||||
}
|
||||
|
||||
func (store *reachabilityStore) futureCoveringSetByBlockNode(node *blockNode) (futureCoveringBlockSet, error) {
|
||||
func (store *reachabilityStore) treeNodeByBlockNode(node *blockNode) (*reachabilityTreeNode, error) {
|
||||
return store.treeNodeByBlockHash(node.hash)
|
||||
}
|
||||
|
||||
func (store *reachabilityStore) futureCoveringSetByBlockNode(node *blockNode) (futureCoveringTreeNodeSet, error) {
|
||||
reachabilityData, exists := store.reachabilityDataByHash(node.hash)
|
||||
if !exists {
|
||||
return nil, reachabilityNotFoundError(node)
|
||||
return nil, reachabilityNotFoundError(node.hash)
|
||||
}
|
||||
return reachabilityData.futureCoveringSet, nil
|
||||
}
|
||||
@ -215,12 +219,6 @@ func (store *reachabilityStore) serializeTreeNode(w io.Writer, treeNode *reachab
|
||||
return err
|
||||
}
|
||||
|
||||
// Serialize the remaining interval
|
||||
err = store.serializeReachabilityInterval(w, treeNode.remainingInterval)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Serialize the parent
|
||||
// If this is the genesis block, write the zero hash instead
|
||||
parentHash := &daghash.ZeroHash
|
||||
@ -265,16 +263,16 @@ func (store *reachabilityStore) serializeReachabilityInterval(w io.Writer, inter
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *reachabilityStore) serializeFutureCoveringSet(w io.Writer, futureCoveringSet futureCoveringBlockSet) error {
|
||||
func (store *reachabilityStore) serializeFutureCoveringSet(w io.Writer, futureCoveringSet futureCoveringTreeNodeSet) error {
|
||||
// Serialize the set size
|
||||
err := wire.WriteVarInt(w, uint64(len(futureCoveringSet)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Serialize each block in the set
|
||||
for _, block := range futureCoveringSet {
|
||||
err = wire.WriteElement(w, block.blockNode.hash)
|
||||
// Serialize each node in the set
|
||||
for _, node := range futureCoveringSet {
|
||||
err = wire.WriteElement(w, node.blockNode.hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -311,13 +309,6 @@ func (store *reachabilityStore) deserializeTreeNode(r io.Reader, destination *re
|
||||
}
|
||||
destination.treeNode.interval = interval
|
||||
|
||||
// Deserialize the remaining interval
|
||||
remainingInterval, err := store.deserializeReachabilityInterval(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
destination.treeNode.remainingInterval = remainingInterval
|
||||
|
||||
// Deserialize the parent
|
||||
// If this is the zero hash, this node is the genesis and as such doesn't have a parent
|
||||
parentHash := &daghash.Hash{}
|
||||
@ -388,25 +379,18 @@ func (store *reachabilityStore) deserializeFutureCoveringSet(r io.Reader, destin
|
||||
}
|
||||
|
||||
// Deserialize each block in the set
|
||||
futureCoveringSet := make(futureCoveringBlockSet, setSize)
|
||||
futureCoveringSet := make(futureCoveringTreeNodeSet, setSize)
|
||||
for i := uint64(0); i < setSize; i++ {
|
||||
blockHash := &daghash.Hash{}
|
||||
err = wire.ReadElement(r, blockHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
blockNode, ok := store.dag.index.LookupNode(blockHash)
|
||||
if !ok {
|
||||
return errors.Errorf("blockNode not found for hash %s", blockHash)
|
||||
}
|
||||
blockReachabilityData, ok := store.reachabilityDataByHash(blockHash)
|
||||
if !ok {
|
||||
return errors.Errorf("block reachability data not found for hash: %s", blockHash)
|
||||
}
|
||||
futureCoveringSet[i] = &futureCoveringBlock{
|
||||
blockNode: blockNode,
|
||||
treeNode: blockReachabilityData.treeNode,
|
||||
}
|
||||
futureCoveringSet[i] = blockReachabilityData.treeNode
|
||||
}
|
||||
destination.futureCoveringSet = futureCoveringSet
|
||||
|
||||
|
@ -709,7 +709,7 @@ func (dag *BlockDAG) validateParents(blockHeader *wire.BlockHeader, parents bloc
|
||||
continue
|
||||
}
|
||||
|
||||
isAncestorOf, err := dag.isAncestorOf(parentA, parentB)
|
||||
isAncestorOf, err := dag.isInPast(parentA, parentB)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
)
|
||||
|
||||
var reachabilityDataBucket = database.MakeBucket([]byte("reachability"))
|
||||
var reachabilityReindexKey = database.MakeBucket().Key([]byte("reachability-reindex-root"))
|
||||
|
||||
func reachabilityKey(hash *daghash.Hash) *database.Key {
|
||||
return reachabilityDataBucket.Key(hash[:])
|
||||
@ -38,3 +39,26 @@ func StoreReachabilityData(context Context, blockHash *daghash.Hash, reachabilit
|
||||
func ClearReachabilityData(dbTx *TxContext) error {
|
||||
return clearBucket(dbTx, reachabilityDataBucket)
|
||||
}
|
||||
|
||||
// StoreReachabilityReindexRoot stores the reachability reindex root in the database.
|
||||
func StoreReachabilityReindexRoot(context Context, reachabilityReindexRoot *daghash.Hash) error {
|
||||
accessor, err := context.accessor()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return accessor.Put(reachabilityReindexKey, reachabilityReindexRoot[:])
|
||||
}
|
||||
|
||||
// FetchReachabilityReindexRoot retrieves the reachability reindex root from the database.
|
||||
// Returns ErrNotFound if the state is missing from the database.
|
||||
func FetchReachabilityReindexRoot(context Context) (*daghash.Hash, error) {
|
||||
accessor, err := context.accessor()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bytes, err := accessor.Get(reachabilityReindexKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return daghash.NewHash(bytes)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user