mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-10-14 00:59:33 +00:00
Make antiPastHashesBetween return blocks sorted in ghostdag-order (#1536)
* Make antiPastHashesBetween return blocks sorted in ghostdag-order * Return sortedMergeSet instead of blueMergeSet * Invert the order of parameters of IsAncestorOf * Add RenderDAGToDot to TestConsensus * Add HighHash explicitly, unless lowHash == highHash * Use Equal instead of == when comparing hashes * Fixed TestSyncManager_GetHashesBetween * Fix tests * findHighHashAccordingToMaxBlueScoreDifference: don't start looking if the whole thing fits * Handle a missed error * Remove redundant call to RenderToDot * Fix bug in findHighHashAccordingToMaxBlueScoreDifference
This commit is contained in:
parent
00a023620d
commit
995e526dae
@ -1,6 +1,10 @@
|
|||||||
package rpchandlers_test
|
package rpchandlers_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"testing"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/app/appmessage"
|
"github.com/kaspanet/kaspad/app/appmessage"
|
||||||
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
|
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
|
||||||
"github.com/kaspanet/kaspad/app/rpc/rpchandlers"
|
"github.com/kaspanet/kaspad/app/rpc/rpchandlers"
|
||||||
@ -12,9 +16,6 @@ import (
|
|||||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||||
"github.com/kaspanet/kaspad/domain/miningmanager"
|
"github.com/kaspanet/kaspad/domain/miningmanager"
|
||||||
"github.com/kaspanet/kaspad/infrastructure/config"
|
"github.com/kaspanet/kaspad/infrastructure/config"
|
||||||
"reflect"
|
|
||||||
"sort"
|
|
||||||
"testing"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type fakeDomain struct {
|
type fakeDomain struct {
|
||||||
@ -65,66 +66,79 @@ func TestHandleGetBlocks(t *testing.T) {
|
|||||||
return antipast
|
return antipast
|
||||||
}
|
}
|
||||||
|
|
||||||
upBfsOrder := make([]*externalapi.DomainHash, 0, 30)
|
// Create a DAG with the following structure:
|
||||||
selectedParent := params.GenesisHash
|
// merging block
|
||||||
upBfsOrder = append(upBfsOrder, selectedParent)
|
// / | \
|
||||||
|
// split1 split2 split3
|
||||||
|
// \ | /
|
||||||
|
// merging block
|
||||||
|
// / | \
|
||||||
|
// split1 split2 split3
|
||||||
|
// \ | /
|
||||||
|
// etc.
|
||||||
|
expectedOrder := make([]*externalapi.DomainHash, 0, 40)
|
||||||
|
mergingBlock := params.GenesisHash
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
parents := make([]*externalapi.DomainHash, 0, 3)
|
splitBlocks := make([]*externalapi.DomainHash, 0, 3)
|
||||||
for j := 0; j < 4; j++ {
|
for j := 0; j < 3; j++ {
|
||||||
blockHash, _, err := tc.AddBlock([]*externalapi.DomainHash{selectedParent}, nil, nil)
|
blockHash, _, err := tc.AddBlock([]*externalapi.DomainHash{mergingBlock}, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed adding block: %v", err)
|
t.Fatalf("Failed adding block: %v", err)
|
||||||
}
|
}
|
||||||
parents = append(parents, blockHash)
|
splitBlocks = append(splitBlocks, blockHash)
|
||||||
upBfsOrder = append(upBfsOrder, blockHash)
|
|
||||||
}
|
}
|
||||||
selectedParent, _, err = tc.AddBlock(parents, nil, nil)
|
sort.Sort(sort.Reverse(testutils.NewTestGhostDAGSorter(splitBlocks, tc, t)))
|
||||||
|
restOfSplitBlocks, selectedParent := splitBlocks[:len(splitBlocks)-1], splitBlocks[len(splitBlocks)-1]
|
||||||
|
expectedOrder = append(expectedOrder, selectedParent)
|
||||||
|
expectedOrder = append(expectedOrder, restOfSplitBlocks...)
|
||||||
|
|
||||||
|
mergingBlock, _, err = tc.AddBlock(splitBlocks, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed adding block: %v", err)
|
t.Fatalf("Failed adding block: %v", err)
|
||||||
}
|
}
|
||||||
upBfsOrder = append(upBfsOrder, selectedParent)
|
expectedOrder = append(expectedOrder, mergingBlock)
|
||||||
}
|
}
|
||||||
|
|
||||||
virtualSelectedParent, err := tc.GetVirtualSelectedParent()
|
virtualSelectedParent, err := tc.GetVirtualSelectedParent()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed getting SelectedParent: %v", err)
|
t.Fatalf("Failed getting SelectedParent: %v", err)
|
||||||
}
|
}
|
||||||
if !virtualSelectedParent.Equal(upBfsOrder[len(upBfsOrder)-1]) {
|
if !virtualSelectedParent.Equal(expectedOrder[len(expectedOrder)-1]) {
|
||||||
t.Fatalf("Expected %s to be selectedParent, instead found: %s", upBfsOrder[len(upBfsOrder)-1], virtualSelectedParent)
|
t.Fatalf("Expected %s to be selectedParent, instead found: %s", expectedOrder[len(expectedOrder)-1], virtualSelectedParent)
|
||||||
}
|
}
|
||||||
|
|
||||||
requestSelectedParent := getBlocks(virtualSelectedParent)
|
requestSelectedParent := getBlocks(virtualSelectedParent)
|
||||||
if !reflect.DeepEqual(requestSelectedParent.BlockHashes, hashes.ToStrings([]*externalapi.DomainHash{virtualSelectedParent})) {
|
if !reflect.DeepEqual(requestSelectedParent.BlockHashes, hashes.ToStrings([]*externalapi.DomainHash{virtualSelectedParent})) {
|
||||||
t.Fatalf("TestSyncManager_GetHashesBetween expected %v\n == \n%v", requestSelectedParent.BlockHashes, virtualSelectedParent)
|
t.Fatalf("TestHandleGetBlocks expected:\n%v\nactual:\n%v", virtualSelectedParent, requestSelectedParent.BlockHashes)
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, blockHash := range upBfsOrder {
|
for i, blockHash := range expectedOrder {
|
||||||
expectedBlocks := filterAntiPast(blockHash, upBfsOrder)
|
expectedBlocks := filterAntiPast(blockHash, expectedOrder)
|
||||||
// sort the slice in the order GetBlocks is returning them
|
|
||||||
sort.Sort(sort.Reverse(testutils.NewTestGhostDAGSorter(expectedBlocks, tc, t)))
|
|
||||||
expectedBlocks = append([]*externalapi.DomainHash{blockHash}, expectedBlocks...)
|
expectedBlocks = append([]*externalapi.DomainHash{blockHash}, expectedBlocks...)
|
||||||
|
|
||||||
blocks := getBlocks(blockHash)
|
actualBlocks := getBlocks(blockHash)
|
||||||
if !reflect.DeepEqual(blocks.BlockHashes, hashes.ToStrings(expectedBlocks)) {
|
if !reflect.DeepEqual(actualBlocks.BlockHashes, hashes.ToStrings(expectedBlocks)) {
|
||||||
t.Fatalf("TestSyncManager_GetHashesBetween %d expected %s\n == \n%s", i, blocks.BlockHashes, hashes.ToStrings(expectedBlocks))
|
t.Fatalf("TestHandleGetBlocks %d \nexpected: \n%v\nactual:\n%v", i,
|
||||||
|
hashes.ToStrings(expectedBlocks), actualBlocks.BlockHashes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make explitly sure that if lowHash==highHash we get a slice with a single hash.
|
// Make explicitly sure that if lowHash==highHash we get a slice with a single hash.
|
||||||
blocks := getBlocks(virtualSelectedParent)
|
actualBlocks := getBlocks(virtualSelectedParent)
|
||||||
if !reflect.DeepEqual(blocks.BlockHashes, []string{virtualSelectedParent.String()}) {
|
if !reflect.DeepEqual(actualBlocks.BlockHashes, []string{virtualSelectedParent.String()}) {
|
||||||
t.Fatalf("TestSyncManager_GetHashesBetween expected blocks to contain just '%s', instead got: \n%s", virtualSelectedParent, blocks.BlockHashes)
|
t.Fatalf("TestHandleGetBlocks expected blocks to contain just '%s', instead got: \n%v",
|
||||||
|
virtualSelectedParent, actualBlocks.BlockHashes)
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Sort(sort.Reverse(testutils.NewTestGhostDAGSorter(upBfsOrder, tc, t)))
|
expectedOrder = append([]*externalapi.DomainHash{params.GenesisHash}, expectedOrder...)
|
||||||
requestAllViaNil := getBlocks(nil)
|
actualOrder := getBlocks(nil)
|
||||||
if !reflect.DeepEqual(requestAllViaNil.BlockHashes, hashes.ToStrings(upBfsOrder)) {
|
if !reflect.DeepEqual(actualOrder.BlockHashes, hashes.ToStrings(expectedOrder)) {
|
||||||
t.Fatalf("TestSyncManager_GetHashesBetween expected %v\n == \n%v", requestAllViaNil.BlockHashes, upBfsOrder)
|
t.Fatalf("TestHandleGetBlocks \nexpected: %v \nactual:\n%v", expectedOrder, actualOrder.BlockHashes)
|
||||||
}
|
}
|
||||||
|
|
||||||
requestAllExplictly := getBlocks(params.GenesisHash)
|
requestAllExplictly := getBlocks(params.GenesisHash)
|
||||||
if !reflect.DeepEqual(requestAllExplictly.BlockHashes, hashes.ToStrings(upBfsOrder)) {
|
if !reflect.DeepEqual(requestAllExplictly.BlockHashes, hashes.ToStrings(expectedOrder)) {
|
||||||
t.Fatalf("TestSyncManager_GetHashesBetween expected %v\n == \n%v", requestAllExplictly.BlockHashes, upBfsOrder)
|
t.Fatalf("TestHandleGetBlocks \nexpected: \n%v\n. actual:\n%v", expectedOrder, requestAllExplictly.BlockHashes)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@ package consensus
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
"github.com/kaspanet/kaspad/infrastructure/logger"
|
||||||
|
"github.com/kaspanet/kaspad/util/panics"
|
||||||
)
|
)
|
||||||
|
|
||||||
var log, _ = logger.Get(logger.SubsystemTags.BDAG)
|
var log, _ = logger.Get(logger.SubsystemTags.BDAG)
|
||||||
|
var spawn = panics.GoroutineWrapperFunc(log)
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
package testapi
|
package testapi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||||
"github.com/kaspanet/kaspad/infrastructure/db/database"
|
"github.com/kaspanet/kaspad/infrastructure/db/database"
|
||||||
"io"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// MineJSONBlockType indicates which type of blocks MineJSON mines
|
// MineJSONBlockType indicates which type of blocks MineJSON mines
|
||||||
@ -50,6 +51,8 @@ type TestConsensus interface {
|
|||||||
MineJSON(r io.Reader, blockType MineJSONBlockType) (tips []*externalapi.DomainHash, err error)
|
MineJSON(r io.Reader, blockType MineJSONBlockType) (tips []*externalapi.DomainHash, err error)
|
||||||
DiscardAllStores()
|
DiscardAllStores()
|
||||||
|
|
||||||
|
RenderDAGToDot(filename string) error
|
||||||
|
|
||||||
AcceptanceDataStore() model.AcceptanceDataStore
|
AcceptanceDataStore() model.AcceptanceDataStore
|
||||||
BlockHeaderStore() model.BlockHeaderStore
|
BlockHeaderStore() model.BlockHeaderStore
|
||||||
BlockRelationStore() model.BlockRelationStore
|
BlockRelationStore() model.BlockRelationStore
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
package syncmanager
|
package syncmanager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/utils/hashset"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// antiPastHashesBetween returns the hashes of the blocks between the
|
// antiPastHashesBetween returns the hashes of the blocks between the
|
||||||
// lowHash's antiPast and highHash's antiPast, or up to
|
// lowHash's antiPast and highHash's antiPast, or up to
|
||||||
// `maxBlueScoreDifference`, if non-zero.
|
// `maxBlueScoreDifference`, if non-zero.
|
||||||
|
// The result excludes lowHash and includes highHash. If lowHash == highHash, returns nothing.
|
||||||
func (sm *syncManager) antiPastHashesBetween(lowHash, highHash *externalapi.DomainHash,
|
func (sm *syncManager) antiPastHashesBetween(lowHash, highHash *externalapi.DomainHash,
|
||||||
maxBlueScoreDifference uint64) ([]*externalapi.DomainHash, error) {
|
maxBlueScoreDifference uint64) ([]*externalapi.DomainHash, error) {
|
||||||
|
|
||||||
@ -17,19 +18,10 @@ func (sm *syncManager) antiPastHashesBetween(lowHash, highHash *externalapi.Doma
|
|||||||
// highHash's selectedParentChain.
|
// highHash's selectedParentChain.
|
||||||
// We keep originalLowHash to filter out blocks in it's past later down the road
|
// We keep originalLowHash to filter out blocks in it's past later down the road
|
||||||
originalLowHash := lowHash
|
originalLowHash := lowHash
|
||||||
for {
|
var err error
|
||||||
isInSelectedParentChain, err := sm.dagTopologyManager.IsInSelectedParentChainOf(lowHash, highHash)
|
lowHash, err = sm.findLowHashInHighHashSelectedParentChain(lowHash, highHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
|
||||||
if isInSelectedParentChain {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
lowBlockGHOSTDAGData, err := sm.ghostdagDataStore.Get(sm.databaseContext, lowHash)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
lowHash = lowBlockGHOSTDAGData.SelectedParent()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lowBlockGHOSTDAGData, err := sm.ghostdagDataStore.Get(sm.databaseContext, lowHash)
|
lowBlockGHOSTDAGData, err := sm.ghostdagDataStore.Get(sm.databaseContext, lowHash)
|
||||||
@ -55,78 +47,138 @@ func (sm *syncManager) antiPastHashesBetween(lowHash, highHash *externalapi.Doma
|
|||||||
// Using blueScore as an approximation is considered to be
|
// Using blueScore as an approximation is considered to be
|
||||||
// fairly accurate because we presume that most DAG blocks are
|
// fairly accurate because we presume that most DAG blocks are
|
||||||
// blue.
|
// blue.
|
||||||
iterator, err := sm.dagTraversalManager.SelectedChildIterator(highHash, lowHash)
|
highHash, err = sm.findHighHashAccordingToMaxBlueScoreDifference(lowHash, highHash, maxBlueScoreDifference, highBlockGHOSTDAGData, lowBlockGHOSTDAGData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for ok := iterator.First(); ok; ok = iterator.Next() {
|
|
||||||
highHash, err = iterator.Get()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
highBlockGHOSTDAGData, err = sm.ghostdagDataStore.Get(sm.databaseContext, highHash)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if highBlockGHOSTDAGData.BlueScore()-lowBlockGHOSTDAGData.BlueScore()+1 > maxBlueScoreDifference {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect every node in highHash's past (including itself) but
|
// Collect all hashes by concatenating the merge-sets of all blocks between highHash and lowHash
|
||||||
// NOT in the lowHash's past (excluding itself) into an up-heap
|
blockHashes := []*externalapi.DomainHash{}
|
||||||
// (a heap sorted by blueScore from lowest to greatest).
|
iterator, err := sm.dagTraversalManager.SelectedChildIterator(highHash, lowHash)
|
||||||
visited := hashset.New()
|
|
||||||
hashesUpHeap := sm.dagTraversalManager.NewUpHeap()
|
|
||||||
queue := sm.dagTraversalManager.NewDownHeap()
|
|
||||||
err = queue.Push(highHash)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for queue.Len() > 0 {
|
for ok := iterator.First(); ok; ok = iterator.Next() {
|
||||||
current := queue.Pop()
|
current, err := iterator.Get()
|
||||||
if visited.Contains(current) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
visited.Add(current)
|
|
||||||
var isCurrentAncestorOfLowHash bool
|
|
||||||
if current == lowHash {
|
|
||||||
isCurrentAncestorOfLowHash = false
|
|
||||||
} else {
|
|
||||||
var err error
|
|
||||||
isCurrentAncestorOfLowHash, err = sm.dagTopologyManager.IsAncestorOf(current, lowHash)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if isCurrentAncestorOfLowHash {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Push current to hashesUpHeap if it's not in the past of originalLowHash
|
|
||||||
isInPastOfOriginalLowHash, err := sm.dagTopologyManager.IsAncestorOf(current, originalLowHash)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !isInPastOfOriginalLowHash {
|
// Both blue and red merge sets are topologically sorted, but not the concatenation of the two.
|
||||||
err = hashesUpHeap.Push(current)
|
// We require the blocks to be topologically sorted. In addition, for optimal performance,
|
||||||
if err != nil {
|
// we want the selectedParent to be first.
|
||||||
return nil, err
|
// Since the rest of the merge set is in the anticone of selectedParent, it's position in the list does not
|
||||||
}
|
// matter, even though it's blue score is the highest, we can arbitrarily decide it comes first.
|
||||||
}
|
// Therefore we first append the selectedParent, then the rest of blocks in ghostdag order.
|
||||||
parents, err := sm.dagTopologyManager.Parents(current)
|
sortedMergeSet, err := sm.getSortedMergeSet(current)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, parent := range parents {
|
|
||||||
err := queue.Push(parent)
|
// append to blockHashes all blocks in sortedMergeSet which are not in the past of originalLowHash
|
||||||
|
for _, blockHash := range sortedMergeSet {
|
||||||
|
isInPastOfOriginalLowHash, err := sm.dagTopologyManager.IsAncestorOf(blockHash, originalLowHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if isInPastOfOriginalLowHash {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
blockHashes = append(blockHashes, blockHash)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return hashesUpHeap.ToSlice(), nil
|
// The process above doesn't return highHash, so include it explicitly, unless highHash == lowHash
|
||||||
|
if !lowHash.Equal(highHash) {
|
||||||
|
blockHashes = append(blockHashes, highHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
return blockHashes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sm *syncManager) getSortedMergeSet(current *externalapi.DomainHash) ([]*externalapi.DomainHash, error) {
|
||||||
|
currentGhostdagData, err := sm.ghostdagDataStore.Get(sm.databaseContext, current)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
blueMergeSet := currentGhostdagData.MergeSetBlues()
|
||||||
|
redMergeSet := currentGhostdagData.MergeSetReds()
|
||||||
|
sortedMergeSet := make([]*externalapi.DomainHash, 0, len(blueMergeSet)+len(redMergeSet))
|
||||||
|
selectedParent, blueMergeSet := blueMergeSet[0], blueMergeSet[1:]
|
||||||
|
sortedMergeSet = append(sortedMergeSet, selectedParent)
|
||||||
|
i, j := 0, 0
|
||||||
|
for i < len(blueMergeSet) && j < len(redMergeSet) {
|
||||||
|
currentBlue := blueMergeSet[i]
|
||||||
|
currentBlueGhostdagData, err := sm.ghostdagDataStore.Get(sm.databaseContext, currentBlue)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
currentRed := redMergeSet[j]
|
||||||
|
currentRedGhostdagData, err := sm.ghostdagDataStore.Get(sm.databaseContext, currentRed)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if sm.ghostdagManager.Less(currentBlue, currentBlueGhostdagData, currentRed, currentRedGhostdagData) {
|
||||||
|
sortedMergeSet = append(sortedMergeSet, currentBlue)
|
||||||
|
i++
|
||||||
|
} else {
|
||||||
|
sortedMergeSet = append(sortedMergeSet, currentRed)
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sortedMergeSet = append(sortedMergeSet, blueMergeSet[i:]...)
|
||||||
|
sortedMergeSet = append(sortedMergeSet, redMergeSet[j:]...)
|
||||||
|
|
||||||
|
return sortedMergeSet, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sm *syncManager) findHighHashAccordingToMaxBlueScoreDifference(lowHash *externalapi.DomainHash,
|
||||||
|
highHash *externalapi.DomainHash, maxBlueScoreDifference uint64, highBlockGHOSTDAGData *model.BlockGHOSTDAGData,
|
||||||
|
lowBlockGHOSTDAGData *model.BlockGHOSTDAGData) (*externalapi.DomainHash, error) {
|
||||||
|
|
||||||
|
if highBlockGHOSTDAGData.BlueScore()-lowBlockGHOSTDAGData.BlueScore() <= maxBlueScoreDifference {
|
||||||
|
return highHash, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator, err := sm.dagTraversalManager.SelectedChildIterator(highHash, lowHash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for ok := iterator.First(); ok; ok = iterator.Next() {
|
||||||
|
highHashCandidate, err := iterator.Get()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
highBlockGHOSTDAGData, err = sm.ghostdagDataStore.Get(sm.databaseContext, highHashCandidate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if highBlockGHOSTDAGData.BlueScore()-lowBlockGHOSTDAGData.BlueScore() > maxBlueScoreDifference {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
highHash = highHashCandidate
|
||||||
|
}
|
||||||
|
return highHash, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sm *syncManager) findLowHashInHighHashSelectedParentChain(
|
||||||
|
lowHash *externalapi.DomainHash, highHash *externalapi.DomainHash) (*externalapi.DomainHash, error) {
|
||||||
|
for {
|
||||||
|
isInSelectedParentChain, err := sm.dagTopologyManager.IsInSelectedParentChainOf(lowHash, highHash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if isInSelectedParentChain {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
lowBlockGHOSTDAGData, err := sm.ghostdagDataStore.Get(sm.databaseContext, lowHash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
lowHash = lowBlockGHOSTDAGData.SelectedParent()
|
||||||
|
}
|
||||||
|
return lowHash, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sm *syncManager) missingBlockBodyHashes(highHash *externalapi.DomainHash) ([]*externalapi.DomainHash, error) {
|
func (sm *syncManager) missingBlockBodyHashes(highHash *externalapi.DomainHash) ([]*externalapi.DomainHash, error) {
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
package syncmanager_test
|
package syncmanager_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/kaspanet/kaspad/domain/consensus"
|
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/utils/testutils"
|
|
||||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
|
||||||
"math"
|
"math"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus"
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/utils/testutils"
|
||||||
|
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSyncManager_GetHashesBetween(t *testing.T) {
|
func TestSyncManager_GetHashesBetween(t *testing.T) {
|
||||||
@ -20,27 +21,40 @@ func TestSyncManager_GetHashesBetween(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer teardown(false)
|
defer teardown(false)
|
||||||
|
|
||||||
upBfsOrder := make([]*externalapi.DomainHash, 0, 30)
|
// Create a DAG with the following structure:
|
||||||
selectedParent := params.GenesisHash
|
// merging block
|
||||||
upBfsOrder = append(upBfsOrder, selectedParent)
|
// / | \
|
||||||
|
// split1 split2 split3
|
||||||
|
// \ | /
|
||||||
|
// merging block
|
||||||
|
// / | \
|
||||||
|
// split1 split2 split3
|
||||||
|
// \ | /
|
||||||
|
// etc.
|
||||||
|
expectedOrder := make([]*externalapi.DomainHash, 0, 40)
|
||||||
|
mergingBlock := params.GenesisHash
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
parents := make([]*externalapi.DomainHash, 0, 3)
|
splitBlocks := make([]*externalapi.DomainHash, 0, 3)
|
||||||
for j := 0; j < 4; j++ {
|
for j := 0; j < 3; j++ {
|
||||||
blockHash, _, err := tc.AddBlock([]*externalapi.DomainHash{selectedParent}, nil, nil)
|
splitBlock, _, err := tc.AddBlock([]*externalapi.DomainHash{mergingBlock}, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed adding block: %v", err)
|
t.Fatalf("Failed adding block: %v", err)
|
||||||
}
|
}
|
||||||
parents = append(parents, blockHash)
|
splitBlocks = append(splitBlocks, splitBlock)
|
||||||
upBfsOrder = append(upBfsOrder, blockHash)
|
|
||||||
}
|
}
|
||||||
selectedParent, _, err = tc.AddBlock(parents, nil, nil)
|
sort.Sort(sort.Reverse(testutils.NewTestGhostDAGSorter(splitBlocks, tc, t)))
|
||||||
|
restOfSplitBlocks, selectedParent := splitBlocks[:len(splitBlocks)-1], splitBlocks[len(splitBlocks)-1]
|
||||||
|
expectedOrder = append(expectedOrder, selectedParent)
|
||||||
|
expectedOrder = append(expectedOrder, restOfSplitBlocks...)
|
||||||
|
|
||||||
|
mergingBlock, _, err = tc.AddBlock(splitBlocks, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed adding block: %v", err)
|
t.Fatalf("Failed adding block: %v", err)
|
||||||
}
|
}
|
||||||
upBfsOrder = append(upBfsOrder, selectedParent)
|
expectedOrder = append(expectedOrder, mergingBlock)
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, blockHash := range upBfsOrder {
|
for i, blockHash := range expectedOrder {
|
||||||
empty, err := tc.SyncManager().GetHashesBetween(blockHash, blockHash, math.MaxUint64)
|
empty, err := tc.SyncManager().GetHashesBetween(blockHash, blockHash, math.MaxUint64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("TestSyncManager_GetHashesBetween failed returning 0 hashes on the %d'th block: %v", i, err)
|
t.Fatalf("TestSyncManager_GetHashesBetween failed returning 0 hashes on the %d'th block: %v", i, err)
|
||||||
@ -50,15 +64,13 @@ func TestSyncManager_GetHashesBetween(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
allHashes, err := tc.SyncManager().GetHashesBetween(upBfsOrder[0], upBfsOrder[len(upBfsOrder)-1], math.MaxUint64)
|
actualOrder, err := tc.SyncManager().GetHashesBetween(params.GenesisHash, expectedOrder[len(expectedOrder)-1], math.MaxUint64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("TestSyncManager_GetHashesBetween failed returning allHashes: %v", err)
|
t.Fatalf("TestSyncManager_GetHashesBetween failed returning actualOrder: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Sort(sort.Reverse(testutils.NewTestGhostDAGSorter(upBfsOrder, tc, t)))
|
if !reflect.DeepEqual(actualOrder, expectedOrder) {
|
||||||
upBfsOrderExcludingGenesis := upBfsOrder[1:]
|
t.Fatalf("TestSyncManager_GetHashesBetween expected: \n%s\nactual:\n%s\n", expectedOrder, actualOrder)
|
||||||
if !reflect.DeepEqual(allHashes, upBfsOrderExcludingGenesis) {
|
|
||||||
t.Fatalf("TestSyncManager_GetHashesBetween expected %v\n == \n%v", allHashes, upBfsOrder)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@ package consensus
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/model/testapi"
|
"github.com/kaspanet/kaspad/domain/consensus/model/testapi"
|
||||||
@ -9,7 +11,6 @@ import (
|
|||||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||||
"github.com/kaspanet/kaspad/infrastructure/db/database"
|
"github.com/kaspanet/kaspad/infrastructure/db/database"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"io"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type testConsensus struct {
|
type testConsensus struct {
|
||||||
|
79
domain/consensus/test_consensus_render_to_dot.go
Normal file
79
domain/consensus/test_consensus_render_to_dot.go
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
package consensus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RenderDAGToDot is a helper function for debugging tests.
|
||||||
|
// It requires graphviz installed.
|
||||||
|
func (tc *testConsensus) RenderDAGToDot(filename string) error {
|
||||||
|
dotScript, _ := tc.convertToDot()
|
||||||
|
return renderDotScript(dotScript, filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc *testConsensus) convertToDot() (string, error) {
|
||||||
|
var dotScriptBuilder strings.Builder
|
||||||
|
dotScriptBuilder.WriteString("digraph {\n\trankdir = TB; \n")
|
||||||
|
|
||||||
|
edges := []string{}
|
||||||
|
|
||||||
|
blocksIterator, err := tc.blockStore.AllBlockHashesIterator(tc.databaseContext)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
for ok := blocksIterator.First(); ok; ok = blocksIterator.Next() {
|
||||||
|
hash, err := blocksIterator.Get()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
dotScriptBuilder.WriteString(fmt.Sprintf("\t\"%s\";\n", hash))
|
||||||
|
|
||||||
|
parents, err := tc.dagTopologyManager.Parents(hash)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, parentHash := range parents {
|
||||||
|
edges = append(edges, fmt.Sprintf("\t\"%s\" -> \"%s\";", hash, parentHash))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dotScriptBuilder.WriteString("\n")
|
||||||
|
|
||||||
|
dotScriptBuilder.WriteString(strings.Join(edges, "\n"))
|
||||||
|
|
||||||
|
dotScriptBuilder.WriteString("\n}")
|
||||||
|
|
||||||
|
return dotScriptBuilder.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderDotScript(dotScript string, filename string) error {
|
||||||
|
command := exec.Command("dot", "-Tsvg")
|
||||||
|
stdin, err := command.StdinPipe()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating stdin pipe: %s", err)
|
||||||
|
}
|
||||||
|
spawn("renderDotScript", func() {
|
||||||
|
defer stdin.Close()
|
||||||
|
|
||||||
|
_, err = io.WriteString(stdin, dotScript)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("Error writing dotScript into stdin pipe: %s", err))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
var stderr bytes.Buffer
|
||||||
|
command.Stderr = &stderr
|
||||||
|
svg, err := command.Output()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error getting output of dot: %s\nstderr:\n%s", err, stderr.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return ioutil.WriteFile(filename, svg, 0600)
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user