mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-06-19 20:46:45 +00:00

* Modify DefaultTimeout to 120 seconds A temporary workaround for nodes having trouble to sync (currently the download of pruning point related data during IBD takes more than 30 seconds) * Cache existence in reachability store * Cache block level in the header * Fix IBD indication on submit block * Add hardForkOmitGenesisFromParentsDAAScore logic * Fix NumThreads bug in the wallet * Get rid of ParentsAtLevel header method * Fix a bug in BuildPruningPointProof * Increase race detector timeout * Add cache to BuildPruningPointProof * Add comments and temp comment out go vet * Fix ParentsAtLevel * Dont fill empty parents * Change HardForkOmitGenesisFromParentsDAAScore in fast netsync test * Add --allow-submit-block-when-not-synced in stability tests * Fix TestPruning * Return fast tests * Fix off by one error on kaspawallet * Fetch only one block with trusted data at a time * Update fork DAA score * Don't ban for unexpected message type * Fix tests Co-authored-by: Michael Sutton <mikisiton2@gmail.com> Co-authored-by: Ori Newman <>
702 lines
23 KiB
Go
702 lines
23 KiB
Go
package pruningproofmanager
|
|
|
|
import (
|
|
consensusDB "github.com/kaspanet/kaspad/domain/consensus/database"
|
|
"github.com/kaspanet/kaspad/domain/consensus/datastructures/blockheaderstore"
|
|
"github.com/kaspanet/kaspad/domain/consensus/datastructures/blockrelationstore"
|
|
"github.com/kaspanet/kaspad/domain/consensus/datastructures/ghostdagdatastore"
|
|
"github.com/kaspanet/kaspad/domain/consensus/datastructures/reachabilitydatastore"
|
|
"github.com/kaspanet/kaspad/domain/consensus/model"
|
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
|
"github.com/kaspanet/kaspad/domain/consensus/processes/dagtopologymanager"
|
|
"github.com/kaspanet/kaspad/domain/consensus/processes/ghostdagmanager"
|
|
"github.com/kaspanet/kaspad/domain/consensus/processes/reachabilitymanager"
|
|
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
|
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
|
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
|
"github.com/kaspanet/kaspad/domain/consensus/utils/hashset"
|
|
"github.com/kaspanet/kaspad/infrastructure/db/database"
|
|
"github.com/kaspanet/kaspad/infrastructure/logger"
|
|
"github.com/pkg/errors"
|
|
"math/big"
|
|
)
|
|
|
|
type pruningProofManager struct {
|
|
databaseContext model.DBManager
|
|
|
|
dagTopologyManagers []model.DAGTopologyManager
|
|
ghostdagManagers []model.GHOSTDAGManager
|
|
reachabilityManagers []model.ReachabilityManager
|
|
dagTraversalManagers []model.DAGTraversalManager
|
|
parentsManager model.ParentsManager
|
|
|
|
ghostdagDataStores []model.GHOSTDAGDataStore
|
|
pruningStore model.PruningStore
|
|
blockHeaderStore model.BlockHeaderStore
|
|
blockStatusStore model.BlockStatusStore
|
|
finalityStore model.FinalityStore
|
|
consensusStateStore model.ConsensusStateStore
|
|
|
|
genesisHash *externalapi.DomainHash
|
|
k externalapi.KType
|
|
pruningProofM uint64
|
|
|
|
cachedPruningPoint *externalapi.DomainHash
|
|
cachedProof *externalapi.PruningPointProof
|
|
}
|
|
|
|
// New instantiates a new PruningManager
|
|
func New(
|
|
databaseContext model.DBManager,
|
|
|
|
dagTopologyManagers []model.DAGTopologyManager,
|
|
ghostdagManagers []model.GHOSTDAGManager,
|
|
reachabilityManagers []model.ReachabilityManager,
|
|
dagTraversalManagers []model.DAGTraversalManager,
|
|
parentsManager model.ParentsManager,
|
|
|
|
ghostdagDataStores []model.GHOSTDAGDataStore,
|
|
pruningStore model.PruningStore,
|
|
blockHeaderStore model.BlockHeaderStore,
|
|
blockStatusStore model.BlockStatusStore,
|
|
finalityStore model.FinalityStore,
|
|
consensusStateStore model.ConsensusStateStore,
|
|
|
|
genesisHash *externalapi.DomainHash,
|
|
k externalapi.KType,
|
|
pruningProofM uint64,
|
|
) model.PruningProofManager {
|
|
|
|
return &pruningProofManager{
|
|
databaseContext: databaseContext,
|
|
dagTopologyManagers: dagTopologyManagers,
|
|
ghostdagManagers: ghostdagManagers,
|
|
reachabilityManagers: reachabilityManagers,
|
|
dagTraversalManagers: dagTraversalManagers,
|
|
parentsManager: parentsManager,
|
|
|
|
ghostdagDataStores: ghostdagDataStores,
|
|
pruningStore: pruningStore,
|
|
blockHeaderStore: blockHeaderStore,
|
|
blockStatusStore: blockStatusStore,
|
|
finalityStore: finalityStore,
|
|
consensusStateStore: consensusStateStore,
|
|
|
|
genesisHash: genesisHash,
|
|
k: k,
|
|
pruningProofM: pruningProofM,
|
|
}
|
|
}
|
|
|
|
func (ppm *pruningProofManager) BuildPruningPointProof(stagingArea *model.StagingArea) (*externalapi.PruningPointProof, error) {
|
|
onEnd := logger.LogAndMeasureExecutionTime(log, "BuildPruningPointProof")
|
|
defer onEnd()
|
|
|
|
pruningPoint, err := ppm.pruningStore.PruningPoint(ppm.databaseContext, stagingArea)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if ppm.cachedPruningPoint != nil && ppm.cachedPruningPoint.Equal(pruningPoint) {
|
|
return ppm.cachedProof, nil
|
|
}
|
|
|
|
proof, err := ppm.buildPruningPointProof(stagingArea)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ppm.cachedProof = proof
|
|
ppm.cachedPruningPoint = pruningPoint
|
|
|
|
return proof, nil
|
|
}
|
|
|
|
func (ppm *pruningProofManager) buildPruningPointProof(stagingArea *model.StagingArea) (*externalapi.PruningPointProof, error) {
|
|
onEnd := logger.LogAndMeasureExecutionTime(log, "buildPruningPointProof")
|
|
defer onEnd()
|
|
|
|
pruningPoint, err := ppm.pruningStore.PruningPoint(ppm.databaseContext, stagingArea)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if pruningPoint.Equal(ppm.genesisHash) {
|
|
return &externalapi.PruningPointProof{}, nil
|
|
}
|
|
|
|
pruningPointHeader, err := ppm.blockHeaderStore.BlockHeader(ppm.databaseContext, stagingArea, pruningPoint)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
maxLevel := len(ppm.parentsManager.Parents(pruningPointHeader)) - 1
|
|
headersByLevel := make(map[int][]externalapi.BlockHeader)
|
|
selectedTipByLevel := make([]*externalapi.DomainHash, maxLevel+1)
|
|
pruningPointLevel := pruningPointHeader.BlockLevel()
|
|
for blockLevel := maxLevel; blockLevel >= 0; blockLevel-- {
|
|
var selectedTip *externalapi.DomainHash
|
|
if blockLevel <= pruningPointLevel {
|
|
selectedTip = pruningPoint
|
|
} else {
|
|
blockLevelParents := ppm.parentsManager.ParentsAtLevel(pruningPointHeader, blockLevel)
|
|
selectedTipCandidates := make([]*externalapi.DomainHash, 0, len(blockLevelParents))
|
|
|
|
// In a pruned node, some pruning point parents might be missing, but we're guaranteed that its
|
|
// selected parent is not missing.
|
|
for _, parent := range blockLevelParents {
|
|
_, err := ppm.ghostdagDataStores[blockLevel].Get(ppm.databaseContext, stagingArea, parent, false)
|
|
if database.IsNotFoundError(err) {
|
|
continue
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
selectedTipCandidates = append(selectedTipCandidates, parent)
|
|
}
|
|
|
|
selectedTip, err = ppm.ghostdagManagers[blockLevel].ChooseSelectedParent(stagingArea, selectedTipCandidates...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
selectedTipByLevel[blockLevel] = selectedTip
|
|
|
|
blockAtDepth2M, err := ppm.blockAtDepth(stagingArea, ppm.ghostdagDataStores[blockLevel], selectedTip, 2*ppm.pruningProofM)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
root := blockAtDepth2M
|
|
if blockLevel != maxLevel {
|
|
blockAtDepthMAtNextLevel, err := ppm.blockAtDepth(stagingArea, ppm.ghostdagDataStores[blockLevel+1], selectedTipByLevel[blockLevel+1], ppm.pruningProofM)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
isBlockAtDepthMAtNextLevelAncestorOfBlockAtDepth2M, err := ppm.dagTopologyManagers[blockLevel].IsAncestorOf(stagingArea, blockAtDepthMAtNextLevel, blockAtDepth2M)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if isBlockAtDepthMAtNextLevelAncestorOfBlockAtDepth2M {
|
|
root = blockAtDepthMAtNextLevel
|
|
} else {
|
|
isBlockAtDepth2MAncestorOfBlockAtDepthMAtNextLevel, err := ppm.dagTopologyManagers[blockLevel].IsAncestorOf(stagingArea, blockAtDepth2M, blockAtDepthMAtNextLevel)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !isBlockAtDepth2MAncestorOfBlockAtDepthMAtNextLevel {
|
|
// find common ancestor
|
|
current := blockAtDepthMAtNextLevel
|
|
for {
|
|
ghostdagData, err := ppm.ghostdagDataStores[blockLevel].Get(ppm.databaseContext, stagingArea, current, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
current = ghostdagData.SelectedParent()
|
|
if current.Equal(model.VirtualGenesisBlockHash) {
|
|
return nil, errors.Errorf("No common ancestor between %s and %s at level %d", blockAtDepth2M, blockAtDepthMAtNextLevel, blockLevel)
|
|
}
|
|
|
|
isCurrentAncestorOfBlockAtDepth2M, err := ppm.dagTopologyManagers[blockLevel].IsAncestorOf(stagingArea, current, blockAtDepth2M)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if isCurrentAncestorOfBlockAtDepth2M {
|
|
root = current
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
headers := make([]externalapi.BlockHeader, 0, 2*ppm.pruningProofM)
|
|
visited := hashset.New()
|
|
queue := ppm.dagTraversalManagers[blockLevel].NewUpHeap(stagingArea)
|
|
err = queue.Push(root)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for queue.Len() > 0 {
|
|
current := queue.Pop()
|
|
|
|
if visited.Contains(current) {
|
|
continue
|
|
}
|
|
|
|
visited.Add(current)
|
|
isAncestorOfSelectedTip, err := ppm.dagTopologyManagers[blockLevel].IsAncestorOf(stagingArea, current, selectedTip)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !isAncestorOfSelectedTip {
|
|
continue
|
|
}
|
|
|
|
currentHeader, err := ppm.blockHeaderStore.BlockHeader(ppm.databaseContext, stagingArea, current)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
headers = append(headers, currentHeader)
|
|
children, err := ppm.dagTopologyManagers[blockLevel].Children(stagingArea, current)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = queue.PushSlice(children)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
headersByLevel[blockLevel] = headers
|
|
}
|
|
|
|
proof := &externalapi.PruningPointProof{Headers: make([][]externalapi.BlockHeader, len(headersByLevel))}
|
|
for i := 0; i < len(headersByLevel); i++ {
|
|
proof.Headers[i] = headersByLevel[i]
|
|
}
|
|
|
|
return proof, nil
|
|
}
|
|
|
|
func (ppm *pruningProofManager) blockAtDepth(stagingArea *model.StagingArea, ghostdagDataStore model.GHOSTDAGDataStore, highHash *externalapi.DomainHash, depth uint64) (*externalapi.DomainHash, error) {
|
|
currentBlockHash := highHash
|
|
highBlockGHOSTDAGData, err := ghostdagDataStore.Get(ppm.databaseContext, stagingArea, highHash, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
requiredBlueScore := uint64(0)
|
|
if highBlockGHOSTDAGData.BlueScore() > depth {
|
|
requiredBlueScore = highBlockGHOSTDAGData.BlueScore() - depth
|
|
}
|
|
|
|
currentBlockGHOSTDAGData := highBlockGHOSTDAGData
|
|
// If we used `BlockIterator` we'd need to do more calls to `ghostdagDataStore` so we can get the blueScore
|
|
for currentBlockGHOSTDAGData.BlueScore() >= requiredBlueScore {
|
|
if currentBlockGHOSTDAGData.SelectedParent().Equal(model.VirtualGenesisBlockHash) {
|
|
break
|
|
}
|
|
|
|
currentBlockHash = currentBlockGHOSTDAGData.SelectedParent()
|
|
currentBlockGHOSTDAGData, err = ghostdagDataStore.Get(ppm.databaseContext, stagingArea, currentBlockHash, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return currentBlockHash, nil
|
|
}
|
|
|
|
func (ppm *pruningProofManager) ValidatePruningPointProof(pruningPointProof *externalapi.PruningPointProof) error {
|
|
onEnd := logger.LogAndMeasureExecutionTime(log, "ValidatePruningPointProof")
|
|
defer onEnd()
|
|
|
|
stagingArea := model.NewStagingArea()
|
|
|
|
if len(pruningPointProof.Headers) == 0 {
|
|
return errors.Wrap(ruleerrors.ErrPruningProofEmpty, "pruning proof is empty")
|
|
}
|
|
|
|
level0Headers := pruningPointProof.Headers[0]
|
|
pruningPointHeader := level0Headers[len(level0Headers)-1]
|
|
pruningPoint := consensushashing.HeaderHash(pruningPointHeader)
|
|
pruningPointBlockLevel := pruningPointHeader.BlockLevel()
|
|
maxLevel := len(ppm.parentsManager.Parents(pruningPointHeader)) - 1
|
|
if maxLevel >= len(pruningPointProof.Headers) {
|
|
return errors.Wrapf(ruleerrors.ErrPruningProofEmpty, "proof has only %d levels while pruning point "+
|
|
"has parents from %d levels", len(pruningPointProof.Headers), maxLevel+1)
|
|
}
|
|
|
|
blockHeaderStore, blockRelationStores, reachabilityDataStores, ghostdagDataStores, err := ppm.dagStores(maxLevel)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
reachabilityManagers, dagTopologyManagers, ghostdagManagers := ppm.dagProcesses(maxLevel, blockHeaderStore, blockRelationStores, reachabilityDataStores, ghostdagDataStores)
|
|
|
|
for blockLevel := 0; blockLevel <= maxLevel; blockLevel++ {
|
|
err := reachabilityManagers[blockLevel].Init(stagingArea)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = dagTopologyManagers[blockLevel].SetParents(stagingArea, model.VirtualGenesisBlockHash, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ghostdagDataStores[blockLevel].Stage(stagingArea, model.VirtualGenesisBlockHash, externalapi.NewBlockGHOSTDAGData(
|
|
0,
|
|
big.NewInt(0),
|
|
nil,
|
|
nil,
|
|
nil,
|
|
nil,
|
|
), false)
|
|
}
|
|
|
|
selectedTipByLevel := make([]*externalapi.DomainHash, maxLevel+1)
|
|
for blockLevel := maxLevel; blockLevel >= 0; blockLevel-- {
|
|
headers := make([]externalapi.BlockHeader, len(pruningPointProof.Headers[blockLevel]))
|
|
copy(headers, pruningPointProof.Headers[blockLevel])
|
|
|
|
var selectedTip *externalapi.DomainHash
|
|
for i, header := range headers {
|
|
blockHash := consensushashing.HeaderHash(header)
|
|
if header.BlockLevel() < blockLevel {
|
|
return errors.Wrapf(ruleerrors.ErrPruningProofWrongBlockLevel, "block %s level is %d when it's "+
|
|
"expected to be at least %d", blockHash, header.BlockLevel(), blockLevel)
|
|
}
|
|
|
|
blockHeaderStore.Stage(stagingArea, blockHash, header)
|
|
|
|
var parents []*externalapi.DomainHash
|
|
for _, parent := range ppm.parentsManager.ParentsAtLevel(header, blockLevel) {
|
|
_, err := ghostdagDataStores[blockLevel].Get(ppm.databaseContext, stagingArea, parent, false)
|
|
if database.IsNotFoundError(err) {
|
|
continue
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
parents = append(parents, parent)
|
|
}
|
|
|
|
if len(parents) == 0 {
|
|
if i != 0 {
|
|
return errors.Wrapf(ruleerrors.ErrPruningProofHeaderWithNoKnownParents, "the proof header "+
|
|
"%s is missing known parents", blockHash)
|
|
}
|
|
parents = append(parents, model.VirtualGenesisBlockHash)
|
|
}
|
|
|
|
err := dagTopologyManagers[blockLevel].SetParents(stagingArea, blockHash, parents)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = ghostdagManagers[blockLevel].GHOSTDAG(stagingArea, blockHash)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if selectedTip == nil {
|
|
selectedTip = blockHash
|
|
} else {
|
|
selectedTip, err = ghostdagManagers[blockLevel].ChooseSelectedParent(stagingArea, selectedTip, blockHash)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
err = reachabilityManagers[blockLevel].AddBlock(stagingArea, blockHash)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if selectedTip.Equal(blockHash) {
|
|
err := reachabilityManagers[blockLevel].UpdateReindexRoot(stagingArea, selectedTip)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
if blockLevel < maxLevel {
|
|
blockAtDepthMAtNextLevel, err := ppm.blockAtDepth(stagingArea, ghostdagDataStores[blockLevel+1], selectedTipByLevel[blockLevel+1], ppm.pruningProofM)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
hasBlockAtDepthMAtNextLevel, err := blockRelationStores[blockLevel].Has(ppm.databaseContext, stagingArea, blockAtDepthMAtNextLevel)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !hasBlockAtDepthMAtNextLevel {
|
|
return errors.Wrapf(ruleerrors.ErrPruningProofMissingBlockAtDepthMFromNextLevel, "proof level %d "+
|
|
"is missing the block at depth m in level %d", blockLevel, blockLevel+1)
|
|
}
|
|
}
|
|
|
|
if !selectedTip.Equal(pruningPoint) && !ppm.parentsManager.ParentsAtLevel(pruningPointHeader, blockLevel).Contains(selectedTip) {
|
|
return errors.Wrapf(ruleerrors.ErrPruningProofMissesBlocksBelowPruningPoint, "the selected tip %s at "+
|
|
"level %d is not a parent of the pruning point", selectedTip, blockLevel)
|
|
}
|
|
selectedTipByLevel[blockLevel] = selectedTip
|
|
}
|
|
|
|
currentDAGPruningPoint, err := ppm.pruningStore.PruningPoint(ppm.databaseContext, model.NewStagingArea())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for blockLevel, selectedTip := range selectedTipByLevel {
|
|
if blockLevel <= pruningPointBlockLevel {
|
|
if !selectedTip.Equal(consensushashing.HeaderHash(pruningPointHeader)) {
|
|
return errors.Wrapf(ruleerrors.ErrPruningProofSelectedTipIsNotThePruningPoint, "the pruning "+
|
|
"proof selected tip %s at level %d is not the pruning point", selectedTip, blockLevel)
|
|
}
|
|
} else if !ppm.parentsManager.ParentsAtLevel(pruningPointHeader, blockLevel).Contains(selectedTip) {
|
|
return errors.Wrapf(ruleerrors.ErrPruningProofSelectedTipNotParentOfPruningPoint, "the pruning "+
|
|
"proof selected tip %s at level %d is not a parent of the of the pruning point on the same "+
|
|
"level", selectedTip, blockLevel)
|
|
}
|
|
|
|
selectedTipGHOSTDAGData, err := ghostdagDataStores[blockLevel].Get(ppm.databaseContext, stagingArea, selectedTip, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if selectedTipGHOSTDAGData.BlueScore() < 2*ppm.pruningProofM {
|
|
continue
|
|
}
|
|
|
|
current := selectedTip
|
|
currentGHOSTDAGData := selectedTipGHOSTDAGData
|
|
var commonAncestor *externalapi.DomainHash
|
|
var commonAncestorGHOSTDAGData *externalapi.BlockGHOSTDAGData
|
|
var currentDAGCommonAncestorGHOSTDAGData *externalapi.BlockGHOSTDAGData
|
|
for {
|
|
currentDAGHOSTDAGData, err := ppm.ghostdagDataStores[blockLevel].Get(ppm.databaseContext, model.NewStagingArea(), current, false)
|
|
if err == nil {
|
|
commonAncestor = current
|
|
commonAncestorGHOSTDAGData = currentGHOSTDAGData
|
|
currentDAGCommonAncestorGHOSTDAGData = currentDAGHOSTDAGData
|
|
break
|
|
}
|
|
|
|
if !database.IsNotFoundError(err) {
|
|
return err
|
|
}
|
|
|
|
current = currentGHOSTDAGData.SelectedParent()
|
|
if current.Equal(model.VirtualGenesisBlockHash) {
|
|
break
|
|
}
|
|
|
|
currentGHOSTDAGData, err = ghostdagDataStores[blockLevel].Get(ppm.databaseContext, stagingArea, current, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if commonAncestor != nil {
|
|
selectedTipBlueWorkDiff := big.NewInt(0).Sub(selectedTipGHOSTDAGData.BlueWork(), commonAncestorGHOSTDAGData.BlueWork())
|
|
currentDAGPruningPointParents, err := ppm.dagTopologyManagers[blockLevel].Parents(model.NewStagingArea(), currentDAGPruningPoint)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
foundBetterParent := false
|
|
for _, parent := range currentDAGPruningPointParents {
|
|
parentGHOSTDAGData, err := ppm.ghostdagDataStores[blockLevel].Get(ppm.databaseContext, model.NewStagingArea(), parent, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
parentBlueWorkDiff := big.NewInt(0).Sub(parentGHOSTDAGData.BlueWork(), currentDAGCommonAncestorGHOSTDAGData.BlueWork())
|
|
if parentBlueWorkDiff.Cmp(selectedTipBlueWorkDiff) >= 0 {
|
|
foundBetterParent = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if foundBetterParent {
|
|
return errors.Wrapf(ruleerrors.ErrPruningProofInsufficientBlueWork, "the proof doesn't "+
|
|
"have sufficient blue work in order to replace the current DAG")
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
for blockLevel := maxLevel; blockLevel >= 0; blockLevel-- {
|
|
currentDAGPruningPointParents, err := ppm.dagTopologyManagers[blockLevel].Parents(model.NewStagingArea(), currentDAGPruningPoint)
|
|
// If the current pruning point doesn't have a parent at this level, we consider the proof state to be better.
|
|
if database.IsNotFoundError(err) {
|
|
return nil
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, parent := range currentDAGPruningPointParents {
|
|
parentGHOSTDAGData, err := ppm.ghostdagDataStores[blockLevel].Get(ppm.databaseContext, model.NewStagingArea(), parent, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if parentGHOSTDAGData.BlueScore() < 2*ppm.pruningProofM {
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return errors.Wrapf(ruleerrors.ErrPruningProofInsufficientBlueWork, "the pruning proof doesn't have any "+
|
|
"shared blocks with the known DAGs, but doesn't have enough headers from levels higher than the existing block levels.")
|
|
}
|
|
|
|
func (ppm *pruningProofManager) dagStores(maxLevel int) (model.BlockHeaderStore, []model.BlockRelationStore, []model.ReachabilityDataStore, []model.GHOSTDAGDataStore, error) {
|
|
blockRelationStores := make([]model.BlockRelationStore, maxLevel+1)
|
|
reachabilityDataStores := make([]model.ReachabilityDataStore, maxLevel+1)
|
|
ghostdagDataStores := make([]model.GHOSTDAGDataStore, maxLevel+1)
|
|
|
|
prefix := consensusDB.MakeBucket([]byte("pruningProofManager"))
|
|
blockHeaderStore, err := blockheaderstore.New(ppm.databaseContext, prefix, 0, false)
|
|
if err != nil {
|
|
return nil, nil, nil, nil, err
|
|
}
|
|
|
|
for i := 0; i <= maxLevel; i++ {
|
|
blockRelationStores[i] = blockrelationstore.New(prefix, 0, false)
|
|
reachabilityDataStores[i] = reachabilitydatastore.New(prefix, 0, false)
|
|
ghostdagDataStores[i] = ghostdagdatastore.New(prefix, 0, false)
|
|
}
|
|
|
|
return blockHeaderStore, blockRelationStores, reachabilityDataStores, ghostdagDataStores, nil
|
|
}
|
|
|
|
func (ppm *pruningProofManager) dagProcesses(
|
|
maxLevel int,
|
|
blockHeaderStore model.BlockHeaderStore,
|
|
blockRelationStores []model.BlockRelationStore,
|
|
reachabilityDataStores []model.ReachabilityDataStore,
|
|
ghostdagDataStores []model.GHOSTDAGDataStore) (
|
|
[]model.ReachabilityManager,
|
|
[]model.DAGTopologyManager,
|
|
[]model.GHOSTDAGManager,
|
|
) {
|
|
|
|
reachabilityManagers := make([]model.ReachabilityManager, constants.MaxBlockLevel+1)
|
|
dagTopologyManagers := make([]model.DAGTopologyManager, constants.MaxBlockLevel+1)
|
|
ghostdagManagers := make([]model.GHOSTDAGManager, constants.MaxBlockLevel+1)
|
|
|
|
for i := 0; i <= maxLevel; i++ {
|
|
reachabilityManagers[i] = reachabilitymanager.New(
|
|
ppm.databaseContext,
|
|
ghostdagDataStores[i],
|
|
reachabilityDataStores[i])
|
|
|
|
dagTopologyManagers[i] = dagtopologymanager.New(
|
|
ppm.databaseContext,
|
|
reachabilityManagers[i],
|
|
blockRelationStores[i],
|
|
ghostdagDataStores[i])
|
|
|
|
ghostdagManagers[i] = ghostdagmanager.New(
|
|
ppm.databaseContext,
|
|
dagTopologyManagers[i],
|
|
ghostdagDataStores[i],
|
|
blockHeaderStore,
|
|
ppm.k,
|
|
ppm.genesisHash)
|
|
}
|
|
|
|
return reachabilityManagers, dagTopologyManagers, ghostdagManagers
|
|
}
|
|
|
|
func (ppm *pruningProofManager) ApplyPruningPointProof(stagingArea *model.StagingArea, pruningPointProof *externalapi.PruningPointProof) error {
|
|
onEnd := logger.LogAndMeasureExecutionTime(log, "ApplyPruningPointProof")
|
|
defer onEnd()
|
|
|
|
for blockLevel, headers := range pruningPointProof.Headers {
|
|
var selectedTip *externalapi.DomainHash
|
|
for i, header := range headers {
|
|
blockHash := consensushashing.HeaderHash(header)
|
|
if header.BlockLevel() < blockLevel {
|
|
return errors.Wrapf(ruleerrors.ErrPruningProofWrongBlockLevel, "block %s level is %d when it's "+
|
|
"expected to be at least %d", blockHash, header.BlockLevel(), blockLevel)
|
|
}
|
|
|
|
ppm.blockHeaderStore.Stage(stagingArea, blockHash, header)
|
|
|
|
var parents []*externalapi.DomainHash
|
|
for _, parent := range ppm.parentsManager.ParentsAtLevel(header, blockLevel) {
|
|
_, err := ppm.ghostdagDataStores[blockLevel].Get(ppm.databaseContext, stagingArea, parent, false)
|
|
if database.IsNotFoundError(err) {
|
|
continue
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
parents = append(parents, parent)
|
|
}
|
|
|
|
if len(parents) == 0 {
|
|
if i != 0 {
|
|
return errors.Wrapf(ruleerrors.ErrPruningProofHeaderWithNoKnownParents, "the proof header "+
|
|
"%s is missing known parents", blockHash)
|
|
}
|
|
parents = append(parents, model.VirtualGenesisBlockHash)
|
|
}
|
|
|
|
err := ppm.dagTopologyManagers[blockLevel].SetParents(stagingArea, blockHash, parents)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = ppm.ghostdagManagers[blockLevel].GHOSTDAG(stagingArea, blockHash)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if blockLevel == 0 {
|
|
// Override the ghostdag data with the real blue score and blue work
|
|
ghostdagData, err := ppm.ghostdagDataStores[0].Get(ppm.databaseContext, stagingArea, blockHash, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ppm.ghostdagDataStores[0].Stage(stagingArea, blockHash, externalapi.NewBlockGHOSTDAGData(
|
|
header.BlueScore(),
|
|
header.BlueWork(),
|
|
ghostdagData.SelectedParent(),
|
|
ghostdagData.MergeSetBlues(),
|
|
ghostdagData.MergeSetReds(),
|
|
ghostdagData.BluesAnticoneSizes(),
|
|
), false)
|
|
|
|
ppm.finalityStore.StageFinalityPoint(stagingArea, blockHash, model.VirtualGenesisBlockHash)
|
|
ppm.blockStatusStore.Stage(stagingArea, blockHash, externalapi.StatusHeaderOnly)
|
|
}
|
|
|
|
if selectedTip == nil {
|
|
selectedTip = blockHash
|
|
} else {
|
|
selectedTip, err = ppm.ghostdagManagers[blockLevel].ChooseSelectedParent(stagingArea, selectedTip, blockHash)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
err = ppm.reachabilityManagers[blockLevel].AddBlock(stagingArea, blockHash)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if selectedTip.Equal(blockHash) {
|
|
err := ppm.reachabilityManagers[blockLevel].UpdateReindexRoot(stagingArea, selectedTip)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pruningPointHeader := pruningPointProof.Headers[0][len(pruningPointProof.Headers[0])-1]
|
|
pruningPoint := consensushashing.HeaderHash(pruningPointHeader)
|
|
ppm.consensusStateStore.StageTips(stagingArea, []*externalapi.DomainHash{pruningPoint})
|
|
return nil
|
|
}
|