package consensusstatemanager import ( "github.com/kaspanet/kaspad/domain/consensus/model" "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" "github.com/kaspanet/kaspad/infrastructure/logger" "github.com/kaspanet/kaspad/util/staging" "github.com/pkg/errors" "sort" ) func (csm *consensusStateManager) findNextPendingTip(stagingArea *model.StagingArea) (*externalapi.DomainHash, externalapi.BlockStatus, error) { tips, err := csm.consensusStateStore.Tips(stagingArea, csm.databaseContext) if err != nil { return nil, externalapi.StatusInvalid, err } var sortErr error sort.Slice(tips, func(i, j int) bool { selectedParent, err := csm.ghostdagManager.ChooseSelectedParent(stagingArea, tips[i], tips[j]) if err != nil { sortErr = err return false } return selectedParent.Equal(tips[i]) }) if sortErr != nil { return nil, externalapi.StatusInvalid, sortErr } for _, tip := range tips { log.Debugf("Resolving tip %s", tip) isViolatingFinality, shouldNotify, err := csm.isViolatingFinality(stagingArea, tip) if err != nil { return nil, externalapi.StatusInvalid, err } if isViolatingFinality { if shouldNotify { //TODO: Send finality conflict notification log.Warnf("Skipping %s tip resolution because it violates finality", tip) } continue } status, err := csm.blockStatusStore.Get(csm.databaseContext, stagingArea, tip) if err != nil { return nil, externalapi.StatusInvalid, err } if status == externalapi.StatusUTXOValid || status == externalapi.StatusUTXOPendingVerification { return tip, status, nil } } return nil, externalapi.StatusInvalid, nil } func (csm *consensusStateManager) getLowerTips(stagingArea *model.StagingArea, pendingTip *externalapi.DomainHash) ([]*externalapi.DomainHash, error) { tips, err := csm.consensusStateStore.Tips(stagingArea, csm.databaseContext) if err != nil { return nil, err } lowerTips := []*externalapi.DomainHash{pendingTip} for _, tip := range tips { if tip.Equal(pendingTip) { continue } selectedParent, err := csm.ghostdagManager.ChooseSelectedParent(stagingArea, tip, pendingTip) if err != nil { return nil, err } if selectedParent.Equal(pendingTip) { lowerTips = append(lowerTips, tip) } } return lowerTips, nil } func (csm *consensusStateManager) ResolveVirtual(maxBlocksToResolve uint64) (*externalapi.VirtualChangeSet, bool, error) { onEnd := logger.LogAndMeasureExecutionTime(log, "csm.ResolveVirtual") defer onEnd() readStagingArea := model.NewStagingArea() pendingTip, pendingTipStatus, err := csm.findNextPendingTip(readStagingArea) if err != nil { return nil, false, err } if pendingTip == nil { log.Warnf("Non of the DAG tips are valid") return nil, true, nil } prevVirtualSelectedParent, err := csm.virtualSelectedParent(readStagingArea) if err != nil { return nil, false, err } if pendingTipStatus == externalapi.StatusUTXOValid && prevVirtualSelectedParent.Equal(pendingTip) { return nil, true, nil } // Resolve a chunk from the pending chain resolveStagingArea := model.NewStagingArea() unverifiedBlocks, err := csm.getUnverifiedChainBlocks(resolveStagingArea, pendingTip) if err != nil { return nil, false, err } intermediateTip := pendingTip // Too many blocks to verify, so we only process a chunk and return if maxBlocksToResolve != 0 && uint64(len(unverifiedBlocks)) > maxBlocksToResolve { intermediateTipIndex := uint64(len(unverifiedBlocks)) - maxBlocksToResolve intermediateTip = unverifiedBlocks[intermediateTipIndex] isNewVirtualSelectedParent, err := csm.isNewSelectedTip(readStagingArea, intermediateTip, prevVirtualSelectedParent) if err != nil { return nil, false, err } // We must find an intermediate tip which wins previous virtual selected parent // even if we process more than `maxBlocksToResolve` for that. // Otherwise, internal UTXO diff logic gets all messed up for !isNewVirtualSelectedParent { if intermediateTipIndex == 0 { return nil, false, errors.Errorf( "Expecting the pending tip %s to overcome the previous selected parent %s", pendingTip, prevVirtualSelectedParent) } intermediateTipIndex-- intermediateTip = unverifiedBlocks[intermediateTipIndex] isNewVirtualSelectedParent, err = csm.isNewSelectedTip(readStagingArea, intermediateTip, prevVirtualSelectedParent) if err != nil { return nil, false, err } } log.Debugf("Has more than %d blocks to resolve. Changing the resolve tip to %s", maxBlocksToResolve, intermediateTip) } intermediateTipStatus, reversalData, err := csm.resolveBlockStatus( resolveStagingArea, intermediateTip, true) if err != nil { return nil, false, err } if intermediateTipStatus == externalapi.StatusUTXOValid { err = staging.CommitAllChanges(csm.databaseContext, resolveStagingArea) if err != nil { return nil, false, err } if reversalData != nil { err = csm.ReverseUTXODiffs(intermediateTip, reversalData) if err != nil { return nil, false, err } } } isActualTip := intermediateTip.Equal(pendingTip) isCompletelyResolved := isActualTip && intermediateTipStatus == externalapi.StatusUTXOValid updateVirtualStagingArea := model.NewStagingArea() // If `isCompletelyResolved`, set virtual correctly with all tips which have less blue work than pending virtualTipCandidates := []*externalapi.DomainHash{intermediateTip} if isCompletelyResolved { lowerTips, err := csm.getLowerTips(readStagingArea, pendingTip) if err != nil { return nil, false, err } log.Debugf("Picking virtual parents from relevant tips len: %d", len(lowerTips)) virtualTipCandidates, err = csm.pickVirtualParents(readStagingArea, lowerTips) if err != nil { return nil, false, err } log.Debugf("Picked virtual parents: %s", virtualTipCandidates) } virtualUTXODiff, err := csm.updateVirtualWithParents(updateVirtualStagingArea, virtualTipCandidates) if err != nil { return nil, false, err } err = staging.CommitAllChanges(csm.databaseContext, updateVirtualStagingArea) if err != nil { return nil, false, err } selectedParentChainChanges, err := csm.dagTraversalManager. CalculateChainPath(updateVirtualStagingArea, prevVirtualSelectedParent, pendingTip) if err != nil { return nil, false, err } virtualParents, err := csm.dagTopologyManager.Parents(updateVirtualStagingArea, model.VirtualBlockHash) if err != nil { return nil, false, err } return &externalapi.VirtualChangeSet{ VirtualSelectedParentChainChanges: selectedParentChainChanges, VirtualUTXODiff: virtualUTXODiff, VirtualParents: virtualParents, }, isCompletelyResolved, nil }