diff --git a/blockdag/reachability.go b/blockdag/reachability.go index fa5a1ec2b..1b69cd4ae 100644 --- a/blockdag/reachability.go +++ b/blockdag/reachability.go @@ -40,14 +40,6 @@ func newModifiedTreeNodes(nodes ...*reachabilityTreeNode) modifiedTreeNodes { return modifiedNodes } -// addAll adds all the reachabilityTreeNodes in `other` -// into `mtn`. Note that `other` is not affected. -func (mtn modifiedTreeNodes) addAll(other modifiedTreeNodes) { - for node := range other { - mtn[node] = struct{}{} - } -} - // reachabilityInterval represents an interval to be used within the // tree reachability algorithm. See reachabilityTreeNode for further // details. @@ -267,8 +259,8 @@ func (rtn *reachabilityTreeNode) hasSlackIntervalAfter() bool { // remaining interval to allocate, a reindexing is triggered. // This method returns a list of reachabilityTreeNodes modified // by it. -func (rtn *reachabilityTreeNode) addChild(child *reachabilityTreeNode, reindexRoot *reachabilityTreeNode) ( - modifiedTreeNodes, error) { +func (rtn *reachabilityTreeNode) addChild(child *reachabilityTreeNode, reindexRoot *reachabilityTreeNode, + modifiedNodes modifiedTreeNodes) error { remaining := rtn.remainingIntervalAfter() @@ -281,9 +273,9 @@ func (rtn *reachabilityTreeNode) addChild(child *reachabilityTreeNode, reindexRo // at this point we don't yet know child's interval. if !reindexRoot.isAncestorOf(rtn) { reindexStartTime := time.Now() - modifiedNodes, err := rtn.reindexIntervalsEarlierThanReindexRoot(reindexRoot) + err := rtn.reindexIntervalsEarlierThanReindexRoot(reindexRoot, modifiedNodes) if err != nil { - return nil, err + return err } reindexTimeElapsed := time.Since(reindexStartTime) log.Debugf("Reachability reindex triggered for "+ @@ -291,30 +283,32 @@ func (rtn *reachabilityTreeNode) addChild(child *reachabilityTreeNode, reindexRo "reindex root %s. Modified %d tree nodes and took %dms.", rtn.blockNode.hash, reindexRoot.blockNode.hash, len(modifiedNodes), reindexTimeElapsed.Milliseconds()) - return modifiedNodes, nil + return nil } // No allocation space left -- reindex if remaining.size() == 0 { reindexStartTime := time.Now() - modifiedNodes, err := rtn.reindexIntervals() + err := rtn.reindexIntervals(modifiedNodes) if err != nil { - return nil, err + return err } reindexTimeElapsed := time.Since(reindexStartTime) log.Debugf("Reachability reindex triggered for "+ "block %s. Modified %d tree nodes and took %dms.", rtn.blockNode.hash, len(modifiedNodes), reindexTimeElapsed.Milliseconds()) - return modifiedNodes, nil + return nil } // Allocate from the remaining space allocated, _, err := remaining.splitInHalf() if err != nil { - return nil, err + return err } child.interval = allocated - return newModifiedTreeNodes(rtn, child), nil + modifiedNodes[rtn] = struct{}{} + modifiedNodes[child] = struct{}{} + return nil } // reindexIntervals traverses the reachability subtree that's @@ -324,7 +318,7 @@ func (rtn *reachabilityTreeNode) addChild(child *reachabilityTreeNode, reindexRo // tree until it finds a node with a subreeSize that's greater than // its interval size. See propagateInterval for further details. // This method returns a list of reachabilityTreeNodes modified by it. -func (rtn *reachabilityTreeNode) reindexIntervals() (modifiedTreeNodes, error) { +func (rtn *reachabilityTreeNode) reindexIntervals(modifiedNodes modifiedTreeNodes) error { current := rtn // Initial interval and subtree sizes @@ -338,7 +332,7 @@ func (rtn *reachabilityTreeNode) reindexIntervals() (modifiedTreeNodes, error) { if current.parent == nil { // If we ended up here it means that there are more // than 2^64 blocks, which shouldn't ever happen. - return nil, errors.Errorf("missing tree " + + return errors.Errorf("missing tree " + "parent during reindexing. Theoretically, this " + "should only ever happen if there are more " + "than 2^64 blocks in the DAG.") @@ -350,7 +344,7 @@ func (rtn *reachabilityTreeNode) reindexIntervals() (modifiedTreeNodes, error) { } // Propagate the interval to the subtree - return current.propagateInterval(subTreeSizeMap) + return current.propagateInterval(subTreeSizeMap, modifiedNodes) } // countSubtrees counts the size of each subtree under this node, @@ -415,10 +409,9 @@ func (rtn *reachabilityTreeNode) countSubtrees(subTreeSizeMap map[*reachabilityT // Subtree intervals are recursively allocated according to subtree sizes and // the allocation rule in splitWithExponentialBias. This method returns // a list of reachabilityTreeNodes modified by it. -func (rtn *reachabilityTreeNode) propagateInterval(subTreeSizeMap map[*reachabilityTreeNode]uint64) ( - modifiedTreeNodes, error) { +func (rtn *reachabilityTreeNode) propagateInterval(subTreeSizeMap map[*reachabilityTreeNode]uint64, + modifiedNodes modifiedTreeNodes) error { - allModifiedTreeNodes := newModifiedTreeNodes() queue := []*reachabilityTreeNode{rtn} for len(queue) > 0 { var current *reachabilityTreeNode @@ -430,7 +423,7 @@ func (rtn *reachabilityTreeNode) propagateInterval(subTreeSizeMap map[*reachabil } intervals, err := current.intervalRangeForChildAllocation().splitWithExponentialBias(sizes) if err != nil { - return nil, err + return err } for i, child := range current.children { childInterval := intervals[i] @@ -439,13 +432,13 @@ func (rtn *reachabilityTreeNode) propagateInterval(subTreeSizeMap map[*reachabil } } - allModifiedTreeNodes[current] = struct{}{} + modifiedNodes[current] = struct{}{} } - return allModifiedTreeNodes, nil + return nil } func (rtn *reachabilityTreeNode) reindexIntervalsEarlierThanReindexRoot( - reindexRoot *reachabilityTreeNode) (modifiedTreeNodes, error) { + reindexRoot *reachabilityTreeNode, modifiedNodes modifiedTreeNodes) error { // Find the common ancestor for both rtn and the reindex root commonAncestor := rtn.findCommonAncestorWithReindexRoot(reindexRoot) @@ -455,25 +448,27 @@ func (rtn *reachabilityTreeNode) reindexIntervalsEarlierThanReindexRoot( // b. A reachability tree ancestor of `reindexRoot` commonAncestorChosenChild, err := commonAncestor.findAncestorAmongChildren(reindexRoot) if err != nil { - return nil, err + return err } if rtn.interval.end < commonAncestorChosenChild.interval.start { // rtn is in the subtree before the chosen child - return rtn.reclaimIntervalBeforeChosenChild(commonAncestor, commonAncestorChosenChild, reindexRoot) + return rtn.reclaimIntervalBeforeChosenChild(commonAncestor, + commonAncestorChosenChild, reindexRoot, modifiedNodes) } - if commonAncestorChosenChild.interval.end < rtn.interval.start { - // rtn is in the subtree after the chosen child - return rtn.reclaimIntervalAfterChosenChild(commonAncestor, commonAncestorChosenChild, reindexRoot) - } - return nil, errors.Errorf("rtn is in the chosen child's subtree") + + // rtn is either: + // * in the subtree after the chosen child + // * the common ancestor + // In both cases we reclaim from the "after" subtree. In the + // latter case this is arbitrary + return rtn.reclaimIntervalAfterChosenChild(commonAncestor, + commonAncestorChosenChild, reindexRoot, modifiedNodes) } func (rtn *reachabilityTreeNode) reclaimIntervalBeforeChosenChild( - commonAncestor *reachabilityTreeNode, commonAncestorChosenChild *reachabilityTreeNode, reindexRoot *reachabilityTreeNode) ( - modifiedTreeNodes, error) { - - allModifiedTreeNodes := newModifiedTreeNodes() + commonAncestor *reachabilityTreeNode, commonAncestorChosenChild *reachabilityTreeNode, + reindexRoot *reachabilityTreeNode, modifiedNodes modifiedTreeNodes) error { current := commonAncestorChosenChild if !commonAncestorChosenChild.hasSlackIntervalBefore() { @@ -484,7 +479,7 @@ func (rtn *reachabilityTreeNode) reclaimIntervalBeforeChosenChild( var err error current, err = current.findAncestorAmongChildren(reindexRoot) if err != nil { - return nil, err + return err } } @@ -497,11 +492,10 @@ func (rtn *reachabilityTreeNode) reclaimIntervalBeforeChosenChild( current.interval.start+slackReachabilityIntervalForReclaiming, current.interval.end, ) - modifiedNodes, err := current.countSubtreesAndPropagateInterval() + err := current.countSubtreesAndPropagateInterval(modifiedNodes) if err != nil { - return nil, err + return err } - allModifiedTreeNodes.addAll(modifiedNodes) current.interval = originalInterval } } @@ -516,25 +510,24 @@ func (rtn *reachabilityTreeNode) reclaimIntervalBeforeChosenChild( current.interval.start+slackReachabilityIntervalForReclaiming, current.interval.end, ) - modifiedNodes, err := current.parent.reindexIntervalsBeforeNode(current) + err := current.parent.reindexIntervalsBeforeNode(current, modifiedNodes) if err != nil { - return nil, err + return err } - allModifiedTreeNodes.addAll(modifiedNodes) current = current.parent } - return allModifiedTreeNodes, nil + return nil } // reindexIntervalsBeforeNode applies a tight interval to the reachability // subtree before `node`. Note that `node` itself is unaffected. -func (rtn *reachabilityTreeNode) reindexIntervalsBeforeNode(node *reachabilityTreeNode) ( - modifiedTreeNodes, error) { +func (rtn *reachabilityTreeNode) reindexIntervalsBeforeNode(node *reachabilityTreeNode, + modifiedNodes modifiedTreeNodes) error { childrenBeforeNode, _, err := rtn.splitChildrenAroundChild(node) if err != nil { - return nil, err + return err } childrenBeforeNodeSizes, childrenBeforeNodeSubtreeSizeMaps, childrenBeforeNodeSizesSum := @@ -545,16 +538,15 @@ func (rtn *reachabilityTreeNode) reindexIntervalsBeforeNode(node *reachabilityTr newInterval := newReachabilityInterval(newIntervalEnd-childrenBeforeNodeSizesSum+1, newIntervalEnd) intervals, err := newInterval.splitExact(childrenBeforeNodeSizes) if err != nil { - return nil, err + return err } - return orderedTreeNodeSet(childrenBeforeNode).propagateIntervals(intervals, childrenBeforeNodeSubtreeSizeMaps) + return orderedTreeNodeSet(childrenBeforeNode). + propagateIntervals(intervals, childrenBeforeNodeSubtreeSizeMaps, modifiedNodes) } func (rtn *reachabilityTreeNode) reclaimIntervalAfterChosenChild( - commonAncestor *reachabilityTreeNode, commonAncestorChosenChild *reachabilityTreeNode, reindexRoot *reachabilityTreeNode) ( - modifiedTreeNodes, error) { - - allModifiedTreeNodes := newModifiedTreeNodes() + commonAncestor *reachabilityTreeNode, commonAncestorChosenChild *reachabilityTreeNode, + reindexRoot *reachabilityTreeNode, modifiedNodes modifiedTreeNodes) error { current := commonAncestorChosenChild if !commonAncestorChosenChild.hasSlackIntervalAfter() { @@ -565,7 +557,7 @@ func (rtn *reachabilityTreeNode) reclaimIntervalAfterChosenChild( var err error current, err = current.findAncestorAmongChildren(reindexRoot) if err != nil { - return nil, err + return err } } @@ -578,11 +570,12 @@ func (rtn *reachabilityTreeNode) reclaimIntervalAfterChosenChild( current.interval.start, current.interval.end-slackReachabilityIntervalForReclaiming, ) - modifiedNodes, err := current.countSubtreesAndPropagateInterval() + modifiedNodes[current] = struct{}{} + + err := current.countSubtreesAndPropagateInterval(modifiedNodes) if err != nil { - return nil, err + return err } - allModifiedTreeNodes.addAll(modifiedNodes) current.interval = originalInterval } } @@ -597,25 +590,26 @@ func (rtn *reachabilityTreeNode) reclaimIntervalAfterChosenChild( current.interval.start, current.interval.end-slackReachabilityIntervalForReclaiming, ) - modifiedNodes, err := current.parent.reindexIntervalsAfterNode(current) + modifiedNodes[current] = struct{}{} + + err := current.parent.reindexIntervalsAfterNode(current, modifiedNodes) if err != nil { - return nil, err + return err } - allModifiedTreeNodes.addAll(modifiedNodes) current = current.parent } - return allModifiedTreeNodes, nil + return nil } // reindexIntervalsAfterNode applies a tight interval to the reachability // subtree after `node`. Note that `node` itself is unaffected. -func (rtn *reachabilityTreeNode) reindexIntervalsAfterNode(node *reachabilityTreeNode) ( - modifiedTreeNodes, error) { +func (rtn *reachabilityTreeNode) reindexIntervalsAfterNode(node *reachabilityTreeNode, + modifiedNodes modifiedTreeNodes) error { _, childrenAfterNode, err := rtn.splitChildrenAroundChild(node) if err != nil { - return nil, err + return err } childrenAfterNodeSizes, childrenAfterNodeSubtreeSizeMaps, childrenAfterNodeSizesSum := @@ -626,25 +620,24 @@ func (rtn *reachabilityTreeNode) reindexIntervalsAfterNode(node *reachabilityTre newInterval := newReachabilityInterval(newIntervalStart, newIntervalStart+childrenAfterNodeSizesSum-1) intervals, err := newInterval.splitExact(childrenAfterNodeSizes) if err != nil { - return nil, err + return err } - return orderedTreeNodeSet(childrenAfterNode).propagateIntervals(intervals, childrenAfterNodeSubtreeSizeMaps) + return orderedTreeNodeSet(childrenAfterNode). + propagateIntervals(intervals, childrenAfterNodeSubtreeSizeMaps, modifiedNodes) } func (tns orderedTreeNodeSet) propagateIntervals(intervals []*reachabilityInterval, - subtreeSizeMaps []map[*reachabilityTreeNode]uint64) (modifiedTreeNodes, error) { + subtreeSizeMaps []map[*reachabilityTreeNode]uint64, modifiedNodes modifiedTreeNodes) error { - allModifiedTreeNodes := newModifiedTreeNodes() for i, node := range tns { node.interval = intervals[i] subtreeSizeMap := subtreeSizeMaps[i] - modifiedNodes, err := node.propagateInterval(subtreeSizeMap) + err := node.propagateInterval(subtreeSizeMap, modifiedNodes) if err != nil { - return nil, err + return err } - allModifiedTreeNodes.addAll(modifiedNodes) } - return allModifiedTreeNodes, nil + return nil } // isAncestorOf checks if this node is a reachability tree ancestor @@ -832,7 +825,8 @@ func (rt *reachabilityTree) addBlock(node *blockNode, selectedParentAnticone []* if err != nil { return err } - modifiedNodes, err := selectedParentTreeNode.addChild(newTreeNode, rt.reindexRoot) + modifiedNodes := newModifiedTreeNodes() + err = selectedParentTreeNode.addChild(newTreeNode, rt.reindexRoot, modifiedNodes) if err != nil { return err } @@ -861,7 +855,8 @@ func (rt *reachabilityTree) addBlock(node *blockNode, selectedParentAnticone []* // at this stage the virtual had not yet been updated. if node.blueScore > rt.dag.SelectedTipBlueScore() { updateStartTime := time.Now() - modifiedNodes, err := rt.updateReindexRoot(newTreeNode) + modifiedNodes := newModifiedTreeNodes() + err := rt.updateReindexRoot(newTreeNode, modifiedNodes) if err != nil { return err } @@ -941,48 +936,47 @@ func (rt *reachabilityTree) storeState(dbTx *dbaccess.TxContext) error { return nil } -func (rt *reachabilityTree) updateReindexRoot(newTreeNode *reachabilityTreeNode) (modifiedTreeNodes, error) { - allModifiedTreeNodes := newModifiedTreeNodes() +func (rt *reachabilityTree) updateReindexRoot(newTreeNode *reachabilityTreeNode, + modifiedNodes modifiedTreeNodes) error { nextReindexRoot := rt.reindexRoot for { - candidateReindexRoot, modifiedNodes, found, err := rt.maybeMoveReindexRoot(nextReindexRoot, newTreeNode) + candidateReindexRoot, found, err := rt.maybeMoveReindexRoot(nextReindexRoot, newTreeNode, modifiedNodes) if err != nil { - return nil, err + return err } if !found { break } - allModifiedTreeNodes.addAll(modifiedNodes) nextReindexRoot = candidateReindexRoot } rt.reindexRoot = nextReindexRoot - return allModifiedTreeNodes, nil + return nil } func (rt *reachabilityTree) maybeMoveReindexRoot( - reindexRoot *reachabilityTreeNode, newTreeNode *reachabilityTreeNode) ( - newReindexRoot *reachabilityTreeNode, modifiedNodes modifiedTreeNodes, found bool, err error) { + reindexRoot *reachabilityTreeNode, newTreeNode *reachabilityTreeNode, modifiedNodes modifiedTreeNodes) ( + newReindexRoot *reachabilityTreeNode, found bool, err error) { if !reindexRoot.isAncestorOf(newTreeNode) { commonAncestor := newTreeNode.findCommonAncestorWithReindexRoot(reindexRoot) - return commonAncestor, nil, true, nil + return commonAncestor, true, nil } reindexRootChosenChild, err := reindexRoot.findAncestorAmongChildren(newTreeNode) if err != nil { - return nil, nil, false, err + return nil, false, err } if newTreeNode.blockNode.blueScore-reindexRootChosenChild.blockNode.blueScore < reachabilityReindexWindow { - return nil, nil, false, nil + return nil, false, nil } - modifiedNodes, err = rt.concentrateIntervalAroundReindexRootChosenChild(reindexRoot, reindexRootChosenChild) + err = rt.concentrateIntervalAroundReindexRootChosenChild(reindexRoot, reindexRootChosenChild, modifiedNodes) if err != nil { - return nil, nil, false, err + return nil, false, err } - return reindexRootChosenChild, modifiedNodes, true, nil + return reindexRootChosenChild, true, nil } // findAncestorAmongChildren finds the reachability tree child @@ -997,39 +991,34 @@ func (rtn *reachabilityTreeNode) findAncestorAmongChildren(node *reachabilityTre } func (rt *reachabilityTree) concentrateIntervalAroundReindexRootChosenChild( - reindexRoot *reachabilityTreeNode, reindexRootChosenChild *reachabilityTreeNode) ( - modifiedTreeNodes, error) { - - allModifiedTreeNodes := newModifiedTreeNodes() + reindexRoot *reachabilityTreeNode, reindexRootChosenChild *reachabilityTreeNode, + modifiedNodes modifiedTreeNodes) error { reindexRootChildNodesBeforeChosen, reindexRootChildNodesAfterChosen, err := reindexRoot.splitChildrenAroundChild(reindexRootChosenChild) if err != nil { - return nil, err + return err } - reindexRootChildNodesBeforeChosenSizesSum, modifiedNodesBeforeChosen, err := - rt.tightenIntervalsBeforeReindexRootChosenChild(reindexRoot, reindexRootChildNodesBeforeChosen) + reindexRootChildNodesBeforeChosenSizesSum, err := + rt.tightenIntervalsBeforeReindexRootChosenChild(reindexRoot, reindexRootChildNodesBeforeChosen, modifiedNodes) if err != nil { - return nil, err + return err } - allModifiedTreeNodes.addAll(modifiedNodesBeforeChosen) - reindexRootChildNodesAfterChosenSizesSum, modifiedNodesAfterChosen, err := - rt.tightenIntervalsAfterReindexRootChosenChild(reindexRoot, reindexRootChildNodesAfterChosen) + reindexRootChildNodesAfterChosenSizesSum, err := + rt.tightenIntervalsAfterReindexRootChosenChild(reindexRoot, reindexRootChildNodesAfterChosen, modifiedNodes) if err != nil { - return nil, err + return err } - allModifiedTreeNodes.addAll(modifiedNodesAfterChosen) - modifiedNodesForReindexRootExpansion, err := rt.expandIntervalInReindexRootChosenChild( - reindexRoot, reindexRootChosenChild, reindexRootChildNodesBeforeChosenSizesSum, reindexRootChildNodesAfterChosenSizesSum) + err = rt.expandIntervalInReindexRootChosenChild(reindexRoot, reindexRootChosenChild, + reindexRootChildNodesBeforeChosenSizesSum, reindexRootChildNodesAfterChosenSizesSum, modifiedNodes) if err != nil { - return nil, err + return err } - allModifiedTreeNodes.addAll(modifiedNodesForReindexRootExpansion) - return allModifiedTreeNodes, nil + return nil } // splitChildrenAroundChild splits `rtn` into two slices: the nodes that are before @@ -1046,8 +1035,8 @@ func (rtn *reachabilityTreeNode) splitChildrenAroundChild(child *reachabilityTre } func (rt *reachabilityTree) tightenIntervalsBeforeReindexRootChosenChild( - reindexRoot *reachabilityTreeNode, reindexRootChildNodesBeforeChosen []*reachabilityTreeNode) ( - reindexRootChildNodesBeforeChosenSizesSum uint64, modifiedNodes modifiedTreeNodes, err error) { + reindexRoot *reachabilityTreeNode, reindexRootChildNodesBeforeChosen []*reachabilityTreeNode, + modifiedNodes modifiedTreeNodes) (reindexRootChildNodesBeforeChosenSizesSum uint64, err error) { reindexRootChildNodesBeforeChosenSizes, reindexRootChildNodesBeforeChosenSubtreeSizeMaps, reindexRootChildNodesBeforeChosenSizesSum := calcReachabilityTreeNodeSizes(reindexRootChildNodesBeforeChosen) @@ -1057,17 +1046,17 @@ func (rt *reachabilityTree) tightenIntervalsBeforeReindexRootChosenChild( reindexRoot.interval.start+reachabilityReindexSlack+reindexRootChildNodesBeforeChosenSizesSum-1, ) - modifiedNodes, err = rt.propagateChildIntervals(intervalBeforeReindexRootStart, reindexRootChildNodesBeforeChosen, - reindexRootChildNodesBeforeChosenSizes, reindexRootChildNodesBeforeChosenSubtreeSizeMaps) + err = rt.propagateChildIntervals(intervalBeforeReindexRootStart, reindexRootChildNodesBeforeChosen, + reindexRootChildNodesBeforeChosenSizes, reindexRootChildNodesBeforeChosenSubtreeSizeMaps, modifiedNodes) if err != nil { - return 0, nil, err + return 0, err } - return reindexRootChildNodesBeforeChosenSizesSum, modifiedNodes, nil + return reindexRootChildNodesBeforeChosenSizesSum, nil } func (rt *reachabilityTree) tightenIntervalsAfterReindexRootChosenChild( - reindexRoot *reachabilityTreeNode, reindexRootChildNodesAfterChosen []*reachabilityTreeNode) ( - reindexRootChildNodesAfterChosenSizesSum uint64, modifiedNodes modifiedTreeNodes, err error) { + reindexRoot *reachabilityTreeNode, reindexRootChildNodesAfterChosen []*reachabilityTreeNode, + modifiedNodes modifiedTreeNodes) (reindexRootChildNodesAfterChosenSizesSum uint64, err error) { reindexRootChildNodesAfterChosenSizes, reindexRootChildNodesAfterChosenSubtreeSizeMaps, reindexRootChildNodesAfterChosenSizesSum := calcReachabilityTreeNodeSizes(reindexRootChildNodesAfterChosen) @@ -1077,19 +1066,17 @@ func (rt *reachabilityTree) tightenIntervalsAfterReindexRootChosenChild( reindexRoot.interval.end-reachabilityReindexSlack-1, ) - modifiedNodes, err = rt.propagateChildIntervals(intervalAfterReindexRootEnd, reindexRootChildNodesAfterChosen, - reindexRootChildNodesAfterChosenSizes, reindexRootChildNodesAfterChosenSubtreeSizeMaps) + err = rt.propagateChildIntervals(intervalAfterReindexRootEnd, reindexRootChildNodesAfterChosen, + reindexRootChildNodesAfterChosenSizes, reindexRootChildNodesAfterChosenSubtreeSizeMaps, modifiedNodes) if err != nil { - return 0, nil, err + return 0, err } - return reindexRootChildNodesAfterChosenSizesSum, modifiedNodes, nil + return reindexRootChildNodesAfterChosenSizesSum, nil } func (rt *reachabilityTree) expandIntervalInReindexRootChosenChild(reindexRoot *reachabilityTreeNode, reindexRootChosenChild *reachabilityTreeNode, reindexRootChildNodesBeforeChosenSizesSum uint64, - reindexRootChildNodesAfterChosenSizesSum uint64) (modifiedTreeNodes, error) { - - allModifiedTreeNodes := newModifiedTreeNodes() + reindexRootChildNodesAfterChosenSizesSum uint64, modifiedNodes modifiedTreeNodes) error { newReindexRootChildInterval := newReachabilityInterval( reindexRoot.interval.start+reindexRootChildNodesBeforeChosenSizesSum+reachabilityReindexSlack, @@ -1110,22 +1097,21 @@ func (rt *reachabilityTree) expandIntervalInReindexRootChosenChild(reindexRoot * newReindexRootChildInterval.start+reachabilityReindexSlack, newReindexRootChildInterval.end-reachabilityReindexSlack, ) - modifiedNodes, err := reindexRootChosenChild.countSubtreesAndPropagateInterval() + err := reindexRootChosenChild.countSubtreesAndPropagateInterval(modifiedNodes) if err != nil { - return nil, err + return err } - allModifiedTreeNodes.addAll(modifiedNodes) } reindexRootChosenChild.interval = newReindexRootChildInterval - allModifiedTreeNodes[reindexRootChosenChild] = struct{}{} - return allModifiedTreeNodes, nil + modifiedNodes[reindexRootChosenChild] = struct{}{} + return nil } -func (rtn *reachabilityTreeNode) countSubtreesAndPropagateInterval() (modifiedTreeNodes, error) { +func (rtn *reachabilityTreeNode) countSubtreesAndPropagateInterval(modifiedNodes modifiedTreeNodes) error { subtreeSizeMap := make(map[*reachabilityTreeNode]uint64) rtn.countSubtrees(subtreeSizeMap) - return rtn.propagateInterval(subtreeSizeMap) + return rtn.propagateInterval(subtreeSizeMap, modifiedNodes) } func calcReachabilityTreeNodeSizes(treeNodes []*reachabilityTreeNode) ( @@ -1146,14 +1132,12 @@ func calcReachabilityTreeNodeSizes(treeNodes []*reachabilityTreeNode) ( } func (rt *reachabilityTree) propagateChildIntervals(interval *reachabilityInterval, - childNodes []*reachabilityTreeNode, sizes []uint64, subtreeSizeMaps []map[*reachabilityTreeNode]uint64) ( - modifiedTreeNodes, error) { - - allModifiedTreeNodes := newModifiedTreeNodes() + childNodes []*reachabilityTreeNode, sizes []uint64, subtreeSizeMaps []map[*reachabilityTreeNode]uint64, + modifiedNodes modifiedTreeNodes) error { childIntervalSizes, err := interval.splitExact(sizes) if err != nil { - return nil, err + return err } for i, child := range childNodes { @@ -1161,14 +1145,13 @@ func (rt *reachabilityTree) propagateChildIntervals(interval *reachabilityInterv child.interval = childInterval childSubtreeSizeMap := subtreeSizeMaps[i] - modifiedNodes, err := child.propagateInterval(childSubtreeSizeMap) + err := child.propagateInterval(childSubtreeSizeMap, modifiedNodes) if err != nil { - return nil, err + return err } - allModifiedTreeNodes.addAll(modifiedNodes) } - return allModifiedTreeNodes, nil + return nil } // isInPast returns true if `this` is in the past (exclusive) of `other` diff --git a/blockdag/reachability_test.go b/blockdag/reachability_test.go index 47ad98bcc..07e03791a 100644 --- a/blockdag/reachability_test.go +++ b/blockdag/reachability_test.go @@ -19,7 +19,8 @@ func TestAddChild(t *testing.T) { currentTip := root for i := 0; i < 6; i++ { node := newReachabilityTreeNode(&blockNode{}) - modifiedNodes, err := currentTip.addChild(node, root) + modifiedNodes := newModifiedTreeNodes() + err := currentTip.addChild(node, root, modifiedNodes) if err != nil { t.Fatalf("TestAddChild: addChild failed: %s", err) } @@ -36,7 +37,8 @@ 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, root) + modifiedNodes := newModifiedTreeNodes() + err := currentTip.addChild(lastChild, root, modifiedNodes) if err != nil { t.Fatalf("TestAddChild: addChild failed: %s", err) } @@ -80,7 +82,8 @@ func TestAddChild(t *testing.T) { childNodes := make([]*reachabilityTreeNode, 6) for i := 0; i < len(childNodes); i++ { childNodes[i] = newReachabilityTreeNode(&blockNode{}) - modifiedNodes, err := root.addChild(childNodes[i], root) + modifiedNodes := newModifiedTreeNodes() + err := root.addChild(childNodes[i], root, modifiedNodes) if err != nil { t.Fatalf("TestAddChild: addChild failed: %s", err) } @@ -95,7 +98,8 @@ 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, root) + modifiedNodes = newModifiedTreeNodes() + err = root.addChild(lastChild, root, modifiedNodes) if err != nil { t.Fatalf("TestAddChild: addChild failed: %s", err) } @@ -135,7 +139,7 @@ func TestReachabilityTreeNodeIsAncestorOf(t *testing.T) { descendants := make([]*reachabilityTreeNode, numberOfDescendants) for i := 0; i < numberOfDescendants; i++ { node := newReachabilityTreeNode(&blockNode{}) - _, err := currentTip.addChild(node, root) + err := currentTip.addChild(node, root, newModifiedTreeNodes()) if err != nil { t.Fatalf("TestReachabilityTreeNodeIsAncestorOf: addChild failed: %s", err) } @@ -682,7 +686,7 @@ func TestReindexIntervalErrors(t *testing.T) { currentTreeNode := treeNode for i := 0; i < 100; i++ { childTreeNode := newReachabilityTreeNode(&blockNode{}) - _, err = currentTreeNode.addChild(childTreeNode, treeNode) + err = currentTreeNode.addChild(childTreeNode, treeNode, newModifiedTreeNodes()) if err != nil { break } @@ -719,7 +723,7 @@ func BenchmarkReindexInterval(b *testing.B) { currentTreeNode := root for i := 0; i < subTreeSize; i++ { childTreeNode := newReachabilityTreeNode(&blockNode{}) - _, err := currentTreeNode.addChild(childTreeNode, root) + err := currentTreeNode.addChild(childTreeNode, root, newModifiedTreeNodes()) if err != nil { b.Fatalf("addChild: %s", err) } @@ -732,7 +736,7 @@ func BenchmarkReindexInterval(b *testing.B) { // node should lead to a reindex from root. fullReindexTriggeringNode := newReachabilityTreeNode(&blockNode{}) b.StartTimer() - _, err := currentTreeNode.addChild(fullReindexTriggeringNode, root) + err := currentTreeNode.addChild(fullReindexTriggeringNode, root, newModifiedTreeNodes()) b.StopTimer() if err != nil { b.Fatalf("addChild: %s", err) @@ -818,6 +822,37 @@ func TestIsInPast(t *testing.T) { } } +func TestAddChildThatPointsDirectlyToTheSelectedParentChainBelowReindexRoot(t *testing.T) { + // Create a new database and DAG instance to run tests against. + dag, teardownFunc, err := DAGSetup("TestAddChildThatPointsDirectlyToTheSelectedParentChainBelowReindexRoot", + 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 a block on top of the genesis block + chainRootBlock := PrepareAndProcessBlockForTest(t, dag, []*daghash.Hash{dag.genesis.hash}, nil) + + // Add chain of reachabilityReindexWindow blocks above chainRootBlock. + // This should move the reindex root + chainRootBlockTipHash := chainRootBlock.BlockHash() + for i := uint64(0); i < reachabilityReindexWindow; i++ { + chainBlock := PrepareAndProcessBlockForTest(t, dag, []*daghash.Hash{chainRootBlockTipHash}, nil) + chainRootBlockTipHash = chainBlock.BlockHash() + } + + // Add another block over genesis + PrepareAndProcessBlockForTest(t, dag, []*daghash.Hash{dag.genesis.hash}, nil) +} + func TestUpdateReindexRoot(t *testing.T) { // Create a new database and DAG instance to run tests against. dag, teardownFunc, err := DAGSetup("TestUpdateReindexRoot", true, Config{