stasatdaglabs 7b5720a155
Implement GHOST (#1819)
* Implement GHOST.

* Implement TestGHOST.

* Make GHOST() take arbitrary subDAGs.

* Hold RootHashes in SubDAG rather than one GenesisHash.

* Select which root the GHOST chain starts with instead of passing a lowHash.

* If two child hashes have the same future size, decide which one is larger using the block hash.

* Extract blockHashWithLargestFutureSize to a separate function.

* Calculate future size for each block individually.

* Make TestGHOST deterministic.

* Increase the timeout for connecting 128 connections in TestRPCMaxInboundConnections.

* Implement BenchmarkGHOST.

* Fix an infinite loop.

* Use much larger benchmark data.

* Optimize `futureSizes` using reverse merge sets.

* Temporarily make the benchmark data smaller while GHOST is being optimized.

* Fix a bug in futureSizes.

* Fix a bug in populateReverseMergeSet.

* Choose a selectedChild at random instead of the one with the largest reverse merge set size.

* Rename populateReverseMergeSet to calculateReverseMergeSet.

* Use reachability to resolve isDescendantOf.

* Extract heightMaps to a separate object.

* Iterate using height maps in futureSizes.

* Don't store reverse merge sets in memory.

* Change calculateReverseMergeSet to calculateReverseMergeSetSize.

* Fix bad initial reverseMergeSetSize.

* Optimize calculateReverseMergeSetSize.

* Enlarge the benchmark data to 86k blocks.
2021-08-19 13:59:43 +03:00

133 lines
4.0 KiB
Go

package ghost
import (
"github.com/kaspanet/kaspad/domain/consensus/model"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/hashset"
)
// GHOST calculates the GHOST chain for the given `subDAG`
func GHOST(subDAG *model.SubDAG) ([]*externalapi.DomainHash, error) {
futureSizes, err := futureSizes(subDAG)
if err != nil {
return nil, err
}
ghostChain := []*externalapi.DomainHash{}
dagRootHashWithLargestFutureSize := blockHashWithLargestFutureSize(futureSizes, subDAG.RootHashes)
currentHash := dagRootHashWithLargestFutureSize
for {
ghostChain = append(ghostChain, currentHash)
currentBlock := subDAG.Blocks[*currentHash]
childHashes := currentBlock.ChildHashes
if len(childHashes) == 0 {
break
}
childHashWithLargestFutureSize := blockHashWithLargestFutureSize(futureSizes, childHashes)
currentHash = childHashWithLargestFutureSize
}
return ghostChain, nil
}
func blockHashWithLargestFutureSize(futureSizes map[externalapi.DomainHash]uint64,
blockHashes []*externalapi.DomainHash) *externalapi.DomainHash {
var blockHashWithLargestFutureSize *externalapi.DomainHash
largestFutureSize := uint64(0)
for _, blockHash := range blockHashes {
blockFutureSize := futureSizes[*blockHash]
if blockHashWithLargestFutureSize == nil || blockFutureSize > largestFutureSize ||
(blockFutureSize == largestFutureSize && blockHash.Less(blockHashWithLargestFutureSize)) {
largestFutureSize = blockFutureSize
blockHashWithLargestFutureSize = blockHash
}
}
return blockHashWithLargestFutureSize
}
func futureSizes(subDAG *model.SubDAG) (map[externalapi.DomainHash]uint64, error) {
heightMaps := buildHeightMaps(subDAG)
ghostReachabilityManager, err := newGHOSTReachabilityManager(subDAG, heightMaps)
if err != nil {
return nil, err
}
futureSizes := make(map[externalapi.DomainHash]uint64, len(subDAG.Blocks))
height := heightMaps.maxHeight
for {
for _, blockHash := range heightMaps.heightToBlockHashesMap[height] {
block := subDAG.Blocks[*blockHash]
currentBlockReverseMergeSetSize, err := calculateReverseMergeSetSize(subDAG, ghostReachabilityManager, block)
if err != nil {
return nil, err
}
futureSize := currentBlockReverseMergeSetSize
if currentBlockReverseMergeSetSize > 0 {
selectedChild := block.ChildHashes[0]
selectedChildFutureSize := futureSizes[*selectedChild]
futureSize += selectedChildFutureSize
}
futureSizes[*blockHash] = futureSize
}
if height == 0 {
break
}
height--
}
return futureSizes, nil
}
func calculateReverseMergeSetSize(subDAG *model.SubDAG,
ghostReachabilityManager *ghostReachabilityManager, block *model.SubDAGBlock) (uint64, error) {
if len(block.ChildHashes) == 0 {
return 0, nil
}
selectedChild := block.ChildHashes[0]
reverseMergeSetSize := uint64(1)
knownSelectedChildDescendants := hashset.NewFromSlice(selectedChild)
queue := append([]*externalapi.DomainHash{}, block.ChildHashes...)
addedToQueue := hashset.NewFromSlice(block.ChildHashes...)
for len(queue) > 0 {
var currentBlockHash *externalapi.DomainHash
currentBlockHash, queue = queue[0], queue[1:]
currentBlock := subDAG.Blocks[*currentBlockHash]
if knownSelectedChildDescendants.Contains(currentBlockHash) {
for _, childHash := range currentBlock.ChildHashes {
knownSelectedChildDescendants.Add(childHash)
}
continue
}
isCurrentBlockDescendantOfSelectedChild, err := ghostReachabilityManager.isDescendantOf(currentBlockHash, selectedChild)
if err != nil {
return 0, err
}
if isCurrentBlockDescendantOfSelectedChild {
knownSelectedChildDescendants.Add(currentBlockHash)
for _, childHash := range currentBlock.ChildHashes {
knownSelectedChildDescendants.Add(childHash)
}
continue
}
reverseMergeSetSize++
for _, childHash := range currentBlock.ChildHashes {
if addedToQueue.Contains(childHash) {
continue
}
queue = append(queue, childHash)
addedToQueue.Add(childHash)
}
}
return reverseMergeSetSize, nil
}