mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-03-30 15:08:33 +00:00
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.
This commit is contained in:
parent
65b5a080e4
commit
7b5720a155
17
domain/consensus/model/subdag.go
Normal file
17
domain/consensus/model/subdag.go
Normal file
@ -0,0 +1,17 @@
|
||||
package model
|
||||
|
||||
import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
|
||||
// SubDAG represents a context-free representation of a partial DAG
|
||||
type SubDAG struct {
|
||||
RootHashes []*externalapi.DomainHash
|
||||
TipHashes []*externalapi.DomainHash
|
||||
Blocks map[externalapi.DomainHash]*SubDAGBlock
|
||||
}
|
||||
|
||||
// SubDAGBlock represents a block in a SubDAG
|
||||
type SubDAGBlock struct {
|
||||
BlockHash *externalapi.DomainHash
|
||||
ParentHashes []*externalapi.DomainHash
|
||||
ChildHashes []*externalapi.DomainHash
|
||||
}
|
622580
domain/consensus/processes/pruningmanager/ghost/benchmark_data.json
Normal file
622580
domain/consensus/processes/pruningmanager/ghost/benchmark_data.json
Normal file
File diff suppressed because it is too large
Load Diff
132
domain/consensus/processes/pruningmanager/ghost/ghost.go
Normal file
132
domain/consensus/processes/pruningmanager/ghost/ghost.go
Normal file
@ -0,0 +1,132 @@
|
||||
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
|
||||
}
|
296
domain/consensus/processes/pruningmanager/ghost/ghost_test.go
Normal file
296
domain/consensus/processes/pruningmanager/ghost/ghost_test.go
Normal file
@ -0,0 +1,296 @@
|
||||
package ghost
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/domain/consensus"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/testapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/hashset"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/testutils"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGHOST(t *testing.T) {
|
||||
testChain := []struct {
|
||||
parents []string
|
||||
id string
|
||||
expectedGHOSTChain []string
|
||||
}{
|
||||
{
|
||||
parents: []string{"A"},
|
||||
id: "B",
|
||||
expectedGHOSTChain: []string{"A", "B"},
|
||||
},
|
||||
{
|
||||
parents: []string{"B"},
|
||||
id: "C",
|
||||
expectedGHOSTChain: []string{"A", "B", "C"},
|
||||
},
|
||||
{
|
||||
parents: []string{"B"},
|
||||
id: "D",
|
||||
expectedGHOSTChain: []string{"A", "B", "D"},
|
||||
},
|
||||
{
|
||||
parents: []string{"C", "D"},
|
||||
id: "E",
|
||||
expectedGHOSTChain: []string{"A", "B", "D", "E"},
|
||||
},
|
||||
{
|
||||
parents: []string{"C", "D"},
|
||||
id: "F",
|
||||
expectedGHOSTChain: []string{"A", "B", "D", "F"},
|
||||
},
|
||||
{
|
||||
parents: []string{"A"},
|
||||
id: "G",
|
||||
expectedGHOSTChain: []string{"A", "B", "D", "F"},
|
||||
},
|
||||
{
|
||||
parents: []string{"G"},
|
||||
id: "H",
|
||||
expectedGHOSTChain: []string{"A", "B", "D", "F"},
|
||||
},
|
||||
{
|
||||
parents: []string{"H", "F"},
|
||||
id: "I",
|
||||
expectedGHOSTChain: []string{"A", "B", "D", "F", "I"},
|
||||
},
|
||||
{
|
||||
parents: []string{"I"},
|
||||
id: "J",
|
||||
expectedGHOSTChain: []string{"A", "B", "D", "F", "I", "J"},
|
||||
},
|
||||
}
|
||||
|
||||
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
|
||||
factory := consensus.NewFactory()
|
||||
tc, tearDown, err := factory.NewTestConsensus(consensusConfig, "TestBlockWindow")
|
||||
if err != nil {
|
||||
t.Fatalf("NewTestConsensus: %s", err)
|
||||
}
|
||||
defer tearDown(false)
|
||||
|
||||
blockByIDMap := make(map[string]*externalapi.DomainHash)
|
||||
idByBlockMap := make(map[externalapi.DomainHash]string)
|
||||
blockByIDMap["A"] = consensusConfig.GenesisHash
|
||||
idByBlockMap[*consensusConfig.GenesisHash] = "A"
|
||||
mostRecentHash := consensusConfig.GenesisHash
|
||||
|
||||
for _, blockData := range testChain {
|
||||
parents := hashset.New()
|
||||
for _, parentID := range blockData.parents {
|
||||
parent := blockByIDMap[parentID]
|
||||
parents.Add(parent)
|
||||
}
|
||||
|
||||
blockHash := addBlockWithHashSmallerThan(t, tc, parents.ToSlice(), mostRecentHash)
|
||||
if err != nil {
|
||||
t.Fatalf("AddBlock: %+v", err)
|
||||
}
|
||||
blockByIDMap[blockData.id] = blockHash
|
||||
idByBlockMap[*blockHash] = blockData.id
|
||||
mostRecentHash = blockHash
|
||||
|
||||
subDAG := convertDAGtoSubDAG(t, consensusConfig, tc)
|
||||
ghostChainHashes, err := GHOST(subDAG)
|
||||
if err != nil {
|
||||
t.Fatalf("GHOST: %+v", err)
|
||||
}
|
||||
ghostChainIDs := make([]string, len(ghostChainHashes))
|
||||
for i, ghostChainHash := range ghostChainHashes {
|
||||
ghostChainIDs[i] = idByBlockMap[*ghostChainHash]
|
||||
}
|
||||
if !reflect.DeepEqual(ghostChainIDs, blockData.expectedGHOSTChain) {
|
||||
t.Errorf("After adding block ID %s, GHOST chain expected to have IDs %s but got IDs %s",
|
||||
blockData.id, blockData.expectedGHOSTChain, ghostChainIDs)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// addBlockWithHashSmallerThan adds a block to the DAG with the given parents such that its
|
||||
// hash is smaller than `maxHash`. This ensures that the GHOST chain calculated from the
|
||||
// DAG is deterministic
|
||||
func addBlockWithHashSmallerThan(t *testing.T, tc testapi.TestConsensus,
|
||||
parentHashes []*externalapi.DomainHash, maxHash *externalapi.DomainHash) *externalapi.DomainHash {
|
||||
|
||||
var block *externalapi.DomainBlock
|
||||
blockHash := maxHash
|
||||
for maxHash.LessOrEqual(blockHash) {
|
||||
var err error
|
||||
block, _, err = tc.BuildBlockWithParents(parentHashes, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("BuildBlockWithParents: %+v", err)
|
||||
}
|
||||
blockHash = consensushashing.BlockHash(block)
|
||||
}
|
||||
|
||||
_, err := tc.ValidateAndInsertBlock(block, true)
|
||||
if err != nil {
|
||||
t.Fatalf("ValidateAndInsertBlock: %+v", err)
|
||||
}
|
||||
|
||||
return blockHash
|
||||
}
|
||||
|
||||
func convertDAGtoSubDAG(t *testing.T, consensusConfig *consensus.Config, tc testapi.TestConsensus) *model.SubDAG {
|
||||
genesisHash := consensusConfig.GenesisHash
|
||||
|
||||
stagingArea := model.NewStagingArea()
|
||||
tipHashes, err := tc.ConsensusStateStore().Tips(stagingArea, tc.DatabaseContext())
|
||||
if err != nil {
|
||||
t.Fatalf("Tips: %+v", err)
|
||||
}
|
||||
|
||||
subDAG := &model.SubDAG{
|
||||
RootHashes: []*externalapi.DomainHash{genesisHash},
|
||||
TipHashes: tipHashes,
|
||||
Blocks: map[externalapi.DomainHash]*model.SubDAGBlock{},
|
||||
}
|
||||
|
||||
visited := hashset.New()
|
||||
queue := tc.DAGTraversalManager().NewDownHeap(stagingArea)
|
||||
err = queue.PushSlice(tipHashes)
|
||||
if err != nil {
|
||||
t.Fatalf("PushSlice: %+v", err)
|
||||
}
|
||||
for queue.Len() > 0 {
|
||||
blockHash := queue.Pop()
|
||||
visited.Add(blockHash)
|
||||
dagChildHashes, err := tc.DAGTopologyManager().Children(stagingArea, blockHash)
|
||||
if err != nil {
|
||||
t.Fatalf("Children: %+v", err)
|
||||
}
|
||||
childHashes := []*externalapi.DomainHash{}
|
||||
for _, dagChildHash := range dagChildHashes {
|
||||
if dagChildHash.Equal(model.VirtualBlockHash) {
|
||||
continue
|
||||
}
|
||||
childHashes = append(childHashes, dagChildHash)
|
||||
}
|
||||
|
||||
dagParentHashes, err := tc.DAGTopologyManager().Parents(stagingArea, blockHash)
|
||||
if err != nil {
|
||||
t.Fatalf("Parents: %+v", err)
|
||||
}
|
||||
parentHashes := []*externalapi.DomainHash{}
|
||||
for _, dagParentHash := range dagParentHashes {
|
||||
if dagParentHash.Equal(model.VirtualGenesisBlockHash) {
|
||||
continue
|
||||
}
|
||||
parentHashes = append(parentHashes, dagParentHash)
|
||||
if !visited.Contains(dagParentHash) {
|
||||
err := queue.Push(dagParentHash)
|
||||
if err != nil {
|
||||
t.Fatalf("Push: %+v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
subDAG.Blocks[*blockHash] = &model.SubDAGBlock{
|
||||
BlockHash: blockHash,
|
||||
ParentHashes: parentHashes,
|
||||
ChildHashes: childHashes,
|
||||
}
|
||||
}
|
||||
return subDAG
|
||||
}
|
||||
|
||||
type jsonBlock struct {
|
||||
ID string `json:"ID"`
|
||||
Parents []string `json:"Parents"`
|
||||
}
|
||||
|
||||
type testJSON struct {
|
||||
Blocks []*jsonBlock `json:"blocks"`
|
||||
}
|
||||
|
||||
func BenchmarkGHOST(b *testing.B) {
|
||||
b.StopTimer()
|
||||
|
||||
// Load JSON
|
||||
b.Logf("Loading JSON data")
|
||||
jsonFile, err := os.Open("benchmark_data.json")
|
||||
if err != nil {
|
||||
b.Fatalf("Open: %+v", err)
|
||||
}
|
||||
defer jsonFile.Close()
|
||||
|
||||
test := &testJSON{}
|
||||
decoder := json.NewDecoder(jsonFile)
|
||||
decoder.DisallowUnknownFields()
|
||||
err = decoder.Decode(&test)
|
||||
if err != nil {
|
||||
b.Fatalf("Decode: %+v", err)
|
||||
}
|
||||
|
||||
// Convert JSON data to a SubDAG
|
||||
b.Logf("Converting JSON data to SubDAG")
|
||||
subDAG := &model.SubDAG{
|
||||
RootHashes: []*externalapi.DomainHash{},
|
||||
TipHashes: []*externalapi.DomainHash{},
|
||||
Blocks: make(map[externalapi.DomainHash]*model.SubDAGBlock, len(test.Blocks)),
|
||||
}
|
||||
blockIDToHash := func(blockID string) *externalapi.DomainHash {
|
||||
blockHashHex := fmt.Sprintf("%064s", blockID)
|
||||
blockHash, err := externalapi.NewDomainHashFromString(blockHashHex)
|
||||
if err != nil {
|
||||
b.Fatalf("NewDomainHashFromString: %+v", err)
|
||||
}
|
||||
return blockHash
|
||||
}
|
||||
for _, block := range test.Blocks {
|
||||
blockHash := blockIDToHash(block.ID)
|
||||
|
||||
parentHashes := []*externalapi.DomainHash{}
|
||||
for _, parentID := range block.Parents {
|
||||
parentHash := blockIDToHash(parentID)
|
||||
parentHashes = append(parentHashes, parentHash)
|
||||
}
|
||||
|
||||
subDAG.Blocks[*blockHash] = &model.SubDAGBlock{
|
||||
BlockHash: blockHash,
|
||||
ParentHashes: parentHashes,
|
||||
ChildHashes: []*externalapi.DomainHash{},
|
||||
}
|
||||
}
|
||||
for _, block := range subDAG.Blocks {
|
||||
for _, parentHash := range block.ParentHashes {
|
||||
parentBlock := subDAG.Blocks[*parentHash]
|
||||
parentAlreadyHasBlockAsChild := false
|
||||
for _, childHash := range parentBlock.ChildHashes {
|
||||
if block.BlockHash.Equal(childHash) {
|
||||
parentAlreadyHasBlockAsChild = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !parentAlreadyHasBlockAsChild {
|
||||
parentBlock.ChildHashes = append(parentBlock.ChildHashes, block.BlockHash)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, block := range subDAG.Blocks {
|
||||
if len(block.ParentHashes) == 0 {
|
||||
subDAG.RootHashes = append(subDAG.RootHashes, block.BlockHash)
|
||||
}
|
||||
if len(block.ChildHashes) == 0 {
|
||||
subDAG.TipHashes = append(subDAG.TipHashes, block.BlockHash)
|
||||
}
|
||||
}
|
||||
|
||||
b.Logf("Running benchmark")
|
||||
b.ResetTimer()
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := GHOST(subDAG)
|
||||
if err != nil {
|
||||
b.Fatalf("GHOST: %+v", err)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
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"
|
||||
)
|
||||
|
||||
type heightMaps struct {
|
||||
blockHashToHeightMap map[externalapi.DomainHash]uint64
|
||||
heightToBlockHashesMap map[uint64][]*externalapi.DomainHash
|
||||
maxHeight uint64
|
||||
}
|
||||
|
||||
func buildHeightMaps(subDAG *model.SubDAG) *heightMaps {
|
||||
blockHashToHeightMap := make(map[externalapi.DomainHash]uint64, len(subDAG.Blocks))
|
||||
heightToBlockHashesMap := make(map[uint64][]*externalapi.DomainHash)
|
||||
maxHeight := uint64(0)
|
||||
|
||||
queue := append([]*externalapi.DomainHash{}, subDAG.RootHashes...)
|
||||
addedToQueue := hashset.NewFromSlice(subDAG.RootHashes...)
|
||||
for len(queue) > 0 {
|
||||
var currentBlockHash *externalapi.DomainHash
|
||||
currentBlockHash, queue = queue[0], queue[1:]
|
||||
|
||||
// Send the block to the back of the queue if one or more of its parents had not been processed yet
|
||||
currentBlock := subDAG.Blocks[*currentBlockHash]
|
||||
hasMissingParentData := false
|
||||
for _, parentHash := range currentBlock.ParentHashes {
|
||||
if _, ok := blockHashToHeightMap[*parentHash]; !ok {
|
||||
hasMissingParentData = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
if hasMissingParentData {
|
||||
queue = append(queue, currentBlockHash)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, childHash := range currentBlock.ChildHashes {
|
||||
if addedToQueue.Contains(childHash) {
|
||||
continue
|
||||
}
|
||||
queue = append(queue, childHash)
|
||||
addedToQueue.Add(childHash)
|
||||
}
|
||||
|
||||
currentBlockHeight := uint64(0)
|
||||
if len(currentBlock.ParentHashes) > 0 {
|
||||
highestParentHeight := uint64(0)
|
||||
for _, parentHash := range currentBlock.ParentHashes {
|
||||
parentHeight := blockHashToHeightMap[*parentHash]
|
||||
if parentHeight > highestParentHeight {
|
||||
highestParentHeight = parentHeight
|
||||
}
|
||||
}
|
||||
currentBlockHeight = highestParentHeight + 1
|
||||
}
|
||||
blockHashToHeightMap[*currentBlockHash] = currentBlockHeight
|
||||
|
||||
if _, ok := heightToBlockHashesMap[currentBlockHeight]; !ok {
|
||||
heightToBlockHashesMap[currentBlockHeight] = []*externalapi.DomainHash{}
|
||||
}
|
||||
heightToBlockHashesMap[currentBlockHeight] = append(heightToBlockHashesMap[currentBlockHeight], currentBlockHash)
|
||||
|
||||
if currentBlockHeight > maxHeight {
|
||||
maxHeight = currentBlockHeight
|
||||
}
|
||||
}
|
||||
return &heightMaps{
|
||||
blockHashToHeightMap: blockHashToHeightMap,
|
||||
heightToBlockHashesMap: heightToBlockHashesMap,
|
||||
maxHeight: maxHeight,
|
||||
}
|
||||
}
|
144
domain/consensus/processes/pruningmanager/ghost/reachability.go
Normal file
144
domain/consensus/processes/pruningmanager/ghost/reachability.go
Normal file
@ -0,0 +1,144 @@
|
||||
package ghost
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/processes/reachabilitymanager"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type ghostReachabilityManager struct {
|
||||
ghostdagDataStore *ghostdagDataStore
|
||||
reachabilityDataStore *reachabilityDataStore
|
||||
reachabilityManager model.ReachabilityManager
|
||||
}
|
||||
|
||||
type ghostdagDataStore struct {
|
||||
blockGHOSTDAGData map[externalapi.DomainHash]*externalapi.BlockGHOSTDAGData
|
||||
}
|
||||
|
||||
func newGHOSTDAGDataStore() *ghostdagDataStore {
|
||||
return &ghostdagDataStore{
|
||||
blockGHOSTDAGData: map[externalapi.DomainHash]*externalapi.BlockGHOSTDAGData{},
|
||||
}
|
||||
}
|
||||
|
||||
func (gds *ghostdagDataStore) Stage(_ *model.StagingArea, blockHash *externalapi.DomainHash,
|
||||
blockGHOSTDAGData *externalapi.BlockGHOSTDAGData, _ bool) {
|
||||
|
||||
gds.blockGHOSTDAGData[*blockHash] = blockGHOSTDAGData
|
||||
}
|
||||
|
||||
func (gds *ghostdagDataStore) IsStaged(_ *model.StagingArea) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (gds *ghostdagDataStore) Get(_ model.DBReader, _ *model.StagingArea,
|
||||
blockHash *externalapi.DomainHash, _ bool) (*externalapi.BlockGHOSTDAGData, error) {
|
||||
|
||||
blockGHOSTDAGData, ok := gds.blockGHOSTDAGData[*blockHash]
|
||||
if !ok {
|
||||
return nil, errors.Errorf("ghostdag data not found for block hash %s", blockHash)
|
||||
}
|
||||
return blockGHOSTDAGData, nil
|
||||
}
|
||||
|
||||
type reachabilityDataStore struct {
|
||||
reachabilityData map[externalapi.DomainHash]model.ReachabilityData
|
||||
reachabilityReindexRoot *externalapi.DomainHash
|
||||
}
|
||||
|
||||
func newReachabilityDataStore() *reachabilityDataStore {
|
||||
return &reachabilityDataStore{
|
||||
reachabilityData: map[externalapi.DomainHash]model.ReachabilityData{},
|
||||
reachabilityReindexRoot: nil,
|
||||
}
|
||||
}
|
||||
|
||||
func (rds *reachabilityDataStore) StageReachabilityData(_ *model.StagingArea,
|
||||
blockHash *externalapi.DomainHash, reachabilityData model.ReachabilityData) {
|
||||
|
||||
rds.reachabilityData[*blockHash] = reachabilityData
|
||||
}
|
||||
|
||||
func (rds *reachabilityDataStore) StageReachabilityReindexRoot(_ *model.StagingArea,
|
||||
reachabilityReindexRoot *externalapi.DomainHash) {
|
||||
|
||||
rds.reachabilityReindexRoot = reachabilityReindexRoot
|
||||
}
|
||||
|
||||
func (rds *reachabilityDataStore) IsStaged(_ *model.StagingArea) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (rds *reachabilityDataStore) ReachabilityData(_ model.DBReader, _ *model.StagingArea,
|
||||
blockHash *externalapi.DomainHash) (model.ReachabilityData, error) {
|
||||
|
||||
reachabilityData, ok := rds.reachabilityData[*blockHash]
|
||||
if !ok {
|
||||
return nil, errors.Errorf("reachability data not found for block hash %s", blockHash)
|
||||
}
|
||||
return reachabilityData, nil
|
||||
}
|
||||
|
||||
func (rds *reachabilityDataStore) HasReachabilityData(_ model.DBReader, _ *model.StagingArea,
|
||||
blockHash *externalapi.DomainHash) (bool, error) {
|
||||
|
||||
_, ok := rds.reachabilityData[*blockHash]
|
||||
return ok, nil
|
||||
}
|
||||
|
||||
func (rds *reachabilityDataStore) ReachabilityReindexRoot(_ model.DBReader,
|
||||
_ *model.StagingArea) (*externalapi.DomainHash, error) {
|
||||
|
||||
return rds.reachabilityReindexRoot, nil
|
||||
}
|
||||
|
||||
func newGHOSTReachabilityManager(subDAG *model.SubDAG, heightMaps *heightMaps) (*ghostReachabilityManager, error) {
|
||||
ghostdagDataStore := newGHOSTDAGDataStore()
|
||||
reachabilityDataStore := newReachabilityDataStore()
|
||||
reachabilityManager := reachabilitymanager.New(nil, ghostdagDataStore, reachabilityDataStore)
|
||||
|
||||
ghostReachabilityManager := &ghostReachabilityManager{
|
||||
ghostdagDataStore: ghostdagDataStore,
|
||||
reachabilityDataStore: reachabilityDataStore,
|
||||
reachabilityManager: reachabilityManager,
|
||||
}
|
||||
err := ghostReachabilityManager.initialize(subDAG, heightMaps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ghostReachabilityManager, nil
|
||||
}
|
||||
|
||||
func (grm *ghostReachabilityManager) initialize(subDAG *model.SubDAG, heightMaps *heightMaps) error {
|
||||
for blockHash, block := range subDAG.Blocks {
|
||||
blockHeight := heightMaps.blockHashToHeightMap[blockHash]
|
||||
selectedParent := model.VirtualGenesisBlockHash
|
||||
if len(block.ParentHashes) > 0 {
|
||||
selectedParent = block.ParentHashes[0]
|
||||
}
|
||||
blockGHOSTDAGData := externalapi.NewBlockGHOSTDAGData(blockHeight, nil, selectedParent, nil, nil, nil)
|
||||
grm.ghostdagDataStore.Stage(nil, &blockHash, blockGHOSTDAGData, false)
|
||||
}
|
||||
|
||||
err := grm.reachabilityManager.Init(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for height := uint64(0); height <= heightMaps.maxHeight; height++ {
|
||||
for _, blockHash := range heightMaps.heightToBlockHashesMap[height] {
|
||||
err := grm.reachabilityManager.AddBlock(nil, blockHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (grm *ghostReachabilityManager) isDescendantOf(blockAHash *externalapi.DomainHash, blockBHash *externalapi.DomainHash) (bool, error) {
|
||||
return grm.reachabilityManager.IsDAGAncestorOf(nil, blockBHash, blockAHash)
|
||||
}
|
@ -59,7 +59,7 @@ func TestRPCMaxInboundConnections(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("newTestRPCClient: %s", err)
|
||||
}
|
||||
case <-time.After(time.Second):
|
||||
case <-time.After(time.Second * 5):
|
||||
t.Fatalf("Timeout for connecting %d RPC connections elapsed", grpcserver.RPCMaxInboundConnections)
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user