mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-07-09 14:22:33 +00:00
Change sirtual parents selection to allow faster branch merges in the network (#1315)
* if more candidates then max, choose half with highest blueWork and half with lowest * Add a Test GhostDAG sorter * Add a test for pick virtual parents * Fix review nits
This commit is contained in:
parent
0f93189c16
commit
533fa8c00e
@ -32,11 +32,23 @@ func (csm *consensusStateManager) pickVirtualParents(tips []*externalapi.DomainH
|
|||||||
log.Debugf("The selected parent of the virtual is: %s", virtualSelectedParent)
|
log.Debugf("The selected parent of the virtual is: %s", virtualSelectedParent)
|
||||||
|
|
||||||
selectedVirtualParents := hashset.NewFromSlice(virtualSelectedParent)
|
selectedVirtualParents := hashset.NewFromSlice(virtualSelectedParent)
|
||||||
|
candidates := candidatesHeap.ToSlice()
|
||||||
|
// prioritize half the blocks with highest blueWork and half with lowest, so the network will merge splits faster.
|
||||||
|
if len(candidates) >= int(csm.maxBlockParents) {
|
||||||
|
// We already have the selectedParent, so we're left with csm.maxBlockParents-1.
|
||||||
|
maxParents := csm.maxBlockParents - 1
|
||||||
|
end := len(candidates) - 1
|
||||||
|
for i := (maxParents) / 2; i < maxParents; i++ {
|
||||||
|
candidates[i], candidates[end] = candidates[end], candidates[i]
|
||||||
|
end--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mergeSetSize := uint64(1) // starts counting from 1 because selectedParent is already in the mergeSet
|
mergeSetSize := uint64(1) // starts counting from 1 because selectedParent is already in the mergeSet
|
||||||
|
|
||||||
for candidatesHeap.Len() > 0 && uint64(len(selectedVirtualParents)) < uint64(csm.maxBlockParents) {
|
for len(candidates) > 0 && uint64(len(selectedVirtualParents)) < uint64(csm.maxBlockParents) {
|
||||||
candidate := candidatesHeap.Pop()
|
candidate := candidates[0]
|
||||||
|
candidates = candidates[1:]
|
||||||
|
|
||||||
log.Debugf("Attempting to add %s to the virtual parents", candidate)
|
log.Debugf("Attempting to add %s to the virtual parents", candidate)
|
||||||
log.Debugf("The current merge set size is %d", mergeSetSize)
|
log.Debugf("The current merge set size is %d", mergeSetSize)
|
||||||
|
@ -0,0 +1,110 @@
|
|||||||
|
package consensusstatemanager_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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/testutils"
|
||||||
|
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||||
|
"sort"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConsensusStateManager_pickVirtualParents(t *testing.T) {
|
||||||
|
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
|
||||||
|
tc, teardown, err := consensus.NewFactory().NewTestConsensus(params, "TestConsensusStateManager_pickVirtualParents")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error setting up tc: %+v", err)
|
||||||
|
}
|
||||||
|
defer teardown(false)
|
||||||
|
|
||||||
|
getSortedVirtualParents := func(tc testapi.TestConsensus) []*externalapi.DomainHash {
|
||||||
|
virtualRelations, err := tc.BlockRelationStore().BlockRelation(tc.DatabaseContext(), model.VirtualBlockHash)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed getting virtual block virtualRelations: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
block, err := tc.BuildBlock(&externalapi.DomainCoinbaseData{}, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Consensus failed building a block: %v", err)
|
||||||
|
}
|
||||||
|
blockParents := block.Header.ParentHashes()
|
||||||
|
sort.Sort(consensus.NewTestGhostDAGSorter(virtualRelations.Parents, tc, t))
|
||||||
|
sort.Sort(consensus.NewTestGhostDAGSorter(blockParents, tc, t))
|
||||||
|
if !externalapi.HashesEqual(virtualRelations.Parents, blockParents) {
|
||||||
|
t.Fatalf("Block relations and BuildBlock return different parents for virtual, %s != %s", virtualRelations.Parents, blockParents)
|
||||||
|
}
|
||||||
|
return virtualRelations.Parents
|
||||||
|
}
|
||||||
|
|
||||||
|
// We build 2*params.MaxBlockParents each one with blueWork higher than the other.
|
||||||
|
parents := make([]*externalapi.DomainHash, 0, params.MaxBlockParents)
|
||||||
|
for i := 0; i < 2*int(params.MaxBlockParents); i++ {
|
||||||
|
lastBlock := params.GenesisHash
|
||||||
|
for j := 0; j <= i; j++ {
|
||||||
|
lastBlock, _, err = tc.AddBlock([]*externalapi.DomainHash{lastBlock}, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed Adding block to tc: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parents = append(parents, lastBlock)
|
||||||
|
}
|
||||||
|
|
||||||
|
virtualParents := getSortedVirtualParents(tc)
|
||||||
|
sort.Sort(consensus.NewTestGhostDAGSorter(parents, tc, t))
|
||||||
|
|
||||||
|
// Make sure the first half of the blocks are with highest blueWork
|
||||||
|
// we use (max+1)/2 because the first "half" is rounded up, so `(dividend + (divisor - 1)) / divisor` = `(max + (2-1))/2` = `(max+1)/2`
|
||||||
|
for i := 0; i < int(params.MaxBlockParents+1)/2; i++ {
|
||||||
|
if !virtualParents[i].Equal(parents[i]) {
|
||||||
|
t.Fatalf("Expected block at %d to be equal, instead found %s != %s", i, virtualParents[i], parents[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the second half is the candidates with lowest blueWork
|
||||||
|
end := len(parents) - int(params.MaxBlockParents)/2
|
||||||
|
for i := (params.MaxBlockParents + 1) / 2; i < params.MaxBlockParents; i++ {
|
||||||
|
if !virtualParents[i].Equal(parents[end]) {
|
||||||
|
t.Fatalf("Expected block at %d to be equal, instead found %s != %s", i, virtualParents[i], parents[end])
|
||||||
|
}
|
||||||
|
end++
|
||||||
|
}
|
||||||
|
if end != len(parents) {
|
||||||
|
t.Fatalf("Expected %d==%d", end, len(parents))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear all tips.
|
||||||
|
var virtualSelectedParent *externalapi.DomainHash
|
||||||
|
for {
|
||||||
|
block, err := tc.BuildBlock(&externalapi.DomainCoinbaseData{}, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed building a block: %v", err)
|
||||||
|
}
|
||||||
|
_, err = tc.ValidateAndInsertBlock(block)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed Inserting block to tc: %v", err)
|
||||||
|
}
|
||||||
|
virtualSelectedParent = consensushashing.BlockHash(block)
|
||||||
|
if len(block.Header.ParentHashes()) == 1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// build exactly params.MaxBlockParents
|
||||||
|
parents = make([]*externalapi.DomainHash, 0, params.MaxBlockParents)
|
||||||
|
for i := 0; i < int(params.MaxBlockParents); i++ {
|
||||||
|
block, _, err := tc.AddBlock([]*externalapi.DomainHash{virtualSelectedParent}, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed Adding block to tc: %v", err)
|
||||||
|
}
|
||||||
|
parents = append(parents, block)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(consensus.NewTestGhostDAGSorter(parents, tc, t))
|
||||||
|
virtualParents = getSortedVirtualParents(tc)
|
||||||
|
if !externalapi.HashesEqual(virtualParents, parents) {
|
||||||
|
t.Fatalf("Expected VirtualParents and parents to be equal, instead: %s != %s", virtualParents, parents)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
@ -1,7 +1,6 @@
|
|||||||
package dagtraversalmanager_test
|
package dagtraversalmanager_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/model/testapi"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
@ -345,7 +344,7 @@ func TestBlueBlockWindow(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("BlueWindow: %s", err)
|
t.Fatalf("BlueWindow: %s", err)
|
||||||
}
|
}
|
||||||
sortWindow(t, tc, window)
|
sort.Sort(consensus.NewTestGhostDAGSorter(window, tc, t))
|
||||||
if err := checkWindowIDs(window, blockData.expectedWindowWithGenesisPadding, idByBlockMap); err != nil {
|
if err := checkWindowIDs(window, blockData.expectedWindowWithGenesisPadding, idByBlockMap); err != nil {
|
||||||
t.Errorf("Unexpected values for window for block %s: %s", blockData.id, err)
|
t.Errorf("Unexpected values for window for block %s: %s", blockData.id, err)
|
||||||
}
|
}
|
||||||
@ -353,20 +352,6 @@ func TestBlueBlockWindow(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func sortWindow(t *testing.T, tc testapi.TestConsensus, window []*externalapi.DomainHash) {
|
|
||||||
sort.Slice(window, func(i, j int) bool {
|
|
||||||
ghostdagDataI, err := tc.GHOSTDAGDataStore().Get(tc.DatabaseContext(), window[i])
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed getting ghostdag data for %s", err)
|
|
||||||
}
|
|
||||||
ghostdagDataJ, err := tc.GHOSTDAGDataStore().Get(tc.DatabaseContext(), window[j])
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed getting ghostdag data for %s", err)
|
|
||||||
}
|
|
||||||
return !tc.GHOSTDAGManager().Less(window[i], ghostdagDataI, window[j], ghostdagDataJ)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkWindowIDs(window []*externalapi.DomainHash, expectedIDs []string, idByBlockMap map[externalapi.DomainHash]string) error {
|
func checkWindowIDs(window []*externalapi.DomainHash, expectedIDs []string, idByBlockMap map[externalapi.DomainHash]string) error {
|
||||||
ids := make([]string, len(window))
|
ids := make([]string, len(window))
|
||||||
for i, node := range window {
|
for i, node := range window {
|
||||||
|
43
domain/consensus/test_ghostdag_sorter.go
Normal file
43
domain/consensus/test_ghostdag_sorter.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package consensus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/model/testapi"
|
||||||
|
"sort"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testGhostDAGSorter struct {
|
||||||
|
slice []*externalapi.DomainHash
|
||||||
|
tc testapi.TestConsensus
|
||||||
|
test testing.TB
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTestGhostDAGSorter returns a sort.Interface over the slice, so you can sort it via GhostDAG ordering
|
||||||
|
func NewTestGhostDAGSorter(slice []*externalapi.DomainHash, tc testapi.TestConsensus, t testing.TB) sort.Interface {
|
||||||
|
return testGhostDAGSorter{
|
||||||
|
slice: slice,
|
||||||
|
tc: tc,
|
||||||
|
test: t,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sorter testGhostDAGSorter) Len() int {
|
||||||
|
return len(sorter.slice)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sorter testGhostDAGSorter) Less(i, j int) bool {
|
||||||
|
ghostdagDataI, err := sorter.tc.GHOSTDAGDataStore().Get(sorter.tc.DatabaseContext(), sorter.slice[i])
|
||||||
|
if err != nil {
|
||||||
|
sorter.test.Fatalf("TestGhostDAGSorter: Failed getting ghostdag data for %s", err)
|
||||||
|
}
|
||||||
|
ghostdagDataJ, err := sorter.tc.GHOSTDAGDataStore().Get(sorter.tc.DatabaseContext(), sorter.slice[j])
|
||||||
|
if err != nil {
|
||||||
|
sorter.test.Fatalf("TestGhostDAGSorter: Failed getting ghostdag data for %s", err)
|
||||||
|
}
|
||||||
|
return !sorter.tc.GHOSTDAGManager().Less(sorter.slice[i], ghostdagDataI, sorter.slice[j], ghostdagDataJ)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sorter testGhostDAGSorter) Swap(i, j int) {
|
||||||
|
sorter.slice[i], sorter.slice[j] = sorter.slice[j], sorter.slice[i]
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user