mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-03-30 15:08:33 +00:00

* [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>
177 lines
6.6 KiB
Go
177 lines
6.6 KiB
Go
package blockdag
|
||
|
||
import (
|
||
"github.com/kaspanet/kaspad/dagconfig"
|
||
"github.com/pkg/errors"
|
||
"sort"
|
||
)
|
||
|
||
// ghostdag runs the GHOSTDAG protocol and updates newNode.blues,
|
||
// newNode.selectedParent and newNode.bluesAnticoneSizes accordingly.
|
||
// The function updates newNode.blues by iterating over the blocks in
|
||
// the anticone of newNode.selectedParent (which is the parent with the
|
||
// highest blue score) and adds any block to newNode.blues if by adding
|
||
// it to newNode.blues these conditions will not be violated:
|
||
//
|
||
// 1) |anticone-of-candidate-block ∩ blue-set-of-newNode| ≤ K
|
||
//
|
||
// 2) For every blue block in blue-set-of-newNode:
|
||
// |(anticone-of-blue-block ∩ blue-set-newNode) ∪ {candidate-block}| ≤ K.
|
||
// We validate this condition by maintaining a map bluesAnticoneSizes for
|
||
// each block which holds all the blue anticone sizes that were affected by
|
||
// the new added blue blocks.
|
||
// So to find out what is |anticone-of-blue ∩ blue-set-of-newNode| we just iterate in
|
||
// the selected parent chain of newNode until we find an existing entry in
|
||
// bluesAnticoneSizes.
|
||
//
|
||
// For further details see the article https://eprint.iacr.org/2018/104.pdf
|
||
func (dag *BlockDAG) ghostdag(newNode *blockNode) (selectedParentAnticone []*blockNode, err error) {
|
||
newNode.selectedParent = newNode.parents.bluest()
|
||
newNode.bluesAnticoneSizes[newNode.selectedParent] = 0
|
||
newNode.blues = []*blockNode{newNode.selectedParent}
|
||
selectedParentAnticone, err = dag.selectedParentAnticone(newNode)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
sort.Slice(selectedParentAnticone, func(i, j int) bool {
|
||
return selectedParentAnticone[i].less(selectedParentAnticone[j])
|
||
})
|
||
|
||
for _, blueCandidate := range selectedParentAnticone {
|
||
candidateBluesAnticoneSizes := make(map[*blockNode]dagconfig.KType)
|
||
var candidateAnticoneSize dagconfig.KType
|
||
possiblyBlue := true
|
||
|
||
// Iterate over all blocks in the blue set of newNode that are not in the past
|
||
// of blueCandidate, and check for each one of them if blueCandidate potentially
|
||
// enlarges their blue anticone to be over K, or that they enlarge the blue anticone
|
||
// of blueCandidate to be over K.
|
||
for chainBlock := newNode; possiblyBlue; chainBlock = chainBlock.selectedParent {
|
||
// If blueCandidate is in the future of chainBlock, it means
|
||
// that all remaining blues are in the past of chainBlock and thus
|
||
// in the past of blueCandidate. In this case we know for sure that
|
||
// the anticone of blueCandidate will not exceed K, and we can mark
|
||
// it as blue.
|
||
//
|
||
// newNode is always in the future of blueCandidate, so there's
|
||
// no point in checking it.
|
||
if chainBlock != newNode {
|
||
if isAncestorOfBlueCandidate, err := dag.isInPast(chainBlock, blueCandidate); err != nil {
|
||
return nil, err
|
||
} else if isAncestorOfBlueCandidate {
|
||
break
|
||
}
|
||
}
|
||
|
||
for _, block := range chainBlock.blues {
|
||
// Skip blocks that exist in the past of blueCandidate.
|
||
if isAncestorOfBlueCandidate, err := dag.isInPast(block, blueCandidate); err != nil {
|
||
return nil, err
|
||
} else if isAncestorOfBlueCandidate {
|
||
continue
|
||
}
|
||
|
||
candidateBluesAnticoneSizes[block], err = dag.blueAnticoneSize(block, newNode)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
candidateAnticoneSize++
|
||
|
||
if candidateAnticoneSize > dag.dagParams.K {
|
||
// k-cluster violation: The candidate's blue anticone exceeded k
|
||
possiblyBlue = false
|
||
break
|
||
}
|
||
|
||
if candidateBluesAnticoneSizes[block] == dag.dagParams.K {
|
||
// k-cluster violation: A block in candidate's blue anticone already
|
||
// has k blue blocks in its own anticone
|
||
possiblyBlue = false
|
||
break
|
||
}
|
||
|
||
// This is a sanity check that validates that a blue
|
||
// block's blue anticone is not already larger than K.
|
||
if candidateBluesAnticoneSizes[block] > dag.dagParams.K {
|
||
return nil, errors.New("found blue anticone size larger than k")
|
||
}
|
||
}
|
||
}
|
||
|
||
if possiblyBlue {
|
||
// No k-cluster violation found, we can now set the candidate block as blue
|
||
newNode.blues = append(newNode.blues, blueCandidate)
|
||
newNode.bluesAnticoneSizes[blueCandidate] = candidateAnticoneSize
|
||
for blue, blueAnticoneSize := range candidateBluesAnticoneSizes {
|
||
newNode.bluesAnticoneSizes[blue] = blueAnticoneSize + 1
|
||
}
|
||
|
||
// The maximum length of node.blues can be K+1 because
|
||
// it contains the selected parent.
|
||
if dagconfig.KType(len(newNode.blues)) == dag.dagParams.K+1 {
|
||
break
|
||
}
|
||
}
|
||
}
|
||
|
||
newNode.blueScore = newNode.selectedParent.blueScore + uint64(len(newNode.blues))
|
||
return selectedParentAnticone, nil
|
||
}
|
||
|
||
// selectedParentAnticone returns the blocks in the anticone of the selected parent of the given node.
|
||
// The function work as follows.
|
||
// We start by adding all parents of the node (other than the selected parent) to a process queue.
|
||
// For each node in the queue:
|
||
// we check whether it is in the past of the selected parent.
|
||
// If not, we add the node to the resulting anticone-set and queue it for processing.
|
||
func (dag *BlockDAG) selectedParentAnticone(node *blockNode) ([]*blockNode, error) {
|
||
anticoneSet := newBlockSet()
|
||
var anticoneSlice []*blockNode
|
||
selectedParentPast := newBlockSet()
|
||
var queue []*blockNode
|
||
// Queueing all parents (other than the selected parent itself) for processing.
|
||
for parent := range node.parents {
|
||
if parent == node.selectedParent {
|
||
continue
|
||
}
|
||
anticoneSet.add(parent)
|
||
anticoneSlice = append(anticoneSlice, parent)
|
||
queue = append(queue, parent)
|
||
}
|
||
for len(queue) > 0 {
|
||
var current *blockNode
|
||
current, queue = queue[0], queue[1:]
|
||
// For each parent of a the current node we check whether it is in the past of the selected parent. If not,
|
||
// we add the it to the resulting anticone-set and queue it for further processing.
|
||
for parent := range current.parents {
|
||
if anticoneSet.contains(parent) || selectedParentPast.contains(parent) {
|
||
continue
|
||
}
|
||
isAncestorOfSelectedParent, err := dag.isInPast(parent, node.selectedParent)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if isAncestorOfSelectedParent {
|
||
selectedParentPast.add(parent)
|
||
continue
|
||
}
|
||
anticoneSet.add(parent)
|
||
anticoneSlice = append(anticoneSlice, parent)
|
||
queue = append(queue, parent)
|
||
}
|
||
}
|
||
return anticoneSlice, nil
|
||
}
|
||
|
||
// blueAnticoneSize returns the blue anticone size of 'block' from the worldview of 'context'.
|
||
// Expects 'block' to be in the blue set of 'context'
|
||
func (dag *BlockDAG) blueAnticoneSize(block, context *blockNode) (dagconfig.KType, error) {
|
||
for current := context; current != nil; current = current.selectedParent {
|
||
if blueAnticoneSize, ok := current.bluesAnticoneSizes[block]; ok {
|
||
return blueAnticoneSize, nil
|
||
}
|
||
}
|
||
return 0, errors.Errorf("block %s is not in blue set of %s", block.hash, context.hash)
|
||
}
|