diff --git a/app/protocol/flows/blockrelay/ibd_with_headers_proof.go b/app/protocol/flows/blockrelay/ibd_with_headers_proof.go index f358f837ed..80e6c7dfb9 100644 --- a/app/protocol/flows/blockrelay/ibd_with_headers_proof.go +++ b/app/protocol/flows/blockrelay/ibd_with_headers_proof.go @@ -7,6 +7,7 @@ import ( "github.com/kaspanet/kaspad/app/protocol/protocolerrors" "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" "github.com/kaspanet/kaspad/domain/consensus/ruleerrors" + "github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing" "github.com/pkg/errors" ) @@ -112,7 +113,16 @@ func (flow *handleRelayInvsFlow) downloadHeadersAndPruningUTXOSet(highHash *exte return err } - log.Debugf("Blocks downloaded from peer %s", flow.peer) + log.Debugf("Headers downloaded from peer %s", flow.peer) + + highHashInfo, err := flow.Domain().StagingConsensus().GetBlockInfo(highHash) + if err != nil { + return err + } + + if !highHashInfo.Exists { + return protocolerrors.Errorf(true, "the triggering IBD block was not send") + } log.Debugf("Syncing the current pruning point UTXO set") syncedPruningPointUTXOSetSuccessfully, err := flow.syncPruningPointUTXOSet(flow.Domain().StagingConsensus(), pruningPoint) @@ -238,6 +248,16 @@ func (flow *handleRelayInvsFlow) validateAndInsertPruningPoints() error { return protocolerrors.Errorf(false, "pruning points are violating finality") } + currentPruningPoint, err := flow.Domain().Consensus().PruningPoint() + if err != nil { + return err + } + + proposedPruningPoint := consensushashing.HeaderHash(headers[len(headers)-1]) + if currentPruningPoint.Equal(proposedPruningPoint) { + return protocolerrors.Errorf(true, "the proposed pruning point is the same as the current pruning point") + } + err = flow.Domain().StagingConsensus().ImportPruningPoints(headers) if err != nil { return err diff --git a/domain/consensus/factory.go b/domain/consensus/factory.go index eab68d5728..09a470e4cd 100644 --- a/domain/consensus/factory.go +++ b/domain/consensus/factory.go @@ -262,12 +262,13 @@ func (f *factory) NewConsensus(config *Config, db infrastructuredatabase.Databas pruningManager := pruningmanager.New( dbManager, dagTraversalManager, - dagTopologyManager, + dagTopologyManagers, consensusStateManager, finalityManager, + ghostdagManagers, consensusStateStore, - ghostdagDataStore, + ghostdagDataStores, pruningStore, blockStatusStore, headersSelectedTipStore, diff --git a/domain/consensus/processes/pruningmanager/pruningmanager.go b/domain/consensus/processes/pruningmanager/pruningmanager.go index 1ca0ed6ccd..770a316428 100644 --- a/domain/consensus/processes/pruningmanager/pruningmanager.go +++ b/domain/consensus/processes/pruningmanager/pruningmanager.go @@ -4,6 +4,7 @@ import ( "github.com/kaspanet/kaspad/domain/consensus/model" "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" "github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing" + "github.com/kaspanet/kaspad/domain/consensus/utils/hashset" "github.com/kaspanet/kaspad/domain/consensus/utils/multiset" "github.com/kaspanet/kaspad/domain/consensus/utils/utxo" "github.com/kaspanet/kaspad/domain/consensus/utils/virtual" @@ -18,12 +19,13 @@ type pruningManager struct { databaseContext model.DBManager dagTraversalManager model.DAGTraversalManager - dagTopologyManager model.DAGTopologyManager + dagTopologyManagers []model.DAGTopologyManager consensusStateManager model.ConsensusStateManager finalityManager model.FinalityManager + ghostdagManagers []model.GHOSTDAGManager consensusStateStore model.ConsensusStateStore - ghostdagDataStore model.GHOSTDAGDataStore + ghostdagDataStores []model.GHOSTDAGDataStore pruningStore model.PruningStore blockStatusStore model.BlockStatusStore headerSelectedTipStore model.HeaderSelectedTipStore @@ -50,12 +52,13 @@ func New( databaseContext model.DBManager, dagTraversalManager model.DAGTraversalManager, - dagTopologyManager model.DAGTopologyManager, + dagTopologyManagers []model.DAGTopologyManager, consensusStateManager model.ConsensusStateManager, finalityManager model.FinalityManager, + ghostdagManagers []model.GHOSTDAGManager, consensusStateStore model.ConsensusStateStore, - ghostdagDataStore model.GHOSTDAGDataStore, + ghostdagDataStores []model.GHOSTDAGDataStore, pruningStore model.PruningStore, blockStatusStore model.BlockStatusStore, headerSelectedTipStore model.HeaderSelectedTipStore, @@ -80,12 +83,12 @@ func New( return &pruningManager{ databaseContext: databaseContext, dagTraversalManager: dagTraversalManager, - dagTopologyManager: dagTopologyManager, + dagTopologyManagers: dagTopologyManagers, consensusStateManager: consensusStateManager, finalityManager: finalityManager, consensusStateStore: consensusStateStore, - ghostdagDataStore: ghostdagDataStore, + ghostdagDataStores: ghostdagDataStores, pruningStore: pruningStore, blockStatusStore: blockStatusStore, multiSetStore: multiSetStore, @@ -133,7 +136,7 @@ func (pm *pruningManager) UpdatePruningPointByVirtual(stagingArea *model.Staging return nil } - virtualGHOSTDAGData, err := pm.ghostdagDataStore.Get(pm.databaseContext, stagingArea, model.VirtualBlockHash, false) + virtualGHOSTDAGData, err := pm.ghostdagDataStores[0].Get(pm.databaseContext, stagingArea, model.VirtualBlockHash, false) if err != nil { return err } @@ -180,7 +183,7 @@ func (pm *pruningManager) NextPruningPointAndCandidateByBlockHash(stagingArea *m return nil, nil, err } - ghostdagData, err := pm.ghostdagDataStore.Get(pm.databaseContext, stagingArea, blockHash, false) + ghostdagData, err := pm.ghostdagDataStores[0].Get(pm.databaseContext, stagingArea, blockHash, false) if err != nil { return nil, nil, err } @@ -190,7 +193,7 @@ func (pm *pruningManager) NextPruningPointAndCandidateByBlockHash(stagingArea *m return nil, nil, err } - currentPruningPointGHOSTDAGData, err := pm.ghostdagDataStore.Get(pm.databaseContext, stagingArea, currentPruningPoint, false) + currentPruningPointGHOSTDAGData, err := pm.ghostdagDataStores[0].Get(pm.databaseContext, stagingArea, currentPruningPoint, false) if err != nil { return nil, nil, err } @@ -225,7 +228,7 @@ func (pm *pruningManager) NextPruningPointAndCandidateByBlockHash(stagingArea *m if err != nil { return nil, nil, err } - selectedChildGHOSTDAGData, err := pm.ghostdagDataStore.Get(pm.databaseContext, stagingArea, selectedChild, false) + selectedChildGHOSTDAGData, err := pm.ghostdagDataStores[0].Get(pm.databaseContext, stagingArea, selectedChild, false) if err != nil { return nil, nil, err } @@ -251,7 +254,7 @@ func (pm *pruningManager) NextPruningPointAndCandidateByBlockHash(stagingArea *m func (pm *pruningManager) isInPruningFutureOrInVirtualPast(stagingArea *model.StagingArea, block *externalapi.DomainHash, pruningPoint *externalapi.DomainHash, virtualParents []*externalapi.DomainHash) (bool, error) { - hasPruningPointInPast, err := pm.dagTopologyManager.IsAncestorOf(stagingArea, pruningPoint, block) + hasPruningPointInPast, err := pm.dagTopologyManagers[0].IsAncestorOf(stagingArea, pruningPoint, block) if err != nil { return false, err } @@ -260,7 +263,7 @@ func (pm *pruningManager) isInPruningFutureOrInVirtualPast(stagingArea *model.St } // Because virtual doesn't have reachability data, we need to check reachability // using it parents. - isInVirtualPast, err := pm.dagTopologyManager.IsAncestorOfAny(stagingArea, block, virtualParents) + isInVirtualPast, err := pm.dagTopologyManagers[0].IsAncestorOfAny(stagingArea, block, virtualParents) if err != nil { return false, err } @@ -277,7 +280,7 @@ func (pm *pruningManager) deletePastBlocks(stagingArea *model.StagingArea, pruni // Go over all pruningPoint.Past and pruningPoint.Anticone that's not in virtual.Past queue := pm.dagTraversalManager.NewDownHeap(stagingArea) - virtualParents, err := pm.dagTopologyManager.Parents(stagingArea, model.VirtualBlockHash) + virtualParents, err := pm.dagTopologyManagers[0].Parents(stagingArea, model.VirtualBlockHash) if err != nil { return err } @@ -293,7 +296,7 @@ func (pm *pruningManager) deletePastBlocks(stagingArea *model.StagingArea, pruni } // Add pruningPoint.Parents to queue - parents, err := pm.dagTopologyManager.Parents(stagingArea, pruningPoint) + parents, err := pm.dagTopologyManagers[0].Parents(stagingArea, pruningPoint) if err != nil { return err } @@ -328,7 +331,7 @@ func (pm *pruningManager) deleteBlocksDownward(stagingArea *model.StagingArea, q return err } if !alreadyPruned { - parents, err := pm.dagTopologyManager.Parents(stagingArea, current) + parents, err := pm.dagTopologyManagers[0].Parents(stagingArea, current) if err != nil { return err } @@ -418,13 +421,13 @@ func (pm *pruningManager) IsValidPruningPoint(stagingArea *model.StagingArea, bl } // A pruning point has to be in the selected chain of the headers selected tip. - headersSelectedTipGHOSTDAGData, err := pm.ghostdagDataStore.Get(pm.databaseContext, stagingArea, headersSelectedTip, false) + headersSelectedTipGHOSTDAGData, err := pm.ghostdagDataStores[0].Get(pm.databaseContext, stagingArea, headersSelectedTip, false) if err != nil { return false, err } isInSelectedParentChainOfHeadersSelectedTip, err := - pm.dagTopologyManager.IsInSelectedParentChainOf(stagingArea, blockHash, headersSelectedTip) + pm.dagTopologyManagers[0].IsInSelectedParentChainOf(stagingArea, blockHash, headersSelectedTip) if err != nil { return false, err } @@ -433,7 +436,7 @@ func (pm *pruningManager) IsValidPruningPoint(stagingArea *model.StagingArea, bl return false, nil } - ghostdagData, err := pm.ghostdagDataStore.Get(pm.databaseContext, stagingArea, blockHash, false) + ghostdagData, err := pm.ghostdagDataStores[0].Get(pm.databaseContext, stagingArea, blockHash, false) if err != nil { return false, err } @@ -470,7 +473,7 @@ func (pm *pruningManager) ArePruningPointsViolatingFinality(stagingArea *model.S continue } - isInSelectedParentChainOfVirtualFinalityPointFinalityPoint, err := pm.dagTopologyManager. + isInSelectedParentChainOfVirtualFinalityPointFinalityPoint, err := pm.dagTopologyManagers[0]. IsInSelectedParentChainOf(stagingArea, virtualFinalityPointFinalityPoint, blockHash) if err != nil { return false, err @@ -508,7 +511,7 @@ func (pm *pruningManager) ArePruningPointsInValidChain(stagingArea *model.Stagin expectedPruningPoints = append(expectedPruningPoints, header.PruningPoint()) } - currentGHOSTDAGData, err := pm.ghostdagDataStore.Get(pm.databaseContext, stagingArea, current, false) + currentGHOSTDAGData, err := pm.ghostdagDataStores[0].Get(pm.databaseContext, stagingArea, current, false) if err != nil { return false, err } @@ -637,11 +640,11 @@ func (pm *pruningManager) calculateDiffBetweenPreviousAndCurrentPruningPoints(st if err != nil { return nil, err } - currentPruningGhostDAG, err := pm.ghostdagDataStore.Get(pm.databaseContext, stagingArea, currentPruningHash, false) + currentPruningGhostDAG, err := pm.ghostdagDataStores[0].Get(pm.databaseContext, stagingArea, currentPruningHash, false) if err != nil { return nil, err } - previousPruningGhostDAG, err := pm.ghostdagDataStore.Get(pm.databaseContext, stagingArea, previousPruningHash, false) + previousPruningGhostDAG, err := pm.ghostdagDataStores[0].Get(pm.databaseContext, stagingArea, previousPruningHash, false) if err != nil { return nil, err } @@ -664,7 +667,7 @@ func (pm *pruningManager) calculateDiffBetweenPreviousAndCurrentPruningPoints(st if err != nil { return nil, err } - diffChildGhostDag, err := pm.ghostdagDataStore.Get(pm.databaseContext, stagingArea, previousPruningCurrentDiffChild, false) + diffChildGhostDag, err := pm.ghostdagDataStores[0].Get(pm.databaseContext, stagingArea, previousPruningCurrentDiffChild, false) if err != nil { return nil, err } @@ -677,7 +680,7 @@ func (pm *pruningManager) calculateDiffBetweenPreviousAndCurrentPruningPoints(st if err != nil { return nil, err } - diffChildGhostDag, err := pm.ghostdagDataStore.Get(pm.databaseContext, stagingArea, currentPruningCurrentDiffChild, false) + diffChildGhostDag, err := pm.ghostdagDataStores[0].Get(pm.databaseContext, stagingArea, currentPruningCurrentDiffChild, false) if err != nil { return nil, err } @@ -837,7 +840,7 @@ func (pm *pruningManager) PruneAllBlocksBelow(stagingArea *model.StagingArea, pr if err != nil { return err } - isInPastOfPruningPoint, err := pm.dagTopologyManager.IsAncestorOf(stagingArea, pruningPointHash, blockHash) + isInPastOfPruningPoint, err := pm.dagTopologyManagers[0].IsAncestorOf(stagingArea, pruningPointHash, blockHash) if err != nil { return err } @@ -922,14 +925,14 @@ func (pm *pruningManager) blockWithTrustedData(stagingArea *model.StagingArea, b current := blockHash isTrustedData := false for i := externalapi.KType(0); i < pm.k+1; i++ { - ghostdagData, err := pm.ghostdagDataStore.Get(pm.databaseContext, stagingArea, current, isTrustedData) + ghostdagData, err := pm.ghostdagDataStores[0].Get(pm.databaseContext, stagingArea, current, isTrustedData) isNotFoundError := database.IsNotFoundError(err) if !isNotFoundError && err != nil { return nil, err } if isNotFoundError || ghostdagData.SelectedParent().Equal(model.VirtualGenesisBlockHash) { isTrustedData = true - ghostdagData, err = pm.ghostdagDataStore.Get(pm.databaseContext, stagingArea, current, true) + ghostdagData, err = pm.ghostdagDataStores[0].Get(pm.databaseContext, stagingArea, current, true) if err != nil { return nil, err } @@ -1008,10 +1011,121 @@ func (pm *pruningManager) isPruningPointInPruningDepth(stagingArea *model.Stagin return false, err } - blockGHOSTDAGData, err := pm.ghostdagDataStore.Get(pm.databaseContext, stagingArea, blockHash, false) + blockGHOSTDAGData, err := pm.ghostdagDataStores[0].Get(pm.databaseContext, stagingArea, blockHash, false) if err != nil { return false, err } return blockGHOSTDAGData.BlueScore() >= pruningPointHeader.BlueScore()+pm.pruningDepth, nil } + +func (pm *pruningManager) BuildPruningPointProof(stagingArea *model.StagingArea) (*externalapi.PruningPointProof, error) { + pruningPoint, err := pm.pruningStore.PruningPoint(pm.databaseContext, stagingArea) + if err != nil { + return nil, err + } + + pruningPointHeader, err := pm.blockHeaderStore.BlockHeader(pm.databaseContext, stagingArea, pruningPoint) + if err != nil { + return nil, err + } + + maxLevel := len(pruningPointHeader.Parents()) - 1 + proof := &externalapi.PruningPointProof{Headers: make([][]externalapi.BlockHeader, maxLevel+1)} + selectedTipByLevel := make([]*externalapi.DomainHash, maxLevel+1) + for blockLevel := maxLevel; blockLevel >= 0; blockLevel-- { + blockLevelParents := pruningPointHeader.ParentsAtLevel(blockLevel) + selectedTip, err := pm.ghostdagManagers[blockLevel].ChooseSelectedParent(stagingArea, []*externalapi.DomainHash(blockLevelParents)...) + if err != nil { + return nil, err + } + + const m = 1000 + blockAtDepth2M, err := pm.blockAtDepth(stagingArea, blockLevel, selectedTip, 2*m) + if err != nil { + return nil, err + } + + root := blockAtDepth2M + if blockLevel != maxLevel { + blockAtDepthMAtNextLevel, err := pm.blockAtDepth(stagingArea, blockLevel+1, selectedTipByLevel[blockLevel+1], m) + if err != nil { + return nil, err + } + + isAncestorOf, err := pm.dagTopologyManagers[blockLevel].IsAncestorOf(stagingArea, blockAtDepthMAtNextLevel, blockAtDepth2M) + if err != nil { + return nil, err + } + + if isAncestorOf { + root = blockAtDepthMAtNextLevel + } + } + + headers := make([]externalapi.BlockHeader, 0, 2*m) + visited := hashset.New() + queue := []*externalapi.DomainHash{root} + for len(queue) > 0 { + var current *externalapi.DomainHash + current, queue = queue[0], queue[1:] + + if visited.Contains(current) { + continue + } + + isAncestorOfPruningPoint, err := pm.dagTopologyManagers[0].IsAncestorOf(stagingArea, current, pruningPoint) + if err != nil { + return nil, err + } + + if !isAncestorOfPruningPoint { + continue + } + + currentHeader, err := pm.blockHeaderStore.BlockHeader(pm.databaseContext, stagingArea, current) + if err != nil { + return nil, err + } + + headers = append(headers, currentHeader) + children, err := pm.dagTopologyManagers[blockLevel].Children(stagingArea, current) + if err != nil { + return nil, err + } + + queue = append(queue, children...) + } + + proof.Headers[blockLevel] = headers + } + + return proof, nil +} + +func (pm *pruningManager) blockAtDepth(stagingArea *model.StagingArea, blockLevel int, highHash *externalapi.DomainHash, depth uint64) (*externalapi.DomainHash, error) { + currentBlockHash := highHash + highBlockGHOSTDAGData, err := pm.ghostdagDataStores[blockLevel].Get(pm.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() == nil { // genesis + return currentBlockHash, nil + } + currentBlockHash = currentBlockGHOSTDAGData.SelectedParent() + currentBlockGHOSTDAGData, err = pm.ghostdagDataStores[blockLevel].Get(pm.databaseContext, stagingArea, currentBlockHash, false) + if err != nil { + return nil, err + } + } + return currentBlockHash, nil +} diff --git a/testing/integration/main_test.go b/testing/integration/main_test.go index ec341075e5..7520d8f1ff 100644 --- a/testing/integration/main_test.go +++ b/testing/integration/main_test.go @@ -7,7 +7,6 @@ import ( ) func TestMain(m *testing.M) { - os.Exit(0) logger.SetLogLevels(logger.LevelDebug) logger.InitLogStdout(logger.LevelDebug)