Ori Newman 48e1a2c396
New headers first flow (#1211)
* Get rid of insertMode

* Rename AddBlockToVirtual->AddBlock

* When F is not in the future of P, enforce finality with P and not with F.

* Don't allow blocks with invalid parents or with missing block body

* Check finality violation before checking block status

* Implement CalculateIndependentPruningPoint

* Move checkBlockStatus to validateBlock

* Add ValidateBlock to block processor interface

* Adjust SetPruningPoint to the new IBD flow

* Add pruning store to CSM's constructor

* Flip wrong condition on AddHeaderTip

* Fix func (hts *headerSelectedTipStore) Has

* Fix block stage order

* Call to ValidateBodyInContext from validatePostProofOfWork

* Enable overrideDAGParams

* Update log

* Rename SetPruningPoint to ValidateAndInsertPruningPoint and move most of its logic inside block processor

* Rename hasValidatedHeader->hasValidatedOnlyHeader

* Fix typo

* Name return values for fetchMissingUTXOSet

* Add comment

* Return ErrMissingParents when block body is missing

* Add logs and comments

* Fix merge error

* Fix pruning point calculation to be by virtual selected parent

* Replace CalculateIndependentPruningPoint to CalculatePruningPointByHeaderSelectedTip

* Fix isAwaitingUTXOSet to check pruning point by headers

* Change isAwaitingUTXOSet indication

* Remove IsBlockInHeaderPruningPointFuture from BlockInfo

* Fix LowestChainBlockAboveOrEqualToBlueScore

* Add validateNewPruningPointTransactions

* Add validateNewPruningAgainstPastUTXO

* Rename set_pruning_utxo_set.go to update_pruning_utxo_set.go

* Check missing block body hashes by missing block instead of status

* Validate pruning point against past UTXO with the pruning point as block hash

* Remove virtualHeaderHash

* Fix comment

* Fix imports
2020-12-14 17:53:08 +02:00

271 lines
7.6 KiB
Go

package pruningmanager
import (
"github.com/golang/protobuf/proto"
"github.com/kaspanet/kaspad/domain/consensus/model"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/utxoserialization"
)
// pruningManager resolves and manages the current pruning point
type pruningManager struct {
databaseContext model.DBReader
dagTraversalManager model.DAGTraversalManager
dagTopologyManager model.DAGTopologyManager
consensusStateManager model.ConsensusStateManager
consensusStateStore model.ConsensusStateStore
ghostdagDataStore model.GHOSTDAGDataStore
pruningStore model.PruningStore
blockStatusStore model.BlockStatusStore
headerSelectedTipStore model.HeaderSelectedTipStore
multiSetStore model.MultisetStore
acceptanceDataStore model.AcceptanceDataStore
blocksStore model.BlockStore
utxoDiffStore model.UTXODiffStore
genesisHash *externalapi.DomainHash
finalityInterval uint64
pruningDepth uint64
}
// New instantiates a new PruningManager
func New(
databaseContext model.DBReader,
dagTraversalManager model.DAGTraversalManager,
dagTopologyManager model.DAGTopologyManager,
consensusStateManager model.ConsensusStateManager,
consensusStateStore model.ConsensusStateStore,
ghostdagDataStore model.GHOSTDAGDataStore,
pruningStore model.PruningStore,
blockStatusStore model.BlockStatusStore,
headerSelectedTipStore model.HeaderSelectedTipStore,
multiSetStore model.MultisetStore,
acceptanceDataStore model.AcceptanceDataStore,
blocksStore model.BlockStore,
utxoDiffStore model.UTXODiffStore,
genesisHash *externalapi.DomainHash,
finalityInterval uint64,
pruningDepth uint64,
) model.PruningManager {
return &pruningManager{
databaseContext: databaseContext,
dagTraversalManager: dagTraversalManager,
dagTopologyManager: dagTopologyManager,
consensusStateManager: consensusStateManager,
consensusStateStore: consensusStateStore,
ghostdagDataStore: ghostdagDataStore,
pruningStore: pruningStore,
blockStatusStore: blockStatusStore,
multiSetStore: multiSetStore,
acceptanceDataStore: acceptanceDataStore,
blocksStore: blocksStore,
utxoDiffStore: utxoDiffStore,
headerSelectedTipStore: headerSelectedTipStore,
genesisHash: genesisHash,
pruningDepth: pruningDepth,
finalityInterval: finalityInterval,
}
}
// FindNextPruningPoint finds the next pruning point from the
// given blockHash
func (pm *pruningManager) UpdatePruningPointByVirtual() error {
hasPruningPoint, err := pm.pruningStore.HasPruningPoint(pm.databaseContext)
if err != nil {
return err
}
if !hasPruningPoint {
err = pm.savePruningPoint(pm.genesisHash)
if err != nil {
return err
}
}
currentP, err := pm.pruningStore.PruningPoint(pm.databaseContext)
if err != nil {
return err
}
virtual, err := pm.ghostdagDataStore.Get(pm.databaseContext, model.VirtualBlockHash)
if err != nil {
return err
}
virtualSelectedParent, err := pm.ghostdagDataStore.Get(pm.databaseContext, virtual.SelectedParent())
if err != nil {
return err
}
currentPGhost, err := pm.ghostdagDataStore.Get(pm.databaseContext, currentP)
if err != nil {
return err
}
currentPBlueScore := currentPGhost.BlueScore()
// Because the pruning point changes only once per finality, then there's no need to even check for that if a finality interval hasn't passed.
if virtualSelectedParent.BlueScore() <= currentPBlueScore+pm.finalityInterval {
return nil
}
// This means the pruning point is still genesis.
if virtualSelectedParent.BlueScore() <= pm.pruningDepth+pm.finalityInterval {
return nil
}
// get Virtual(pruningDepth)
newPruningPoint, err := pm.calculatePruningPointFromBlock(model.VirtualBlockHash)
if err != nil {
return err
}
if *newPruningPoint != *currentP {
err = pm.savePruningPoint(newPruningPoint)
if err != nil {
return err
}
return pm.deletePastBlocks(newPruningPoint)
}
return nil
}
func (pm *pruningManager) deletePastBlocks(pruningPoint *externalapi.DomainHash) error {
// Go over all P.Past and P.AC that's not in V.Past
queue := pm.dagTraversalManager.NewDownHeap()
// Find P.AC that's not in V.Past
dagTips, err := pm.consensusStateStore.Tips(pm.databaseContext)
if err != nil {
return err
}
for _, tip := range dagTips {
hasPruningPointInPast, err := pm.dagTopologyManager.IsAncestorOf(pruningPoint, tip)
if err != nil {
return err
}
if !hasPruningPointInPast {
isInVirtualPast, err := pm.dagTopologyManager.IsAncestorOf(model.VirtualBlockHash, tip)
if err != nil {
return err
}
if !isInVirtualPast {
// Add them to the queue so they and their past will be pruned
err := queue.Push(tip)
if err != nil {
return err
}
}
}
}
// Add P.Parents
parents, err := pm.dagTopologyManager.Parents(pruningPoint)
if err != nil {
return err
}
for _, parent := range parents {
err = queue.Push(parent)
if err != nil {
return err
}
}
visited := map[externalapi.DomainHash]struct{}{}
// Prune everything in the queue including its past
for queue.Len() > 0 {
current := queue.Pop()
if _, ok := visited[*current]; ok {
continue
}
visited[*current] = struct{}{}
alreadyPruned, err := pm.deleteBlock(current)
if err != nil {
return err
}
if !alreadyPruned {
parents, err := pm.dagTopologyManager.Parents(current)
if err != nil {
return err
}
for _, parent := range parents {
err = queue.Push(parent)
if err != nil {
return err
}
}
}
}
return nil
}
func (pm *pruningManager) savePruningPoint(blockHash *externalapi.DomainHash) error {
utxoIter, err := pm.consensusStateManager.RestorePastUTXOSetIterator(blockHash)
if err != nil {
return err
}
serializedUtxo, err := serializeUTXOSetIterator(utxoIter)
if err != nil {
return err
}
pm.pruningStore.Stage(blockHash, serializedUtxo)
return nil
}
func (pm *pruningManager) deleteBlock(blockHash *externalapi.DomainHash) (alreadyPruned bool, err error) {
status, err := pm.blockStatusStore.Get(pm.databaseContext, blockHash)
if err != nil {
return false, err
}
if status == externalapi.StatusHeaderOnly {
return true, nil
}
pm.multiSetStore.Delete(blockHash)
pm.acceptanceDataStore.Delete(blockHash)
pm.blocksStore.Delete(blockHash)
pm.utxoDiffStore.Delete(blockHash)
pm.blockStatusStore.Stage(blockHash, externalapi.StatusHeaderOnly)
return false, nil
}
func (pm *pruningManager) CalculatePruningPointByHeaderSelectedTip() (*externalapi.DomainHash, error) {
headersSelectedTip, err := pm.headerSelectedTipStore.HeadersSelectedTip(pm.databaseContext)
if err != nil {
return nil, err
}
return pm.calculatePruningPointFromBlock(headersSelectedTip)
}
func (pm *pruningManager) calculatePruningPointFromBlock(blockHash *externalapi.DomainHash) (*externalapi.DomainHash, error) {
ghostdagData, err := pm.ghostdagDataStore.Get(pm.databaseContext, blockHash)
if err != nil {
return nil, err
}
targetBlueScore := uint64(0)
if ghostdagData.BlueScore() > pm.pruningDepth {
// The target blue is calculated by calculating ghostdagData.BlueScore() - pm.pruningDepth and rounding
// down with the precision of finality interval.
targetBlueScore = ((ghostdagData.BlueScore() - pm.pruningDepth) / pm.finalityInterval) * pm.finalityInterval
}
return pm.dagTraversalManager.LowestChainBlockAboveOrEqualToBlueScore(blockHash, targetBlueScore)
}
func serializeUTXOSetIterator(iter model.ReadOnlyUTXOSetIterator) ([]byte, error) {
serializedUtxo, err := utxoserialization.ReadOnlyUTXOSetToProtoUTXOSet(iter)
if err != nil {
return nil, err
}
return proto.Marshal(serializedUtxo)
}