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

256 lines
9.0 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package ghostdagmanager
import (
"math/big"
"github.com/kaspanet/kaspad/domain/consensus/model"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/util/difficulty"
"github.com/pkg/errors"
)
type blockGHOSTDAGData struct {
blueScore uint64
blueWork *big.Int
selectedParent *externalapi.DomainHash
mergeSetBlues []*externalapi.DomainHash
mergeSetReds []*externalapi.DomainHash
bluesAnticoneSizes map[externalapi.DomainHash]model.KType
}
func (bg *blockGHOSTDAGData) toModel() *model.BlockGHOSTDAGData {
return model.NewBlockGHOSTDAGData(bg.blueScore, bg.blueWork, bg.selectedParent, bg.mergeSetBlues, bg.mergeSetReds, bg.bluesAnticoneSizes)
}
// GHOSTDAG runs the GHOSTDAG protocol and calculates the block BlockGHOSTDAGData by the given parents.
// The function calculates MergeSetBlues by iterating over the blocks in
// the anticone of the new block selected parent (which is the parent with the
// highest blue score) and adds any block to newNode.blues if by adding
// it to MergeSetBlues these conditions will not be violated:
//
// 1) |anticone-of-candidate-block ∩ blue-set-of-newBlock| ≤ K
//
// 2) For every blue block in blue-set-of-newBlock:
// |(anticone-of-blue-block ∩ blue-set-newBlock) {candidate-block}| ≤ K.
// We validate this condition by maintaining a map BluesAnticoneSizes for
// each block which holds all the blue anticone sizes that were affected by
// the new added blue blocks.
// So to find out what is |anticone-of-blue ∩ blue-set-of-newBlock| we just iterate in
// the selected parent chain of the new block until we find an existing entry in
// BluesAnticoneSizes.
//
// For further details see the article https://eprint.iacr.org/2018/104.pdf
func (gm *ghostdagManager) GHOSTDAG(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash) error {
newBlockData := &blockGHOSTDAGData{
blueWork: new(big.Int),
mergeSetBlues: make([]*externalapi.DomainHash, 0),
mergeSetReds: make([]*externalapi.DomainHash, 0),
bluesAnticoneSizes: make(map[externalapi.DomainHash]model.KType),
}
blockParents, err := gm.dagTopologyManager.Parents(stagingArea, blockHash)
if err != nil {
return err
}
isGenesis := len(blockParents) == 0
if !isGenesis {
selectedParent, err := gm.findSelectedParent(stagingArea, blockParents)
if err != nil {
return err
}
newBlockData.selectedParent = selectedParent
newBlockData.mergeSetBlues = append(newBlockData.mergeSetBlues, selectedParent)
newBlockData.bluesAnticoneSizes[*selectedParent] = 0
}
mergeSetWithoutSelectedParent, err := gm.mergeSetWithoutSelectedParent(
stagingArea, newBlockData.selectedParent, blockParents)
if err != nil {
return err
}
for _, blueCandidate := range mergeSetWithoutSelectedParent {
isBlue, candidateAnticoneSize, candidateBluesAnticoneSizes, err := gm.checkBlueCandidate(
stagingArea, newBlockData.toModel(), blueCandidate)
if err != nil {
return err
}
if isBlue {
// No k-cluster violation found, we can now set the candidate block as blue
newBlockData.mergeSetBlues = append(newBlockData.mergeSetBlues, blueCandidate)
newBlockData.bluesAnticoneSizes[*blueCandidate] = candidateAnticoneSize
for blue, blueAnticoneSize := range candidateBluesAnticoneSizes {
newBlockData.bluesAnticoneSizes[blue] = blueAnticoneSize + 1
}
} else {
newBlockData.mergeSetReds = append(newBlockData.mergeSetReds, blueCandidate)
}
}
if !isGenesis {
selectedParentGHOSTDAGData, err := gm.ghostdagDataStore.Get(gm.databaseContext, stagingArea, newBlockData.selectedParent)
if err != nil {
return err
}
newBlockData.blueScore = selectedParentGHOSTDAGData.BlueScore() + uint64(len(newBlockData.mergeSetBlues))
// We inherit the bluework from the selected parent
newBlockData.blueWork.Set(selectedParentGHOSTDAGData.BlueWork())
// Then we add up all the *work*(not blueWork) that all of newBlock merge set blues and selected parent did
for _, blue := range newBlockData.mergeSetBlues {
header, err := gm.headerStore.BlockHeader(gm.databaseContext, stagingArea, blue)
if err != nil {
return err
}
newBlockData.blueWork.Add(newBlockData.blueWork, difficulty.CalcWork(header.Bits()))
}
} else {
// Genesis's blue score is defined to be 0.
newBlockData.blueScore = 0
newBlockData.blueWork.SetUint64(0)
}
gm.ghostdagDataStore.Stage(stagingArea, blockHash, newBlockData.toModel())
return nil
}
type chainBlockData struct {
hash *externalapi.DomainHash
blockData *model.BlockGHOSTDAGData
}
func (gm *ghostdagManager) checkBlueCandidate(stagingArea *model.StagingArea, newBlockData *model.BlockGHOSTDAGData,
blueCandidate *externalapi.DomainHash) (isBlue bool, candidateAnticoneSize model.KType,
candidateBluesAnticoneSizes map[externalapi.DomainHash]model.KType, err error) {
// The maximum length of node.blues can be K+1 because
// it contains the selected parent.
if model.KType(len(newBlockData.MergeSetBlues())) == gm.k+1 {
return false, 0, nil, nil
}
candidateBluesAnticoneSizes = make(map[externalapi.DomainHash]model.KType, gm.k)
// Iterate over all blocks in the blue set of newNode that are not in the past
// of blueCandidate, and check for each one of them if blueCandidate potentially
// enlarges their blue anticone to be over K, or that they enlarge the blue anticone
// of blueCandidate to be over K.
chainBlock := chainBlockData{
blockData: newBlockData,
}
for {
isBlue, isRed, err := gm.checkBlueCandidateWithChainBlock(stagingArea, newBlockData, chainBlock, blueCandidate,
candidateBluesAnticoneSizes, &candidateAnticoneSize)
if err != nil {
return false, 0, nil, err
}
if isBlue {
break
}
if isRed {
return false, 0, nil, nil
}
selectedParentGHOSTDAGData, err := gm.ghostdagDataStore.Get(gm.databaseContext, stagingArea, chainBlock.blockData.SelectedParent())
if err != nil {
return false, 0, nil, err
}
chainBlock = chainBlockData{hash: chainBlock.blockData.SelectedParent(),
blockData: selectedParentGHOSTDAGData,
}
}
return true, candidateAnticoneSize, candidateBluesAnticoneSizes, nil
}
func (gm *ghostdagManager) checkBlueCandidateWithChainBlock(stagingArea *model.StagingArea,
newBlockData *model.BlockGHOSTDAGData, chainBlock chainBlockData, blueCandidate *externalapi.DomainHash,
candidateBluesAnticoneSizes map[externalapi.DomainHash]model.KType,
candidateAnticoneSize *model.KType) (isBlue, isRed bool, err error) {
// If blueCandidate is in the future of chainBlock, it means
// that all remaining blues are in the past of chainBlock and thus
// in the past of blueCandidate. In this case we know for sure that
// the anticone of blueCandidate will not exceed K, and we can mark
// it as blue.
//
// The new block is always in the future of blueCandidate, so there's
// no point in checking it.
// We check if chainBlock is not the new block by checking if it has a hash.
if chainBlock.hash != nil {
isAncestorOfBlueCandidate, err := gm.dagTopologyManager.IsAncestorOf(stagingArea, chainBlock.hash, blueCandidate)
if err != nil {
return false, false, err
}
if isAncestorOfBlueCandidate {
return true, false, nil
}
}
for _, block := range chainBlock.blockData.MergeSetBlues() {
// Skip blocks that exist in the past of blueCandidate.
isAncestorOfBlueCandidate, err := gm.dagTopologyManager.IsAncestorOf(stagingArea, block, blueCandidate)
if err != nil {
return false, false, err
}
if isAncestorOfBlueCandidate {
continue
}
candidateBluesAnticoneSizes[*block], err = gm.blueAnticoneSize(stagingArea, block, newBlockData)
if err != nil {
return false, false, err
}
*candidateAnticoneSize++
if *candidateAnticoneSize > gm.k {
// k-cluster violation: The candidate's blue anticone exceeded k
return false, true, nil
}
if candidateBluesAnticoneSizes[*block] == gm.k {
// k-cluster violation: A block in candidate's blue anticone already
// has k blue blocks in its own anticone
return false, true, nil
}
// This is a sanity check that validates that a blue
// block's blue anticone is not already larger than K.
if candidateBluesAnticoneSizes[*block] > gm.k {
return false, false, errors.New("found blue anticone size larger than k")
}
}
return false, false, nil
}
// blueAnticoneSize returns the blue anticone size of 'block' from the worldview of 'context'.
// Expects 'block' to be in the blue set of 'context'
func (gm *ghostdagManager) blueAnticoneSize(stagingArea *model.StagingArea,
block *externalapi.DomainHash, context *model.BlockGHOSTDAGData) (model.KType, error) {
for current := context; current != nil; {
if blueAnticoneSize, ok := current.BluesAnticoneSizes()[*block]; ok {
return blueAnticoneSize, nil
}
if current.SelectedParent() == nil {
break
}
var err error
current, err = gm.ghostdagDataStore.Get(gm.databaseContext, stagingArea, current.SelectedParent())
if err != nil {
return 0, err
}
}
return 0, errors.Errorf("block %s is not in blue set of the given context", block)
}