diff --git a/app/protocol/flows/v5/blockrelay/ibd.go b/app/protocol/flows/v5/blockrelay/ibd.go index f8130f33b..5e1e5a13d 100644 --- a/app/protocol/flows/v5/blockrelay/ibd.go +++ b/app/protocol/flows/v5/blockrelay/ibd.go @@ -181,6 +181,10 @@ func (flow *handleIBDFlow) negotiateMissingSyncerChainSegment() (*externalapi.Do return nil, nil, err } if info.Exists { + if info.BlockStatus == externalapi.StatusInvalid { + return nil, nil, protocolerrors.Errorf(true, "Sent invalid chain block %s", syncerChainHash) + } + currentHighestKnownSyncerChainHash = syncerChainHash break } diff --git a/app/rpc/rpchandlers/get_blocks.go b/app/rpc/rpchandlers/get_blocks.go index 444de710c..eb45fab01 100644 --- a/app/rpc/rpchandlers/get_blocks.go +++ b/app/rpc/rpchandlers/get_blocks.go @@ -37,7 +37,7 @@ func HandleGetBlocks(context *rpccontext.Context, _ *router.Router, request appm return nil, err } - if !blockInfo.Exists { + if !blockInfo.HasHeader() { return &appmessage.GetBlocksResponseMessage{ Error: appmessage.RPCErrorf("Could not find lowHash %s", getBlocksRequest.LowHash), }, nil diff --git a/domain/consensus/consensus.go b/domain/consensus/consensus.go index 9e9b637c4..2a83255c5 100644 --- a/domain/consensus/consensus.go +++ b/domain/consensus/consensus.go @@ -891,12 +891,16 @@ func (s *consensus) GetVirtualSelectedParentChainFromBlock(blockHash *externalap } func (s *consensus) validateBlockHashExists(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash) error { - exists, err := s.blockStatusStore.Exists(s.databaseContext, stagingArea, blockHash) + status, err := s.blockStatusStore.Get(s.databaseContext, stagingArea, blockHash) + if database.IsNotFoundError(err) { + return errors.Errorf("block %s does not exist", blockHash) + } if err != nil { return err } - if !exists { - return errors.Errorf("block %s does not exist", blockHash) + + if status == externalapi.StatusInvalid { + return errors.Errorf("block %s is invalid", blockHash) } return nil } diff --git a/domain/consensus/processes/pruningmanager/pruningmanager.go b/domain/consensus/processes/pruningmanager/pruningmanager.go index cf944f7ba..c978edff1 100644 --- a/domain/consensus/processes/pruningmanager/pruningmanager.go +++ b/domain/consensus/processes/pruningmanager/pruningmanager.go @@ -776,6 +776,114 @@ func (pm *pruningManager) calculateDiffBetweenPreviousAndCurrentPruningPoints(st return nil, errors.Errorf("previous pruning point doesn't exist") } + previousPruningHash, err := pm.pruningStore.PruningPointByIndex(pm.databaseContext, stagingArea, pruningPointIndex-1) + if err != nil { + return nil, err + } + currentPruningGhostDAG, err := pm.ghostdagDataStore.Get(pm.databaseContext, stagingArea, currentPruningHash, false) + if err != nil { + return nil, err + } + previousPruningGhostDAG, err := pm.ghostdagDataStore.Get(pm.databaseContext, stagingArea, previousPruningHash, false) + if err != nil { + return nil, err + } + + currentPruningCurrentDiffChild := currentPruningHash + previousPruningCurrentDiffChild := previousPruningHash + // We need to use BlueWork because it's the only thing that's monotonic in the whole DAG + // We use the BlueWork to know which point is currently lower on the DAG so we can keep climbing its children, + // that way we keep climbing on the lowest point until they both reach the exact same descendant + currentPruningCurrentDiffChildBlueWork := currentPruningGhostDAG.BlueWork() + previousPruningCurrentDiffChildBlueWork := previousPruningGhostDAG.BlueWork() + + var diffHashesFromPrevious []*externalapi.DomainHash + var diffHashesFromCurrent []*externalapi.DomainHash + for { + // if currentPruningCurrentDiffChildBlueWork > previousPruningCurrentDiffChildBlueWork + if currentPruningCurrentDiffChildBlueWork.Cmp(previousPruningCurrentDiffChildBlueWork) == 1 { + diffHashesFromPrevious = append(diffHashesFromPrevious, previousPruningCurrentDiffChild) + previousPruningCurrentDiffChild, err = pm.utxoDiffStore.UTXODiffChild(pm.databaseContext, stagingArea, previousPruningCurrentDiffChild) + if err != nil { + return nil, err + } + diffChildGhostDag, err := pm.ghostdagDataStore.Get(pm.databaseContext, stagingArea, previousPruningCurrentDiffChild, false) + if err != nil { + return nil, err + } + previousPruningCurrentDiffChildBlueWork = diffChildGhostDag.BlueWork() + } else if currentPruningCurrentDiffChild.Equal(previousPruningCurrentDiffChild) { + break + } else { + diffHashesFromCurrent = append(diffHashesFromCurrent, currentPruningCurrentDiffChild) + currentPruningCurrentDiffChild, err = pm.utxoDiffStore.UTXODiffChild(pm.databaseContext, stagingArea, currentPruningCurrentDiffChild) + if err != nil { + return nil, err + } + diffChildGhostDag, err := pm.ghostdagDataStore.Get(pm.databaseContext, stagingArea, currentPruningCurrentDiffChild, false) + if err != nil { + return nil, err + } + currentPruningCurrentDiffChildBlueWork = diffChildGhostDag.BlueWork() + } + } + // The order in which we apply the diffs should be from top to bottom, but we traversed from bottom to top + // so we apply the diffs in reverse order. + oldDiff := utxo.NewMutableUTXODiff() + for i := len(diffHashesFromPrevious) - 1; i >= 0; i-- { + utxoDiff, err := pm.utxoDiffStore.UTXODiff(pm.databaseContext, stagingArea, diffHashesFromPrevious[i]) + if err != nil { + return nil, err + } + err = oldDiff.WithDiffInPlace(utxoDiff) + if err != nil { + return nil, err + } + } + newDiff := utxo.NewMutableUTXODiff() + for i := len(diffHashesFromCurrent) - 1; i >= 0; i-- { + utxoDiff, err := pm.utxoDiffStore.UTXODiff(pm.databaseContext, stagingArea, diffHashesFromCurrent[i]) + if err != nil { + return nil, err + } + err = newDiff.WithDiffInPlace(utxoDiff) + if err != nil { + return nil, err + } + } + return oldDiff.DiffFrom(newDiff.ToImmutable()) +} + +// This function takes 2 chain blocks (currentPruningHash, previousPruningHash) and finds +// the UTXO diff between them by iterating over acceptance data of the chain blocks in between. +func (pm *pruningManager) calculateDiffBetweenPreviousAndCurrentPruningPointsUsingAcceptanceData(stagingArea *model.StagingArea, currentPruningHash *externalapi.DomainHash) (externalapi.UTXODiff, error) { + onEnd := logger.LogAndMeasureExecutionTime(log, "pruningManager.calculateDiffBetweenPreviousAndCurrentPruningPoints__UsingAcceptanceData") + defer onEnd() + if currentPruningHash.Equal(pm.genesisHash) { + iter, err := pm.consensusStateManager.RestorePastUTXOSetIterator(stagingArea, currentPruningHash) + if err != nil { + return nil, err + } + set := make(map[externalapi.DomainOutpoint]externalapi.UTXOEntry) + for ok := iter.First(); ok; ok = iter.Next() { + outpoint, entry, err := iter.Get() + if err != nil { + return nil, err + } + set[*outpoint] = entry + } + return utxo.NewUTXODiffFromCollections(utxo.NewUTXOCollection(set), utxo.NewUTXOCollection(make(map[externalapi.DomainOutpoint]externalapi.UTXOEntry))) + } + + pruningPointIndex, err := pm.pruningStore.CurrentPruningPointIndex(pm.databaseContext, stagingArea) + if err != nil { + return nil, err + } + + if pruningPointIndex == 0 { + return nil, errors.Errorf("previous pruning point doesn't exist") + } + previousPruningHash, err := pm.pruningStore.PruningPointByIndex(pm.databaseContext, stagingArea, pruningPointIndex-1) if err != nil { return nil, err @@ -901,7 +1009,13 @@ func (pm *pruningManager) updatePruningPoint() error { log.Debugf("Restoring the pruning point UTXO set") utxoSetDiff, err := pm.calculateDiffBetweenPreviousAndCurrentPruningPoints(stagingArea, pruningPoint) if err != nil { - return err + log.Infof("Calculating pruning points diff through utxo diff children failed %s. Falling back to calculation "+ + "through acceptance data", err) + + utxoSetDiff, err = pm.calculateDiffBetweenPreviousAndCurrentPruningPointsUsingAcceptanceData(stagingArea, pruningPoint) + if err != nil { + return err + } } log.Debugf("Updating the pruning point UTXO set") err = pm.pruningStore.UpdatePruningPointUTXOSet(pm.databaseContext, utxoSetDiff)