Svarog f1451406f7
Add support for multiple staging areas (#1633)
* Add StagingArea struct

* Implemented staging areas in blockStore

* Move blockStagingShard to separate folder

* Apply staging shard to acceptanceDataStore

* Update blockHeaderStore with StagingArea

* Add StagingArea to BlockRelationStore

* Add StagingArea to blockStatusStore

* Add StagingArea to consensusStateStore

* Add StagingArea to daaBlocksStore

* Add StagingArea to finalityStore

* Add StagingArea to ghostdagDataStore

* Add StagingArea to headersSelectedChainStore and headersSelectedTipStore

* Add StagingArea to multisetStore

* Add StagingArea to pruningStore

* Add StagingArea to reachabilityDataStore

* Add StagingArea to utxoDiffStore

* Fix forgotten compilation error

* Update reachability manager and some more things with StagingArea

* Add StagingArea to dagTopologyManager, and some more

* Add StagingArea to GHOSTDAGManager, and some more

* Add StagingArea to difficultyManager, and some more

* Add StagingArea to dagTraversalManager, and some more

* Add StagingArea to headerTipsManager, and some more

* Add StagingArea to constnsusStateManager, pastMedianTimeManager

* Add StagingArea to transactionValidator

* Add StagingArea to finalityManager

* Add StagingArea to mergeDepthManager

* Add StagingArea to pruningManager

* Add StagingArea to rest of ValidateAndInsertBlock

* Add StagingArea to blockValidator

* Add StagingArea to coinbaseManager

* Add StagingArea to syncManager

* Add StagingArea to blockBuilder

* Update consensus with StagingArea

* Add StagingArea to ghostdag2

* Fix remaining compilation errors

* Update names of stagingShards

* Fix forgotten stagingArea passing

* Mark stagingShard.isCommited = true once commited

* Move isStaged to stagingShard, so that it's available without going through store

* Make blockHeaderStore count be avilable from stagingShard

* Fix remaining forgotten stagingArea passing

* commitAllChanges should call dbTx.Commit in the end

* Fix all tests tests in blockValidator

* Fix all tests in consensusStateManager and some more

* Fix all tests in pruningManager

* Add many missing stagingAreas in tests

* Fix many tests

* Fix most of all other tests

* Fix ghostdag_test.go

* Add comment to StagingArea

* Make list of StagingShards an array

* Add comment to StagingShardID

* Make sure all staging shards are pointer-receiver

* Undo bucket rename in block_store

* Typo: isCommited -> isCommitted

* Add comment explaining why stagingArea.shards is an array
2021-03-29 10:34:11 +03:00

270 lines
9.5 KiB
Go

package syncmanager
import (
"github.com/kaspanet/kaspad/domain/consensus/model"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/pkg/errors"
)
// antiPastHashesBetween returns the hashes of the blocks between the
// lowHash's antiPast and highHash's antiPast, or up to
// `maxBlueScoreDifference`, if non-zero.
// The result excludes lowHash and includes highHash. If lowHash == highHash, returns nothing.
func (sm *syncManager) antiPastHashesBetween(stagingArea *model.StagingArea, lowHash, highHash *externalapi.DomainHash,
maxBlueScoreDifference uint64) (hashes []*externalapi.DomainHash, actualHighHash *externalapi.DomainHash, err error) {
// If lowHash is not in the selectedParentChain of highHash - SelectedChildIterator will fail.
// Therefore, we traverse down lowHash's selectedParentChain until we reach a block that is in
// highHash's selectedParentChain.
// We keep originalLowHash to filter out blocks in it's past later down the road
originalLowHash := lowHash
lowHash, err = sm.findLowHashInHighHashSelectedParentChain(stagingArea, lowHash, highHash)
if err != nil {
return nil, nil, err
}
lowBlockGHOSTDAGData, err := sm.ghostdagDataStore.Get(sm.databaseContext, stagingArea, lowHash)
if err != nil {
return nil, nil, err
}
highBlockGHOSTDAGData, err := sm.ghostdagDataStore.Get(sm.databaseContext, stagingArea, highHash)
if err != nil {
return nil, nil, err
}
if lowBlockGHOSTDAGData.BlueScore() > highBlockGHOSTDAGData.BlueScore() {
return nil, nil, errors.Errorf("low hash blueScore > high hash blueScore (%d > %d)",
lowBlockGHOSTDAGData.BlueScore(), highBlockGHOSTDAGData.BlueScore())
}
if maxBlueScoreDifference != 0 {
// In order to get no more then maxBlueScoreDifference
// blocks from the future of the lowHash (including itself),
// we iterate the selected parent chain of the highNode and
// stop once we reach
// highBlockBlueScore-lowBlockBlueScore+1 <= maxBlueScoreDifference.
// That stop point becomes the new highHash.
// Using blueScore as an approximation is considered to be
// fairly accurate because we presume that most DAG blocks are
// blue.
highHash, err = sm.findHighHashAccordingToMaxBlueScoreDifference(stagingArea,
lowHash, highHash, maxBlueScoreDifference, highBlockGHOSTDAGData, lowBlockGHOSTDAGData)
if err != nil {
return nil, nil, err
}
}
// Collect all hashes by concatenating the merge-sets of all blocks between highHash and lowHash
blockHashes := []*externalapi.DomainHash{}
iterator, err := sm.dagTraversalManager.SelectedChildIterator(stagingArea, highHash, lowHash)
if err != nil {
return nil, nil, err
}
defer iterator.Close()
for ok := iterator.First(); ok; ok = iterator.Next() {
current, err := iterator.Get()
if err != nil {
return nil, nil, err
}
// Both blue and red merge sets are topologically sorted, but not the concatenation of the two.
// We require the blocks to be topologically sorted. In addition, for optimal performance,
// we want the selectedParent to be first.
// 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.
sortedMergeSet, err := sm.getSortedMergeSet(stagingArea, current)
if err != nil {
return nil, nil, err
}
// append to blockHashes all blocks in sortedMergeSet which are not in the past of originalLowHash
for _, blockHash := range sortedMergeSet {
isInPastOfOriginalLowHash, err := sm.dagTopologyManager.IsAncestorOf(stagingArea, blockHash, originalLowHash)
if err != nil {
return nil, nil, err
}
if isInPastOfOriginalLowHash {
continue
}
blockHashes = append(blockHashes, blockHash)
}
}
// The process above doesn't return highHash, so include it explicitly, unless highHash == lowHash
if !lowHash.Equal(highHash) {
blockHashes = append(blockHashes, highHash)
}
return blockHashes, highHash, nil
}
func (sm *syncManager) getSortedMergeSet(stagingArea *model.StagingArea, current *externalapi.DomainHash) (
[]*externalapi.DomainHash, error) {
currentGhostdagData, err := sm.ghostdagDataStore.Get(sm.databaseContext, stagingArea, 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, stagingArea, currentBlue)
if err != nil {
return nil, err
}
currentRed := redMergeSet[j]
currentRedGhostdagData, err := sm.ghostdagDataStore.Get(sm.databaseContext, stagingArea, 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(stagingArea *model.StagingArea,
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(stagingArea, highHash, lowHash)
if err != nil {
return nil, err
}
defer iterator.Close()
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, stagingArea, highHashCandidate)
if err != nil {
return nil, err
}
if highBlockGHOSTDAGData.BlueScore()-lowBlockGHOSTDAGData.BlueScore() > maxBlueScoreDifference {
break
}
highHash = highHashCandidate
}
return highHash, nil
}
func (sm *syncManager) findLowHashInHighHashSelectedParentChain(stagingArea *model.StagingArea,
lowHash *externalapi.DomainHash, highHash *externalapi.DomainHash) (*externalapi.DomainHash, error) {
for {
isInSelectedParentChain, err := sm.dagTopologyManager.IsInSelectedParentChainOf(stagingArea, lowHash, highHash)
if err != nil {
return nil, err
}
if isInSelectedParentChain {
break
}
lowBlockGHOSTDAGData, err := sm.ghostdagDataStore.Get(sm.databaseContext, stagingArea, lowHash)
if err != nil {
return nil, err
}
lowHash = lowBlockGHOSTDAGData.SelectedParent()
}
return lowHash, nil
}
func (sm *syncManager) missingBlockBodyHashes(stagingArea *model.StagingArea, highHash *externalapi.DomainHash) (
[]*externalapi.DomainHash, error) {
pruningPoint, err := sm.pruningStore.PruningPoint(sm.databaseContext, stagingArea)
if err != nil {
return nil, err
}
selectedChildIterator, err := sm.dagTraversalManager.SelectedChildIterator(stagingArea, highHash, pruningPoint)
if err != nil {
return nil, err
}
defer selectedChildIterator.Close()
lowHash := pruningPoint
foundHeaderOnlyBlock := false
for ok := selectedChildIterator.First(); ok; ok = selectedChildIterator.Next() {
selectedChild, err := selectedChildIterator.Get()
if err != nil {
return nil, err
}
hasBlock, err := sm.blockStore.HasBlock(sm.databaseContext, stagingArea, selectedChild)
if err != nil {
return nil, err
}
if !hasBlock {
foundHeaderOnlyBlock = true
break
}
lowHash = selectedChild
}
if !foundHeaderOnlyBlock {
if lowHash == highHash {
// Blocks can be inserted inside the DAG during IBD if those were requested before IBD started.
// In rare cases, all the IBD blocks might be already inserted by the time we reach this point.
// In these cases - return an empty list of blocks to sync
return []*externalapi.DomainHash{}, nil
}
// TODO: Once block children are fixed (https://github.com/kaspanet/kaspad/issues/1499),
// this error should be returned rather the logged
log.Errorf("no header-only blocks between %s and %s",
lowHash, highHash)
}
hashesBetween, _, err := sm.antiPastHashesBetween(stagingArea, lowHash, highHash, 0)
if err != nil {
return nil, err
}
missingBlocks := make([]*externalapi.DomainHash, 0, len(hashesBetween))
for _, blockHash := range hashesBetween {
blockStatus, err := sm.blockStatusStore.Get(sm.databaseContext, stagingArea, blockHash)
if err != nil {
return nil, err
}
if blockStatus == externalapi.StatusHeaderOnly {
missingBlocks = append(missingBlocks, blockHash)
}
}
return missingBlocks, nil
}
func (sm *syncManager) isHeaderOnlyBlock(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash) (bool, error) {
exists, err := sm.blockStatusStore.Exists(sm.databaseContext, stagingArea, blockHash)
if err != nil {
return false, err
}
if !exists {
return false, nil
}
status, err := sm.blockStatusStore.Get(sm.databaseContext, stagingArea, blockHash)
if err != nil {
return false, err
}
return status == externalapi.StatusHeaderOnly, nil
}