[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:
stasatdaglabs 2020-06-28 14:27:01 +03:00 committed by GitHub
parent a86255ba51
commit 57b1653383
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 1226 additions and 340 deletions

View File

@ -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

View File

@ -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)

View File

@ -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
}

View File

@ -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

View File

@ -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")
}
}
}

View File

@ -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

View File

@ -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
}

View File

@ -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)
}