kaspad/domain/blockdag/reachability.go
Svarog 1e08bfca9c
[NOD-1032] Consensus updates pre-pruning (#917)
* [NOD-1249] Add pruning related constants (#869)

* [NOD-1249] Add pruning related constants

* [NOD-1249] Change status suspect to UTXONotVerified

* [NOD-1249] Add TestPruningDepth

* [NOD-1249] Add comment to pruningDepth

* [NOD-1249] Add pruning helper functions (#875)

* [NOD-1249] Added node.blockAtDepth

* [NOD-1249] Added node.finalityPoint()

* [NOD-1249] Add hasFinalityPointInOthersSelectedChain

* [NOD-1249] Add nonFinalityViolatingBlues

* [NOD-1249] Added isInPastOfAny

* [NOD-1249] Updated all calls to blockNode functions that require dag

* [NOD-1249] Add blockNode.reds field and persist it

* [NOD-1249] Add checkObjectiveFinality

* [NOD-1249] Add isViolatingSubjectiveFinality

* [NOD-1249] Added to TestGHOSTDAG check that reds are as expected

* [NOD-1249] Add checkMergeLimit and checkDAGRelations

* [NOD-1249] Invert condition in blockInDepth

* [NOD-1249] Make isInPastOfAny resemble isInPast

* [NOD-1249] Added comments to isInPast and isInPastOfAny

* [NOD-1252] Remove any references to legacy finality (#876)

* [NOD-1032] validateParents: check number of parents and that no parents were manually rejected (#877)

* [NOD-1254] Block verification changes (#882)

* [NOD-1254] Call checkDAGRelations and move it to correct place

* [NOD-1254] Use blockStatuses properly

* [NOD-1254] Add support for setting node's verification flag and set it to UTXONotVerified once block passes basic verification

* [NOD-1254] Check for subjctiveFinality, and for node not being in the selectedParentChain

* [NOD-1254] Make blockStatus an ordinary value - not bit flags

* [NOD-1254] Isolate all utxo-requiring validation into a single separate if branches

* [NOD-1254] Re-arrange connectBloc so that things that happen in UTXO-validated blocks only are all grouped together

* [NOD-1254] Resolve and check selectedParent's status before validatingUTXO

* [NOD-1254] Separate virtualUTXODiff from utxoVerificationOutput

* [NOD-1254] Stylistic fixes

* [NOD-1254] Use dag.index.(Set)BlockNodeStatus instead of accessing node.status

* [NOD-1288] Sub-routinize checkConnectToPastUTXO

* [NOD-1288] Re-write checkConnectToPastUTXO in a way that allows to filter-out invalid transactions

* [NOD-1288] Make checkTxSequenceLock use already calculated utxo outputs

* [NOD-1288] Make checkTxMass use already calculated utxo outputs

* [NOD-1288] Use dag.sigCache for ValidateTransactionScripts

* [NOD-1288] Use checkConnectTransactionToPastUTXO in applyBlueBlocks

* [NOD-1288] Clean-up old code-path from no longer used functions

* [NOD-1288] Skip any irrelevant parts of txo verification if block is genesis

* [NOD-1288] Set  where it should have been

* [NOD-1288] Fix reachability checks to never use the new node + make isInSelectedParentChainOf return true if node == other

* [NOD-1288] invert the condition for isNewSelectedTip

* [NOD-1288] Separate checkIsAccepted to own function, and properly handle coinbase

* [NOD-1288] Don't update utxo-diff for UTXONotVerified parents/tips + Make PrepareBlockForTest resolve the selectedParent's UTXOSet if needed

* [NOD-1288] Include mass off coinbase transactions

* [NOD-1288] Move comment to correct position

* [NOD-1288] If blockAtDepth should return genesis - do it immidiately

* [NOD-1288] Comment-out instead of removeing scriptval_test.go

* [NOD-1288] Rename: entry -> utxoEntry

* [NOD-1288] Remove special function for calcCoinbaseTxMass

* [NOD-1288] Remove redundant check from checkEntryAmounts

* [NOD-1288] Rename: MaxMassPerBlock -> MaxMassAcceptedByBlock

* [NOD-1255] Implement boundedMergeBreakingParents

* [NOD-1255] Implement selectAllowedTips

* [NOD-1255] Integrate virtual parent selection into block verification process

* [NOD-1255] Add node to tips all the time, remove it from candidates and add it's parents if it's disqualified

* [NOD-1255] remove tips from virtaulBlock

* [NOD-1255] Rename: didVirtualParentsChanged -> didVirtualParentsChange

* [NOD-1255] Remove redundant sanity check

* [NOD-1255] Handle a forgotten error

* [NOD-1255] Prettify selectVirtualParents

* [NOD-1255] UpdateTipsUTXO should run over all UTXO-Verified tips, even if they are not parents of virtual

* [NOD-1311] Make isInPast inclusive

* [NOD-1032] Handle finality conflicts (#904)

* [NOD-1312] AddTip should not include finalityViolating and manuallyRejected blocks

* [NOD-1312] Implement resolveFinalityConflict

* [NOD-1312] Implement dag notifications for finalityChanges + updateing DAG state

* [NOD-1312] Added finality conflict rpc boilerplate

* [NOD-1312] Implement handling of getFinalityConflicts + resolveFinalityConflict RPCs

* [NOD-1312] Implement finality conflict related notifications

* [NOD-1312] Move all time to millisecond time

* [NOD-1312] Add comments + unexport some methods

* [NOD-1312] Add clarification in comments

* [NOD-1312] Move updateFinalityConflictResolution to finality_conflicts.go

* [NOD-1312] Rename: currentSelectedTip -> selectedTip

* [NOD-1312] Add long comment to ResolveFinalityConflict

* [NOD-1312] Convert areAllInSelectedParentChainOf into isInSelectedParentChainOfAll

* [NOD-1312] Rename chainUpdates (type) -> selectedParentChainUpdates, to distinguish from the variable chainUpdates

* [NOD-1032] Make all blockdag tests compile

* [NOD-1278] Fix finality-related tests (#910)

* [NOD-1032] Don't return node.dag.genesis from blockAtDepth because it might still not exist

* [NOD-1032] Actually add a tip in dag.addTip

* [NOD-1278] Add transaction to utxo set if it's coinbase

* [NOD-1278] Use VirtualParentHashes instead of TipHashes where appropriate

* [NOD-1278] If no valid virtual parent candidates - return error, don't wait for panic

* [NOD-1278] Transition TestCalcSequenceLock from newTestDAG to DAGSetup

* [NOD-1278] Fix .bluest() tie-breaker

* [NOD-1278] Remove feeData structure, as it no longer works, store feeData in acceptanceData

* [NOD-1278] Remove dag parameter from blockNode methods

* [NOD-1278] Fix TestBlueBlockWindow

* [NOD-1278] Don't subject selectedParent to MaxMergeSet

* [NOD-1278] se PrepareAndProcessBlockForTest instead of .addTip in TestSelectedPath

* [NOD-1278] Fixed TestDAGStateSerialization

* [NOD-1278] Fix TestAcceptanceIndexRecover

* [NOD-1278] Fix TestCheckConnectBlockTemplate

* [NOD-1278] Fix TestChainUpdates

* [NOD-1278] Fix and rename TestVirtualBlock -> TestTips

* [NOD-1278] Rename checkIsAccepted -> maybeAcceptTx

* [NOD-1278] Re-activate TestDoubleSpends

* Revert "[NOD-1278] Fixed TestDAGStateSerialization"

This reverts commit 845095d6de7207b07cf819d05f3f38ad94da9cf6.

* [NOD-1278] Remove dag parameter from expectedCoinbaseTransaction

* [NOD-1348] Implemented simplified Finality Conflict Resolution scheme (#911)

* [NOD-1348] Rename functions according to Research spec

* [NOD-1348] Added blockSet.areAllIn

* [NOD-1348] Implemented simplified finality conflict resolution scheme

* [NOD-1348] Refactorings and fixes in selectVirtualParents

* [NOD-1278] Fix bugs discovered by unit-tests + Fix unit-tests (#916)

* Updated to version v0.3.1

* [NOD-858] Don't switch sync peer if the syncing process hasn't yet started with the current sync peer (#700)

* [NOD-858] Don't switch sync peer if the syncing process hasn't yet started with the current sync peer

* [NOD-858] SetShouldSendBlockLocator(false) on OnBlockLocator

* [NOD-858] Rename shouldSendBlockLocator->wasBlockLocatorRequested

* [NOD-858] Move panic to shouldReplaceSyncPeer

* [NOD-869] Add a print after os.Exit(1) to see if it is ever called (#701)

* [NOD-1238] Fix acceptance index never being initialized. (#859)

* [NOD-1278] Genesis never violates finality

* [NOD-1348] Refactorings and fixes in selectVirtualParents

* [NOD-1278] Don't call dag.selectVirtualParents for genesis

* [NOD-1278] Properly organize errors in maybeAcceptBlock

* [NOD-1278] updateTipsUTXO should only run on tips whose status is

* [NOD-1278] updateTipsUTXO should only run on tips whose status is `valid`

* [NOD-1278] Fix TestDoubleSpends

* [NOD-1278] Fix TestDAGIndexFailedStatus

* [NOD-1278] IsFinalizedTransaction should use uint64 everywhere

* [NOD-1278] If tx is coinbase and not selectedParent - don't update pastUTXO

* [NOD-1278] Store tips and validTips separately

* [NOD-1278] Store ValidTips and VirtualParents in dagState

* [NOD-1278] Fix TestProcessOrphans

* [NOD-1278] Fix TestProcessOrphans

* [NOD-1278] Fix TestOrderInDiffFromAcceptanceData

* [NOD-1278] Fix TestHelp

* [NOD-1278] Remove mining.PrepareBlockForTest; use blockdag.PrepareBlockForTest instead

* [NOD-1278] Explicitly disallow chained transactions

* [NOD-1278]

* [NOD-1278] Fix some comments

Co-authored-by: Ori Newman <orinewman1@gmail.com>
Co-authored-by: stasatdaglabs <39559713+stasatdaglabs@users.noreply.github.com>
Co-authored-by: Yuval Shaul <yuval.shaul@gmail.com>

* [NOD-1355] Add unit-test for finality + When resolving finalityConflict - make sure the block that will come out selectedTip is statusValid (#919)

* [NOD-1355] Added test for finality

* [NOD-1355] When resolving finalityConflict - make sure the block that will come out selectedTip is statusValid

* [NOD-1032] Renames: anything about inputsWithReferencedUTXOEntries -> remove 'Referenced'

* [NOD-1032] Don't ignore non-rule errors

* [NOD-1032] Fix comment

* [NOD-1032] Enhanced comments on TestChainUpdates

* [NOD-1032] Remove scriptval_test.go

* [NOD-1032] Extracted isNewSelectedTip to a method

* [NOD-1032] Use dag.Now() instead of mstime.Now()

* [NOD-1032] Print block status when accepting block

* [NOD-1032] Add comment explaining boundedMergeBreakingParents

* [NOD-1032] Enhanced test and imporved comments in TestFinality

* [NOD-1032] Rename: Objective finality -> bounded merge depth

* [NOD-1032] No need to check that validTips are valid

* [NOD-1032] Remove dag from arguments of updateDiffAndDiffChild

* [NOD-1032] Correct variable names in LookupNodes

[NOD-1032] Correct variable names in LookupNodes

* [NOD-1032] Fix some comments

* [NOD-1032] Some style changes

* [NOD-1032] Refactor isAnyInPastOf

* [NOD-1032] Enhance comment in updateVirtualParents

* [NOD-1032] Flip condition in updateVirtualParents

* [NOD-1032] Stylistic and grammatic fixes in dag.go and dag_test.go

* [NOD-1032] Explain why updateSelectedParentSet receives geneses on init

* [NOD-1032] Remove ErrParentManuallyRejected

* [NOD-1032] Added wrapper for resolveNodeStatus that creates a special transaction for it

* [NOD-1032] Rename: statusUTXONotVerified -> statusUTXOPendingVerification

* [NOD-1032] Use virtual parents in CurrentBits()

* [NOD-1032] rename: isViolatingSubjectiveFinality -> isViolatingFinality

* [NOD-1032] Move updateVirtualAndTips to applyDAGChanges

* [NOD-1032] Invert condition for isFinalityPointInPast

* [NOD-1032] Fix antiPastBetween isInPast became inclusive

* [NOD-1032] Remove redundant call for addTip

* [NOD-1032] Use calcCoinbaseTxMass where appropriate

* [NOD-1032] Remove time fields from conflict notifications

* [NOD-1032] Assign the correct thing to i

* [NOD-1032] unify checkOutputsAmounts and checkTxOutputAmounts

* [NOD-1032] Cleanup in CheckTransactionInputsAndCalulateFee

* [NOD-1032] Fixed some style and comments

* [NOD-1032] If selectedParent is disqualifiedFromChain - validateAndApplyUTXOSet should return this as a ruleError

* [NOD-1032] Set the status in resolveNodeStatus

* [NOD-1032] Correct comment on boundedMergeBreakingParents

* [NOD-1032] Fix a typo.

* [NOD-1032] Update a variable name.

* [NOD-1032] Fix a comment.

* [NOD-1032] Fix merge errors.

* [NOD-1032] Add VirtualParentHashes to getBlockDagInfo.

* [NOD-1032] Update handleGetBlockTemplate.

* [NOD-1032] Comment out all the old RPC stuff.

* [NOD-1032] Remove irrelevant type.

* [NOD-1032] Implement ResolveFinalityConflict.

* [NOD-1032] Remove irrelevant comments.

* [NOD-1032] Implement NotifyFinalityConflicts.

* [NOD-1032] Add FinalityConflictNotification and FinalityConflictResolvedNotification.

* [NOD-1032] Finish implementing finality conflict notifications.

* [NOD-1032] Remove old RPC stuff.

* [NOD-1032] Fix grammar in a comment.

Co-authored-by: Ori Newman <orinewman1@gmail.com>
Co-authored-by: stasatdaglabs <39559713+stasatdaglabs@users.noreply.github.com>
Co-authored-by: Yuval Shaul <yuval.shaul@gmail.com>
Co-authored-by: stasatdaglabs <stas@daglabs.com>
2020-09-13 17:49:59 +03:00

1209 lines
41 KiB
Go

package blockdag
import (
"fmt"
"math"
"strings"
"time"
"github.com/kaspanet/kaspad/infrastructure/db/dbaccess"
"github.com/pkg/errors"
)
var (
// reachabilityReindexWindow is the target window size for reachability
// reindexes. Note that this is not a constant for testing purposes.
reachabilityReindexWindow uint64 = 200
// reachabilityReindexSlack is the slack interval given to reachability
// tree nodes not in the selected parent chain. Note that this is not
// a constant for testing purposes.
reachabilityReindexSlack uint64 = 1 << 12
// slackReachabilityIntervalForReclaiming is the slack interval to
// reclaim during reachability reindexes earlier than the reindex root.
// See reclaimIntervalBeforeChosenChild for further details. Note that
// this is not a constant for testing purposes.
slackReachabilityIntervalForReclaiming uint64 = 1
)
// modifiedTreeNodes are a set of reachabilityTreeNodes that's bubbled up
// from any function that modifies them, so that the original caller may
// update the database accordingly. This is a set rather than a slice due
// to frequent duplicate treeNodes between operations.
type modifiedTreeNodes map[*reachabilityTreeNode]struct{}
func newModifiedTreeNodes(nodes ...*reachabilityTreeNode) modifiedTreeNodes {
modifiedNodes := make(modifiedTreeNodes)
for _, node := range nodes {
modifiedNodes[node] = struct{}{}
}
return modifiedNodes
}
// reachabilityInterval represents an interval to be used within the
// tree reachability algorithm. See reachabilityTreeNode for further
// details.
type reachabilityInterval struct {
start uint64
end uint64
}
func newReachabilityInterval(start uint64, end uint64) *reachabilityInterval {
return &reachabilityInterval{start: start, end: end}
}
// size returns the size of this interval. Note that intervals are
// inclusive from both sides.
func (ri *reachabilityInterval) size() uint64 {
return ri.end - ri.start + 1
}
// splitInHalf splits this interval by a fraction of 0.5.
// See splitFraction for further details.
func (ri *reachabilityInterval) splitInHalf() (
left *reachabilityInterval, right *reachabilityInterval, err error) {
return ri.splitFraction(0.5)
}
// splitFraction splits this interval to two parts such that their
// union is equal to the original interval and the first (left) part
// contains the given fraction of the original interval's size.
// Note: if the split results in fractional parts, this method rounds
// the first part up and the last part down.
func (ri *reachabilityInterval) splitFraction(fraction float64) (
left *reachabilityInterval, right *reachabilityInterval, err error) {
if fraction < 0 || fraction > 1 {
return nil, nil, errors.Errorf("fraction must be between 0 and 1")
}
if ri.size() == 0 {
return nil, nil, errors.Errorf("cannot split an empty interval")
}
allocationSize := uint64(math.Ceil(float64(ri.size()) * fraction))
left = newReachabilityInterval(ri.start, ri.start+allocationSize-1)
right = newReachabilityInterval(ri.start+allocationSize, ri.end)
return left, right, nil
}
// splitExact splits this interval to exactly |sizes| parts where
// |part_i| = sizes[i]. This method expects sum(sizes) to be exactly
// equal to the interval's size.
func (ri *reachabilityInterval) splitExact(sizes []uint64) ([]*reachabilityInterval, error) {
sizesSum := uint64(0)
for _, size := range sizes {
sizesSum += size
}
if sizesSum != ri.size() {
return nil, errors.Errorf("sum of sizes must be equal to the interval's size")
}
intervals := make([]*reachabilityInterval, len(sizes))
start := ri.start
for i, size := range sizes {
intervals[i] = newReachabilityInterval(start, start+size-1)
start += size
}
return intervals, nil
}
// splitWithExponentialBias splits this interval to |sizes| parts
// by the allocation rule described below. This method expects sum(sizes)
// to be smaller or equal to the interval's size. Every part_i is
// allocated at least sizes[i] capacity. The remaining budget is
// split by an exponentially biased rule described below.
//
// This rule follows the GHOSTDAG protocol behavior where the child
// with the largest subtree is expected to dominate the competition
// for new blocks and thus grow the most. However, we may need to
// add slack for non-largest subtrees in order to make CPU reindexing
// attacks unworthy.
func (ri *reachabilityInterval) splitWithExponentialBias(sizes []uint64) ([]*reachabilityInterval, error) {
intervalSize := ri.size()
sizesSum := uint64(0)
for _, size := range sizes {
sizesSum += size
}
if sizesSum > intervalSize {
return nil, errors.Errorf("sum of sizes must be less than or equal to the interval's size")
}
if sizesSum == intervalSize {
return ri.splitExact(sizes)
}
// Add a fractional bias to every size in the given sizes
totalBias := intervalSize - sizesSum
remainingBias := totalBias
biasedSizes := make([]uint64, len(sizes))
fractions := exponentialFractions(sizes)
for i, fraction := range fractions {
var bias uint64
if i == len(fractions)-1 {
bias = remainingBias
} else {
bias = uint64(math.Round(float64(totalBias) * fraction))
if bias > remainingBias {
bias = remainingBias
}
}
biasedSizes[i] = sizes[i] + bias
remainingBias -= bias
}
return ri.splitExact(biasedSizes)
}
// exponentialFractions returns a fraction of each size in sizes
// as follows:
// fraction[i] = 2^size[i] / sum_j(2^size[j])
// In the code below the above equation is divided by 2^max(size)
// to avoid exploding numbers. Note that in 1 / 2^(max(size)-size[i])
// we divide 1 by potentially a very large number, which will
// result in loss of float precision. This is not a problem - all
// numbers close to 0 bear effectively the same weight.
func exponentialFractions(sizes []uint64) []float64 {
maxSize := uint64(0)
for _, size := range sizes {
if size > maxSize {
maxSize = size
}
}
fractions := make([]float64, len(sizes))
for i, size := range sizes {
fractions[i] = 1 / math.Pow(2, float64(maxSize-size))
}
fractionsSum := float64(0)
for _, fraction := range fractions {
fractionsSum += fraction
}
for i, fraction := range fractions {
fractions[i] = fraction / fractionsSum
}
return fractions
}
// contains returns true if ri contains other.
func (ri *reachabilityInterval) contains(other *reachabilityInterval) bool {
return ri.start <= other.start && other.end <= ri.end
}
// String returns a string representation of the interval.
func (ri *reachabilityInterval) String() string {
return fmt.Sprintf("[%d,%d]", ri.start, ri.end)
}
// reachabilityTreeNode represents a node in the reachability tree
// of some DAG block. It mainly provides the ability to query *tree*
// reachability with O(1) query time. It does so by managing an
// index interval for each node and making sure all nodes in its
// subtree are indexed within the interval, so the query
// B ∈ subtree(A) simply becomes B.interval ⊂ A.interval.
//
// The main challenge of maintaining such intervals is that our tree
// is an ever-growing tree and as such pre-allocated intervals may
// not suffice as per future events. This is where the reindexing
// algorithm below comes into place.
// We use the reasonable assumption that the initial root interval
// (e.g., [0, 2^64-1]) should always suffice for any practical use-
// case, and so reindexing should always succeed unless more than
// 2^64 blocks are added to the DAG/tree.
type reachabilityTreeNode struct {
blockNode *blockNode
children []*reachabilityTreeNode
parent *reachabilityTreeNode
// interval is the index interval containing all intervals of
// blocks in this node's subtree
interval *reachabilityInterval
}
func newReachabilityTreeNode(blockNode *blockNode) *reachabilityTreeNode {
// Please see the comment above reachabilityTreeNode to understand why
// we use these initial values.
interval := newReachabilityInterval(1, math.MaxUint64-1)
return &reachabilityTreeNode{blockNode: blockNode, interval: interval}
}
func (rtn *reachabilityTreeNode) intervalRangeForChildAllocation() *reachabilityInterval {
// We subtract 1 from the end of the range to prevent the node from allocating
// the entire interval to its child, so its interval would *strictly* contain the interval of its child.
return newReachabilityInterval(rtn.interval.start, rtn.interval.end-1)
}
func (rtn *reachabilityTreeNode) remainingIntervalBefore() *reachabilityInterval {
childRange := rtn.intervalRangeForChildAllocation()
if len(rtn.children) == 0 {
return childRange
}
return newReachabilityInterval(childRange.start, rtn.children[0].interval.start-1)
}
func (rtn *reachabilityTreeNode) remainingIntervalAfter() *reachabilityInterval {
childRange := rtn.intervalRangeForChildAllocation()
if len(rtn.children) == 0 {
return childRange
}
return newReachabilityInterval(rtn.children[len(rtn.children)-1].interval.end+1, childRange.end)
}
func (rtn *reachabilityTreeNode) hasSlackIntervalBefore() bool {
return rtn.remainingIntervalBefore().size() > 0
}
func (rtn *reachabilityTreeNode) hasSlackIntervalAfter() bool {
return rtn.remainingIntervalAfter().size() > 0
}
// addChild adds child to this tree node. If this node has no
// 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,
modifiedNodes modifiedTreeNodes) error {
remaining := rtn.remainingIntervalAfter()
// Set the parent-child relationship
rtn.children = append(rtn.children, child)
child.parent = rtn
modifiedNodes[rtn] = struct{}{}
modifiedNodes[child] = struct{}{}
// Temporarily set the child's interval to be empty, at
// the start of rtn's remaining interval. This is done
// so that child-of-rtn checks (e.g.
// findAncestorAmongChildren) will not fail for rtn.
child.interval = newReachabilityInterval(remaining.start, remaining.start-1)
// Handle rtn not being a descendant of the reindex root.
// Note that we check rtn here instead of child because
// at this point we don't yet know child's interval.
if !reindexRoot.isAncestorOf(rtn) {
reindexStartTime := time.Now()
err := rtn.reindexIntervalsEarlierThanReindexRoot(reindexRoot, modifiedNodes)
if err != nil {
return err
}
reindexTimeElapsed := time.Since(reindexStartTime)
log.Debugf("Reachability reindex triggered for "+
"block %s. This block is not a child of the current "+
"reindex root %s. Modified %d tree nodes and took %dms.",
rtn.blockNode.hash, reindexRoot.blockNode.hash,
len(modifiedNodes), reindexTimeElapsed.Milliseconds())
return nil
}
// No allocation space left -- reindex
if remaining.size() == 0 {
reindexStartTime := time.Now()
err := rtn.reindexIntervals(modifiedNodes)
if err != nil {
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 nil
}
// Allocate from the remaining space
allocated, _, err := remaining.splitInHalf()
if err != nil {
return err
}
child.interval = allocated
return nil
}
// reindexIntervals traverses the reachability subtree that's
// defined by this node and reallocates reachability interval space
// such that another reindexing is unlikely to occur shortly
// thereafter. It does this by traversing down the reachability
// 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(modifiedNodes modifiedTreeNodes) error {
current := rtn
// Initial interval and subtree sizes
intervalSize := current.interval.size()
subTreeSizeMap := make(map[*reachabilityTreeNode]uint64)
current.countSubtrees(subTreeSizeMap)
currentSubtreeSize := subTreeSizeMap[current]
// Find the first ancestor that has sufficient interval space
for intervalSize < currentSubtreeSize {
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 errors.Errorf("missing tree " +
"parent during reindexing. Theoretically, this " +
"should only ever happen if there are more " +
"than 2^64 blocks in the DAG.")
}
current = current.parent
intervalSize = current.interval.size()
current.countSubtrees(subTreeSizeMap)
currentSubtreeSize = subTreeSizeMap[current]
}
// Propagate the interval to the subtree
return current.propagateInterval(subTreeSizeMap, modifiedNodes)
}
// countSubtrees counts the size of each subtree under this node,
// and populates the provided subTreeSizeMap with the results.
// It is equivalent to the following recursive implementation:
//
// func (rtn *reachabilityTreeNode) countSubtrees() uint64 {
// subtreeSize := uint64(0)
// for _, child := range rtn.children {
// subtreeSize += child.countSubtrees()
// }
// return subtreeSize + 1
// }
//
// However, we are expecting (linearly) deep trees, and so a
// recursive stack-based approach is inefficient and will hit
// recursion limits. Instead, the same logic was implemented
// using a (queue-based) BFS method. At a high level, the
// algorithm uses BFS for reaching all leaves and pushes
// intermediate updates from leaves via parent chains until all
// size information is gathered at the root of the operation
// (i.e. at rtn).
func (rtn *reachabilityTreeNode) countSubtrees(subTreeSizeMap map[*reachabilityTreeNode]uint64) {
queue := []*reachabilityTreeNode{rtn}
calculatedChildrenCount := make(map[*reachabilityTreeNode]uint64)
for len(queue) > 0 {
var current *reachabilityTreeNode
current, queue = queue[0], queue[1:]
if len(current.children) == 0 {
// We reached a leaf
subTreeSizeMap[current] = 1
} else if _, ok := subTreeSizeMap[current]; !ok {
// We haven't yet calculated the subtree size of
// the current node. Add all its children to the
// queue
queue = append(queue, current.children...)
continue
}
// We reached a leaf or a pre-calculated subtree.
// Push information up
for current != rtn {
current = current.parent
calculatedChildrenCount[current]++
if calculatedChildrenCount[current] != uint64(len(current.children)) {
// Not all subtrees of the current node are ready
break
}
// All children of `current` have calculated their subtree size.
// Sum them all together and add 1 to get the sub tree size of
// `current`.
childSubtreeSizeSum := uint64(0)
for _, child := range current.children {
childSubtreeSizeSum += subTreeSizeMap[child]
}
subTreeSizeMap[current] = childSubtreeSizeSum + 1
}
}
}
// propagateInterval propagates the new interval using a BFS traversal.
// 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,
modifiedNodes modifiedTreeNodes) error {
queue := []*reachabilityTreeNode{rtn}
for len(queue) > 0 {
var current *reachabilityTreeNode
current, queue = queue[0], queue[1:]
if len(current.children) > 0 {
sizes := make([]uint64, len(current.children))
for i, child := range current.children {
sizes[i] = subTreeSizeMap[child]
}
intervals, err := current.intervalRangeForChildAllocation().splitWithExponentialBias(sizes)
if err != nil {
return err
}
for i, child := range current.children {
childInterval := intervals[i]
child.interval = childInterval
queue = append(queue, child)
}
}
modifiedNodes[current] = struct{}{}
}
return nil
}
func (rtn *reachabilityTreeNode) reindexIntervalsEarlierThanReindexRoot(
reindexRoot *reachabilityTreeNode, modifiedNodes modifiedTreeNodes) error {
// Find the common ancestor for both rtn and the reindex root
commonAncestor := rtn.findCommonAncestorWithReindexRoot(reindexRoot)
// The chosen child is:
// a. A reachability tree child of `commonAncestor`
// b. A reachability tree ancestor of `reindexRoot`
commonAncestorChosenChild, err := commonAncestor.findAncestorAmongChildren(reindexRoot)
if err != nil {
return err
}
if rtn.interval.end < commonAncestorChosenChild.interval.start {
// rtn is in the subtree before the chosen child
return rtn.reclaimIntervalBeforeChosenChild(commonAncestor,
commonAncestorChosenChild, reindexRoot, modifiedNodes)
}
// 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, modifiedNodes modifiedTreeNodes) error {
current := commonAncestorChosenChild
if !commonAncestorChosenChild.hasSlackIntervalBefore() {
// The common ancestor ran out of slack before its chosen child.
// Climb up the reachability tree toward the reindex root until
// we find a node that has enough slack.
for !current.hasSlackIntervalBefore() && current != reindexRoot {
var err error
current, err = current.findAncestorAmongChildren(reindexRoot)
if err != nil {
return err
}
}
if current == reindexRoot {
// "Deallocate" an interval of slackReachabilityIntervalForReclaiming
// from this node. This is the interval that we'll use for the new
// node.
originalInterval := current.interval
current.interval = newReachabilityInterval(
current.interval.start+slackReachabilityIntervalForReclaiming,
current.interval.end,
)
err := current.countSubtreesAndPropagateInterval(modifiedNodes)
if err != nil {
return err
}
current.interval = originalInterval
}
}
// Go down the reachability tree towards the common ancestor.
// On every hop we reindex the reachability subtree before the
// current node with an interval that is smaller by
// slackReachabilityIntervalForReclaiming. This is to make room
// for the new node.
for current != commonAncestor {
current.interval = newReachabilityInterval(
current.interval.start+slackReachabilityIntervalForReclaiming,
current.interval.end,
)
err := current.parent.reindexIntervalsBeforeNode(current, modifiedNodes)
if err != nil {
return err
}
current = current.parent
}
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,
modifiedNodes modifiedTreeNodes) error {
childrenBeforeNode, _, err := rtn.splitChildrenAroundChild(node)
if err != nil {
return err
}
childrenBeforeNodeSizes, childrenBeforeNodeSubtreeSizeMaps, childrenBeforeNodeSizesSum :=
calcReachabilityTreeNodeSizes(childrenBeforeNode)
// Apply a tight interval
newIntervalEnd := node.interval.start - 1
newInterval := newReachabilityInterval(newIntervalEnd-childrenBeforeNodeSizesSum+1, newIntervalEnd)
intervals, err := newInterval.splitExact(childrenBeforeNodeSizes)
if err != nil {
return err
}
return orderedTreeNodeSet(childrenBeforeNode).
propagateIntervals(intervals, childrenBeforeNodeSubtreeSizeMaps, modifiedNodes)
}
func (rtn *reachabilityTreeNode) reclaimIntervalAfterChosenChild(
commonAncestor *reachabilityTreeNode, commonAncestorChosenChild *reachabilityTreeNode,
reindexRoot *reachabilityTreeNode, modifiedNodes modifiedTreeNodes) error {
current := commonAncestorChosenChild
if !commonAncestorChosenChild.hasSlackIntervalAfter() {
// The common ancestor ran out of slack after its chosen child.
// Climb up the reachability tree toward the reindex root until
// we find a node that has enough slack.
for !current.hasSlackIntervalAfter() && current != reindexRoot {
var err error
current, err = current.findAncestorAmongChildren(reindexRoot)
if err != nil {
return err
}
}
if current == reindexRoot {
// "Deallocate" an interval of slackReachabilityIntervalForReclaiming
// from this node. This is the interval that we'll use for the new
// node.
originalInterval := current.interval
current.interval = newReachabilityInterval(
current.interval.start,
current.interval.end-slackReachabilityIntervalForReclaiming,
)
modifiedNodes[current] = struct{}{}
err := current.countSubtreesAndPropagateInterval(modifiedNodes)
if err != nil {
return err
}
current.interval = originalInterval
}
}
// Go down the reachability tree towards the common ancestor.
// On every hop we reindex the reachability subtree after the
// current node with an interval that is smaller by
// slackReachabilityIntervalForReclaiming. This is to make room
// for the new node.
for current != commonAncestor {
current.interval = newReachabilityInterval(
current.interval.start,
current.interval.end-slackReachabilityIntervalForReclaiming,
)
modifiedNodes[current] = struct{}{}
err := current.parent.reindexIntervalsAfterNode(current, modifiedNodes)
if err != nil {
return err
}
current = current.parent
}
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,
modifiedNodes modifiedTreeNodes) error {
_, childrenAfterNode, err := rtn.splitChildrenAroundChild(node)
if err != nil {
return err
}
childrenAfterNodeSizes, childrenAfterNodeSubtreeSizeMaps, childrenAfterNodeSizesSum :=
calcReachabilityTreeNodeSizes(childrenAfterNode)
// Apply a tight interval
newIntervalStart := node.interval.end + 1
newInterval := newReachabilityInterval(newIntervalStart, newIntervalStart+childrenAfterNodeSizesSum-1)
intervals, err := newInterval.splitExact(childrenAfterNodeSizes)
if err != nil {
return err
}
return orderedTreeNodeSet(childrenAfterNode).
propagateIntervals(intervals, childrenAfterNodeSubtreeSizeMaps, modifiedNodes)
}
func (tns orderedTreeNodeSet) propagateIntervals(intervals []*reachabilityInterval,
subtreeSizeMaps []map[*reachabilityTreeNode]uint64, modifiedNodes modifiedTreeNodes) error {
for i, node := range tns {
node.interval = intervals[i]
subtreeSizeMap := subtreeSizeMaps[i]
err := node.propagateInterval(subtreeSizeMap, modifiedNodes)
if err != nil {
return err
}
}
return nil
}
// isAncestorOf checks if this node is a reachability tree ancestor
// of the other node. Note that we use the graph theory convention
// here which defines that rtn is also an ancestor of itself.
func (rtn *reachabilityTreeNode) isAncestorOf(other *reachabilityTreeNode) bool {
return rtn.interval.contains(other.interval)
}
// findCommonAncestorWithReindexRoot finds the most recent reachability
// tree ancestor common to both rtn and the given reindex root. Note
// that we assume that almost always the chain between the reindex root
// and the common ancestor is longer than the chain between rtn and the
// common ancestor.
func (rtn *reachabilityTreeNode) findCommonAncestorWithReindexRoot(reindexRoot *reachabilityTreeNode) *reachabilityTreeNode {
currentThis := rtn
for {
if currentThis.isAncestorOf(reindexRoot) {
return currentThis
}
currentThis = currentThis.parent
}
}
// String returns a string representation of a reachability tree node
// and its children.
func (rtn *reachabilityTreeNode) String() string {
queue := []*reachabilityTreeNode{rtn}
lines := []string{rtn.interval.String()}
for len(queue) > 0 {
var current *reachabilityTreeNode
current, queue = queue[0], queue[1:]
if len(current.children) == 0 {
continue
}
line := ""
for _, child := range current.children {
line += child.interval.String()
queue = append(queue, child)
}
lines = append([]string{line}, lines...)
}
return strings.Join(lines, "\n")
}
// orderedTreeNodeSet is an ordered set of reachabilityTreeNodes
// Note that this type does not validate order validity. It's the
// responsibility of the caller to construct instances of this
// type properly.
type orderedTreeNodeSet []*reachabilityTreeNode
// futureCoveringTreeNodeSet represents a collection of blocks in the future of
// a certain block. Once a block B is added to the DAG, every block A_i in
// B's selected parent anticone must register B in its futureCoveringTreeNodeSet. This allows
// to relatively quickly (O(log(|futureCoveringTreeNodeSet|))) query whether B
// is a descendent (is in the "future") of any block that previously
// registered it.
//
// Note that futureCoveringTreeNodeSet is meant to be queried only if B is not
// a reachability tree descendant of the block in question, as reachability
// tree queries are always O(1).
//
// See insertNode, hasAncestorOf, and reachabilityTree.isInPast for further
// details.
type futureCoveringTreeNodeSet orderedTreeNodeSet
// insertNode inserts the given block into this futureCoveringTreeNodeSet
// while keeping futureCoveringTreeNodeSet ordered by interval.
// If a block B ∈ futureCoveringTreeNodeSet exists such that its interval
// contains block's interval, block need not be added. If block's
// interval contains B's interval, it replaces it.
//
// Notes:
// * Intervals never intersect unless one contains the other
// (this follows from the tree structure and the indexing rule).
// * Since futureCoveringTreeNodeSet is kept ordered, a binary search can be
// used for insertion/queries.
// * Although reindexing may change a block's interval, the
// is-superset relation will by definition
// be always preserved.
func (fb *futureCoveringTreeNodeSet) insertNode(node *reachabilityTreeNode) {
ancestorIndex, ok := orderedTreeNodeSet(*fb).findAncestorIndexOfNode(node)
if !ok {
*fb = append([]*reachabilityTreeNode{node}, *fb...)
return
}
candidate := (*fb)[ancestorIndex]
if candidate.isAncestorOf(node) {
// candidate is an ancestor of node, no need to insert
return
}
if node.isAncestorOf(candidate) {
// node is an ancestor of candidate, and can thus replace it
(*fb)[ancestorIndex] = node
return
}
// Insert node in the correct index to maintain futureCoveringTreeNodeSet as
// a sorted-by-interval list.
// Note that ancestorIndex might be equal to len(futureCoveringTreeNodeSet)
left := (*fb)[:ancestorIndex+1]
right := append([]*reachabilityTreeNode{node}, (*fb)[ancestorIndex+1:]...)
*fb = append(left, right...)
}
// hasAncestorOf resolves whether the given node is in the subtree of
// any node in this futureCoveringTreeNodeSet.
// See insertNode method for the complementary insertion behavior.
//
// Like the insert method, this method also relies on the fact that
// futureCoveringTreeNodeSet is kept ordered by interval to efficiently perform a
// binary search over futureCoveringTreeNodeSet and answer the query in
// O(log(|futureCoveringTreeNodeSet|)).
func (fb futureCoveringTreeNodeSet) hasAncestorOf(node *reachabilityTreeNode) bool {
ancestorIndex, ok := orderedTreeNodeSet(fb).findAncestorIndexOfNode(node)
if !ok {
// No candidate to contain node
return false
}
candidate := fb[ancestorIndex]
return candidate.isAncestorOf(node)
}
// findAncestorOfNode finds the reachability tree ancestor of `node`
// among the nodes in `tns`.
func (tns orderedTreeNodeSet) findAncestorOfNode(node *reachabilityTreeNode) (*reachabilityTreeNode, bool) {
ancestorIndex, ok := tns.findAncestorIndexOfNode(node)
if !ok {
return nil, false
}
return tns[ancestorIndex], true
}
// findAncestorIndexOfNode finds the index of the reachability tree
// ancestor of `node` among the nodes in `tns`. It does so by finding
// the index of the block with the maximum start that is below the
// given block.
func (tns orderedTreeNodeSet) findAncestorIndexOfNode(node *reachabilityTreeNode) (int, bool) {
blockInterval := node.interval
end := blockInterval.end
low := 0
high := len(tns)
for low < high {
middle := (low + high) / 2
middleInterval := tns[middle].interval
if end < middleInterval.start {
high = middle
} else {
low = middle + 1
}
}
if low == 0 {
return 0, false
}
return low - 1, true
}
// String returns a string representation of the intervals in this futureCoveringTreeNodeSet.
func (fb futureCoveringTreeNodeSet) String() string {
intervalsString := ""
for _, node := range fb {
intervalsString += node.interval.String()
}
return intervalsString
}
func (rt *reachabilityTree) addBlock(node *blockNode, selectedParentAnticone []*blockNode) error {
// Allocate a new reachability tree node
newTreeNode := newReachabilityTreeNode(node)
// If this is the genesis node, simply initialize it and return
if node.isGenesis() {
rt.store.setTreeNode(newTreeNode)
rt.reindexRoot = newTreeNode
return nil
}
// Insert the node into the selected parent's reachability tree
selectedParentTreeNode, err := rt.store.treeNodeByBlockNode(node.selectedParent)
if err != nil {
return err
}
modifiedNodes := newModifiedTreeNodes()
err = selectedParentTreeNode.addChild(newTreeNode, rt.reindexRoot, modifiedNodes)
if err != nil {
return err
}
for modifiedNode := range modifiedNodes {
rt.store.setTreeNode(modifiedNode)
}
// Add the block to the futureCoveringSets of all the blocks
// in the selected parent's anticone
for _, current := range selectedParentAnticone {
currentFutureCoveringSet, err := rt.store.futureCoveringSetByBlockNode(current)
if err != nil {
return err
}
currentFutureCoveringSet.insertNode(newTreeNode)
err = rt.store.setFutureCoveringSet(current, currentFutureCoveringSet)
if err != nil {
return err
}
}
// Update the reindex root.
// Note that we check for blue score here in order to find out
// whether the new node is going to be the virtual's selected
// parent. We don't check node == virtual.selectedParent because
// at this stage the virtual had not yet been updated.
if node.blueScore > rt.dag.SelectedTipBlueScore() {
updateStartTime := time.Now()
modifiedNodes := newModifiedTreeNodes()
err := rt.updateReindexRoot(newTreeNode, modifiedNodes)
if err != nil {
return err
}
if len(modifiedNodes) > 0 {
updateTimeElapsed := time.Since(updateStartTime)
log.Debugf("Reachability reindex root updated to %s. "+
"Modified %d tree nodes and took %dms.",
rt.reindexRoot.blockNode.hash,
len(modifiedNodes), updateTimeElapsed.Milliseconds())
for modifiedNode := range modifiedNodes {
rt.store.setTreeNode(modifiedNode)
}
}
}
return nil
}
type reachabilityTree struct {
dag *BlockDAG
store *reachabilityStore
reindexRoot *reachabilityTreeNode
}
func newReachabilityTree(dag *BlockDAG) *reachabilityTree {
store := newReachabilityStore(dag)
return &reachabilityTree{
dag: dag,
store: store,
reindexRoot: nil,
}
}
func (rt *reachabilityTree) init(dbContext *dbaccess.DatabaseContext) error {
// Init the store
err := rt.store.init(dbContext)
if err != nil {
return err
}
// Fetch the reindex root hash. If missing, use the genesis hash
reindexRootHash, err := dbaccess.FetchReachabilityReindexRoot(dbContext)
if err != nil {
if !dbaccess.IsNotFoundError(err) {
return err
}
reindexRootHash = rt.dag.Params.GenesisHash
}
// Init the reindex root
reachabilityReindexRootNode, ok := rt.dag.index.LookupNode(reindexRootHash)
if !ok {
return errors.Errorf("reachability reindex root block %s "+
"does not exist in the DAG", reindexRootHash)
}
rt.reindexRoot, err = rt.store.treeNodeByBlockNode(reachabilityReindexRootNode)
if err != nil {
return errors.Wrapf(err, "cannot set reachability reindex root")
}
return nil
}
func (rt *reachabilityTree) storeState(dbTx *dbaccess.TxContext) error {
// Flush the store
err := rt.dag.reachabilityTree.store.flushToDB(dbTx)
if err != nil {
return err
}
// Store the reindex root
err = dbaccess.StoreReachabilityReindexRoot(dbTx, rt.reindexRoot.blockNode.hash)
if err != nil {
return err
}
return nil
}
func (rt *reachabilityTree) updateReindexRoot(newTreeNode *reachabilityTreeNode,
modifiedNodes modifiedTreeNodes) error {
nextReindexRoot := rt.reindexRoot
for {
candidateReindexRoot, found, err := rt.maybeMoveReindexRoot(nextReindexRoot, newTreeNode, modifiedNodes)
if err != nil {
return err
}
if !found {
break
}
nextReindexRoot = candidateReindexRoot
}
rt.reindexRoot = nextReindexRoot
return nil
}
func (rt *reachabilityTree) maybeMoveReindexRoot(
reindexRoot *reachabilityTreeNode, newTreeNode *reachabilityTreeNode, modifiedNodes modifiedTreeNodes) (
newReindexRoot *reachabilityTreeNode, found bool, err error) {
if !reindexRoot.isAncestorOf(newTreeNode) {
commonAncestor := newTreeNode.findCommonAncestorWithReindexRoot(reindexRoot)
return commonAncestor, true, nil
}
reindexRootChosenChild, err := reindexRoot.findAncestorAmongChildren(newTreeNode)
if err != nil {
return nil, false, err
}
if newTreeNode.blockNode.blueScore-reindexRootChosenChild.blockNode.blueScore < reachabilityReindexWindow {
return nil, false, nil
}
err = rt.concentrateIntervalAroundReindexRootChosenChild(reindexRoot, reindexRootChosenChild, modifiedNodes)
if err != nil {
return nil, false, err
}
return reindexRootChosenChild, true, nil
}
// findAncestorAmongChildren finds the reachability tree child
// of rtn that is the ancestor of node.
func (rtn *reachabilityTreeNode) findAncestorAmongChildren(node *reachabilityTreeNode) (*reachabilityTreeNode, error) {
ancestor, ok := orderedTreeNodeSet(rtn.children).findAncestorOfNode(node)
if !ok {
return nil, errors.Errorf("rtn is not an ancestor of node")
}
return ancestor, nil
}
func (rt *reachabilityTree) concentrateIntervalAroundReindexRootChosenChild(
reindexRoot *reachabilityTreeNode, reindexRootChosenChild *reachabilityTreeNode,
modifiedNodes modifiedTreeNodes) error {
reindexRootChildNodesBeforeChosen, reindexRootChildNodesAfterChosen, err :=
reindexRoot.splitChildrenAroundChild(reindexRootChosenChild)
if err != nil {
return err
}
reindexRootChildNodesBeforeChosenSizesSum, err :=
rt.tightenIntervalsBeforeReindexRootChosenChild(reindexRoot, reindexRootChildNodesBeforeChosen, modifiedNodes)
if err != nil {
return err
}
reindexRootChildNodesAfterChosenSizesSum, err :=
rt.tightenIntervalsAfterReindexRootChosenChild(reindexRoot, reindexRootChildNodesAfterChosen, modifiedNodes)
if err != nil {
return err
}
err = rt.expandIntervalInReindexRootChosenChild(reindexRoot, reindexRootChosenChild,
reindexRootChildNodesBeforeChosenSizesSum, reindexRootChildNodesAfterChosenSizesSum, modifiedNodes)
if err != nil {
return err
}
return nil
}
// splitChildrenAroundChild splits `rtn` into two slices: the nodes that are before
// `child` and the nodes that are after.
func (rtn *reachabilityTreeNode) splitChildrenAroundChild(child *reachabilityTreeNode) (
nodesBeforeChild []*reachabilityTreeNode, nodesAfterChild []*reachabilityTreeNode, err error) {
for i, candidateChild := range rtn.children {
if candidateChild == child {
return rtn.children[:i], rtn.children[i+1:], nil
}
}
return nil, nil, errors.Errorf("child not a child of rtn")
}
func (rt *reachabilityTree) tightenIntervalsBeforeReindexRootChosenChild(
reindexRoot *reachabilityTreeNode, reindexRootChildNodesBeforeChosen []*reachabilityTreeNode,
modifiedNodes modifiedTreeNodes) (reindexRootChildNodesBeforeChosenSizesSum uint64, err error) {
reindexRootChildNodesBeforeChosenSizes, reindexRootChildNodesBeforeChosenSubtreeSizeMaps, reindexRootChildNodesBeforeChosenSizesSum :=
calcReachabilityTreeNodeSizes(reindexRootChildNodesBeforeChosen)
intervalBeforeReindexRootStart := newReachabilityInterval(
reindexRoot.interval.start+reachabilityReindexSlack,
reindexRoot.interval.start+reachabilityReindexSlack+reindexRootChildNodesBeforeChosenSizesSum-1,
)
err = rt.propagateChildIntervals(intervalBeforeReindexRootStart, reindexRootChildNodesBeforeChosen,
reindexRootChildNodesBeforeChosenSizes, reindexRootChildNodesBeforeChosenSubtreeSizeMaps, modifiedNodes)
if err != nil {
return 0, err
}
return reindexRootChildNodesBeforeChosenSizesSum, nil
}
func (rt *reachabilityTree) tightenIntervalsAfterReindexRootChosenChild(
reindexRoot *reachabilityTreeNode, reindexRootChildNodesAfterChosen []*reachabilityTreeNode,
modifiedNodes modifiedTreeNodes) (reindexRootChildNodesAfterChosenSizesSum uint64, err error) {
reindexRootChildNodesAfterChosenSizes, reindexRootChildNodesAfterChosenSubtreeSizeMaps, reindexRootChildNodesAfterChosenSizesSum :=
calcReachabilityTreeNodeSizes(reindexRootChildNodesAfterChosen)
intervalAfterReindexRootEnd := newReachabilityInterval(
reindexRoot.interval.end-reachabilityReindexSlack-reindexRootChildNodesAfterChosenSizesSum,
reindexRoot.interval.end-reachabilityReindexSlack-1,
)
err = rt.propagateChildIntervals(intervalAfterReindexRootEnd, reindexRootChildNodesAfterChosen,
reindexRootChildNodesAfterChosenSizes, reindexRootChildNodesAfterChosenSubtreeSizeMaps, modifiedNodes)
if err != nil {
return 0, err
}
return reindexRootChildNodesAfterChosenSizesSum, nil
}
func (rt *reachabilityTree) expandIntervalInReindexRootChosenChild(reindexRoot *reachabilityTreeNode,
reindexRootChosenChild *reachabilityTreeNode, reindexRootChildNodesBeforeChosenSizesSum uint64,
reindexRootChildNodesAfterChosenSizesSum uint64, modifiedNodes modifiedTreeNodes) error {
newReindexRootChildInterval := newReachabilityInterval(
reindexRoot.interval.start+reindexRootChildNodesBeforeChosenSizesSum+reachabilityReindexSlack,
reindexRoot.interval.end-reindexRootChildNodesAfterChosenSizesSum-reachabilityReindexSlack-1,
)
if !newReindexRootChildInterval.contains(reindexRootChosenChild.interval) {
// New interval doesn't contain the previous one, propagation is required
// We assign slack on both sides as an optimization. Were we to
// assign a tight interval, the next time the reindex root moves we
// would need to propagate intervals again. That is to say, When we
// DO allocate slack, next time
// expandIntervalInReindexRootChosenChild is called (next time the
// reindex root moves), newReindexRootChildInterval is likely to
// contain reindexRootChosenChild.interval.
reindexRootChosenChild.interval = newReachabilityInterval(
newReindexRootChildInterval.start+reachabilityReindexSlack,
newReindexRootChildInterval.end-reachabilityReindexSlack,
)
err := reindexRootChosenChild.countSubtreesAndPropagateInterval(modifiedNodes)
if err != nil {
return err
}
}
reindexRootChosenChild.interval = newReindexRootChildInterval
modifiedNodes[reindexRootChosenChild] = struct{}{}
return nil
}
func (rtn *reachabilityTreeNode) countSubtreesAndPropagateInterval(modifiedNodes modifiedTreeNodes) error {
subtreeSizeMap := make(map[*reachabilityTreeNode]uint64)
rtn.countSubtrees(subtreeSizeMap)
return rtn.propagateInterval(subtreeSizeMap, modifiedNodes)
}
func calcReachabilityTreeNodeSizes(treeNodes []*reachabilityTreeNode) (
sizes []uint64, subtreeSizeMaps []map[*reachabilityTreeNode]uint64, sum uint64) {
sizes = make([]uint64, len(treeNodes))
subtreeSizeMaps = make([]map[*reachabilityTreeNode]uint64, len(treeNodes))
sum = 0
for i, node := range treeNodes {
subtreeSizeMap := make(map[*reachabilityTreeNode]uint64)
node.countSubtrees(subtreeSizeMap)
subtreeSize := subtreeSizeMap[node]
sizes[i] = subtreeSize
subtreeSizeMaps[i] = subtreeSizeMap
sum += subtreeSize
}
return sizes, subtreeSizeMaps, sum
}
func (rt *reachabilityTree) propagateChildIntervals(interval *reachabilityInterval,
childNodes []*reachabilityTreeNode, sizes []uint64, subtreeSizeMaps []map[*reachabilityTreeNode]uint64,
modifiedNodes modifiedTreeNodes) error {
childIntervalSizes, err := interval.splitExact(sizes)
if err != nil {
return err
}
for i, child := range childNodes {
childInterval := childIntervalSizes[i]
child.interval = childInterval
childSubtreeSizeMap := subtreeSizeMaps[i]
err := child.propagateInterval(childSubtreeSizeMap, modifiedNodes)
if err != nil {
return err
}
}
return nil
}
// isInPast returns true if `this` is in the past of `other`
// in the DAG.
//
// Note: this method will return true if this == other
// The complexity of this method is O(log(|this.futureCoveringTreeNodeSet|))
func (rt *reachabilityTree) isInPast(this *blockNode, other *blockNode) (bool, error) {
// Check if this node is a reachability tree ancestor of the
// other node
isReachabilityTreeAncestor, err := rt.isReachabilityTreeAncestorOf(this, other)
if err != nil {
return false, err
}
if isReachabilityTreeAncestor {
return true, nil
}
// Otherwise, use previously registered future blocks to complete the
// reachability test
thisFutureCoveringSet, err := rt.store.futureCoveringSetByBlockNode(this)
if err != nil {
return false, err
}
otherTreeNode, err := rt.store.treeNodeByBlockNode(other)
if err != nil {
return false, err
}
return thisFutureCoveringSet.hasAncestorOf(otherTreeNode), nil
}
// isReachabilityTreeAncestorOf returns whether `this` is in the selected parent chain of `other`.
//
// Note: this method will return true if this == other
func (rt *reachabilityTree) isReachabilityTreeAncestorOf(this *blockNode, other *blockNode) (bool, error) {
thisTreeNode, err := rt.store.treeNodeByBlockNode(this)
if err != nil {
return false, err
}
otherTreeNode, err := rt.store.treeNodeByBlockNode(other)
if err != nil {
return false, err
}
return thisTreeNode.isAncestorOf(otherTreeNode), nil
}