talelbaz f407c44a8d
[Issue - #1126] - Checking pruning point violation - pruning point in the past. (#1160)
* [NOD-1126]
1. Change function name in BlockValidator interface from: "ValidateProofOfWorkAndDifficulty" to "ValidatePruningPointViolationAndProofOfWorkAndDifficulty".
2. Add to the blockValidator struct the pruningManager (also added to the function "New" Respectively).
3. Added new function "checkPruningPointViolation" of blockValidator type.
4. Add new internal check - "checkPruningPointViolation", on the function "ValidateProofOfWorkAndDifficulty".(The third check).
5. Add new error rule - "ErrPruningPointViolation".

* [Issue-1126]
1. Remove the function "PruningPoint" from PruningManager interface.
2. Changes in blockValidator struct - remove pruningManager, and adding pruningStore.
3. Reads for "pruningPoint" function from pruningStore instead of pruningManager (because of note 1 above) in the functions: * "checkPruningPointViolation" of type blockValidator.
             * "FindNextPruningPoint" of type pruningManager.

* [Issue-1126]
1. Add missing error handling.

* [Issue-1126] Changes in function "checkPruningPointViolation": If header = genesis, stop checking and return nil.

* [Issue-1126] In function "checkPruningPointViolation" - change from a for loop to the "IsAncestorOfAny" function.

* [#1126] "FindNextPruningPoint" - save the pruning point in case the point is the genesis and change code internal order.

* [#1126] "FindNextPruningPoint" - cosmetics change.

* [#1126] "FindNextPruningPoint" - remove "return nil" when there is no pruning point on the if expression.

Co-authored-by: tal <tal@daglabs.com>
2020-11-30 09:57:15 +02:00

243 lines
6.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
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,
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,
genesisHash: genesisHash,
pruningDepth: pruningDepth,
finalityInterval: finalityInterval,
}
}
// FindNextPruningPoint finds the next pruning point from the
// given blockHash
func (pm *pruningManager) FindNextPruningPoint() 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
}
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 virtual.BlueScore <= currentPBlueScore+pm.finalityInterval {
return nil
}
// This means the pruning point is still genesis.
if virtual.BlueScore <= pm.pruningDepth+pm.finalityInterval {
return nil
}
// get Virtual(pruningDepth)
candidatePHash, err := pm.dagTraversalManager.BlockAtDepth(model.VirtualBlockHash, pm.pruningDepth)
if err != nil {
return err
}
candidatePGhost, err := pm.ghostdagDataStore.Get(pm.databaseContext, candidatePHash)
if err != nil {
return err
}
// Actually check if the pruning point changed
if (currentPBlueScore / pm.finalityInterval) < (candidatePGhost.BlueScore / pm.finalityInterval) {
err = pm.savePruningPoint(candidatePHash)
if err != nil {
return err
}
return pm.deletePastBlocks(candidatePHash)
}
return pm.deletePastBlocks(currentP)
}
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 serializeUTXOSetIterator(iter model.ReadOnlyUTXOSetIterator) ([]byte, error) {
serializedUtxo, err := utxoserialization.ReadOnlyUTXOSetToProtoUTXOSet(iter)
if err != nil {
return nil, err
}
return proto.Marshal(serializedUtxo)
}