Fixes a deep bug in the resolve virtual process

This commit is contained in:
msutton 2022-07-08 15:52:12 +03:00
parent 52e5aa9fab
commit d17f52d87a
3 changed files with 119 additions and 52 deletions

View File

@ -5,17 +5,15 @@ import (
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi" "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/infrastructure/logger" "github.com/kaspanet/kaspad/infrastructure/logger"
"github.com/kaspanet/kaspad/util/staging" "github.com/kaspanet/kaspad/util/staging"
"github.com/pkg/errors"
"sort" "sort"
) )
func (csm *consensusStateManager) ResolveVirtual(maxBlocksToResolve uint64) (*externalapi.VirtualChangeSet, bool, error) { func (csm *consensusStateManager) findNextPendingTip() (*externalapi.DomainHash, externalapi.BlockStatus, error) {
onEnd := logger.LogAndMeasureExecutionTime(log, "csm.ResolveVirtual")
defer onEnd()
readStagingArea := model.NewStagingArea() readStagingArea := model.NewStagingArea()
tips, err := csm.consensusStateStore.Tips(readStagingArea, csm.databaseContext) tips, err := csm.consensusStateStore.Tips(readStagingArea, csm.databaseContext)
if err != nil { if err != nil {
return nil, false, err return nil, externalapi.StatusInvalid, err
} }
var sortErr error var sortErr error
@ -29,16 +27,14 @@ func (csm *consensusStateManager) ResolveVirtual(maxBlocksToResolve uint64) (*ex
return selectedParent.Equal(tips[i]) return selectedParent.Equal(tips[i])
}) })
if sortErr != nil { if sortErr != nil {
return nil, false, sortErr return nil, externalapi.StatusInvalid, sortErr
} }
var selectedTip *externalapi.DomainHash
isCompletelyResolved := true
for _, tip := range tips { for _, tip := range tips {
log.Debugf("Resolving tip %s", tip) log.Debugf("Resolving tip %s", tip)
isViolatingFinality, shouldNotify, err := csm.isViolatingFinality(readStagingArea, tip) isViolatingFinality, shouldNotify, err := csm.isViolatingFinality(readStagingArea, tip)
if err != nil { if err != nil {
return nil, false, err return nil, externalapi.StatusInvalid, err
} }
if isViolatingFinality { if isViolatingFinality {
@ -49,55 +45,125 @@ func (csm *consensusStateManager) ResolveVirtual(maxBlocksToResolve uint64) (*ex
continue continue
} }
resolveStagingArea := model.NewStagingArea() status, err := csm.blockStatusStore.Get(csm.databaseContext, readStagingArea, tip)
unverifiedBlocks, err := csm.getUnverifiedChainBlocks(resolveStagingArea, tip)
if err != nil { if err != nil {
return nil, false, err return nil, externalapi.StatusInvalid, err
} }
if status == externalapi.StatusUTXOValid || status == externalapi.StatusUTXOPendingVerification {
resolveTip := tip return tip, status, nil
hasMoreUnverifiedThanMax := maxBlocksToResolve != 0 && uint64(len(unverifiedBlocks)) > maxBlocksToResolve
if hasMoreUnverifiedThanMax {
resolveTip = unverifiedBlocks[uint64(len(unverifiedBlocks))-maxBlocksToResolve]
log.Debugf("Has more than %d blocks to resolve. Changing the resolve tip to %s", maxBlocksToResolve, resolveTip)
}
blockStatus, reversalData, err := csm.resolveBlockStatus(resolveStagingArea, resolveTip, true)
if err != nil {
return nil, false, err
}
if blockStatus == externalapi.StatusUTXOValid {
selectedTip = resolveTip
isCompletelyResolved = !hasMoreUnverifiedThanMax
err = staging.CommitAllChanges(csm.databaseContext, resolveStagingArea)
if err != nil {
return nil, false, err
}
if reversalData != nil {
err = csm.ReverseUTXODiffs(resolveTip, reversalData)
if err != nil {
return nil, false, err
}
}
break
} }
} }
if selectedTip == nil { return nil, externalapi.StatusInvalid, nil
log.Warnf("Non of the DAG tips are valid") }
return &externalapi.VirtualChangeSet{}, true, nil
}
oldVirtualGHOSTDAGData, err := csm.ghostdagDataStore.Get(csm.databaseContext, readStagingArea, model.VirtualBlockHash, false) func (csm *consensusStateManager) ResolveVirtual(maxBlocksToResolve uint64) (*externalapi.VirtualChangeSet, bool, error) {
onEnd := logger.LogAndMeasureExecutionTime(log, "csm.ResolveVirtual")
defer onEnd()
readStagingArea := model.NewStagingArea()
/*
Algo (begin resolve):
Go over tips by GHOSTDAG select parent order ignoring UTXO disqualified blocks and finality violating blocks
Set pending tip to the first tip
if this tip is already UTXO valid, finalize virtual state and return
if the tip is UTXO pending, find the earliest UTXO pending block in its chain and set it as position
set the tip as resolving virtual pending
try resolving a chunk up the chain from position to pending tip
Algo (continue resolve):
Start from position and try to continue resolving another chunk up the chain to pending tip
If we encounter a UTXO disqualified block, we should
mark the whole chain up to pending tip as disqualified
set position and pending tip to the next candidate chain
return and let the next call continue the processing
If we reach the tip, and it is valid, only then set virtual parents to DAG tips, and clear resolving state
*/
pendingTip, pendingTipStatus, err := csm.findNextPendingTip()
if err != nil { if err != nil {
return nil, false, err return nil, false, err
} }
if pendingTip == nil {
log.Warnf("Non of the DAG tips are valid")
return &externalapi.VirtualChangeSet{}, true, nil
}
prevVirtualSelectedParent, err := csm.virtualSelectedParent(readStagingArea)
if err != nil {
return nil, false, err
}
if pendingTipStatus == externalapi.StatusUTXOValid && prevVirtualSelectedParent.Equal(pendingTip) {
return &externalapi.VirtualChangeSet{}, 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() updateVirtualStagingArea := model.NewStagingArea()
virtualUTXODiff, err := csm.updateVirtualWithParents(updateVirtualStagingArea, []*externalapi.DomainHash{selectedTip})
// TODO: if `isCompletelyResolved`, set virtual correctly with all tips which have less blue work than pending
virtualUTXODiff, err := csm.updateVirtualWithParents(updateVirtualStagingArea, []*externalapi.DomainHash{intermediateTip})
if err != nil { if err != nil {
return nil, false, err return nil, false, err
} }
@ -107,13 +173,14 @@ func (csm *consensusStateManager) ResolveVirtual(maxBlocksToResolve uint64) (*ex
return nil, false, err return nil, false, err
} }
// TODO: why was `readStagingArea` used here ?
selectedParentChainChanges, err := csm.dagTraversalManager. selectedParentChainChanges, err := csm.dagTraversalManager.
CalculateChainPath(readStagingArea, oldVirtualGHOSTDAGData.SelectedParent(), selectedTip) CalculateChainPath(updateVirtualStagingArea, prevVirtualSelectedParent, pendingTip)
if err != nil { if err != nil {
return nil, false, err return nil, false, err
} }
virtualParents, err := csm.dagTopologyManager.Parents(readStagingArea, model.VirtualBlockHash) virtualParents, err := csm.dagTopologyManager.Parents(updateVirtualStagingArea, model.VirtualBlockHash)
if err != nil { if err != nil {
return nil, false, err return nil, false, err
} }

View File

@ -233,7 +233,7 @@ func (csm *consensusStateManager) resolveSingleBlockStatus(stagingArea *model.St
return externalapi.StatusUTXOValid, nil, nil return externalapi.StatusUTXOValid, nil, nil
} }
oldSelectedTip, err := csm.selectedTip(stagingArea) oldSelectedTip, err := csm.virtualSelectedParent(stagingArea)
if err != nil { if err != nil {
return 0, nil, err return 0, nil, err
} }
@ -298,7 +298,7 @@ func (csm *consensusStateManager) isNewSelectedTip(stagingArea *model.StagingAre
return blockHash.Equal(newSelectedTip), nil return blockHash.Equal(newSelectedTip), nil
} }
func (csm *consensusStateManager) selectedTip(stagingArea *model.StagingArea) (*externalapi.DomainHash, error) { func (csm *consensusStateManager) virtualSelectedParent(stagingArea *model.StagingArea) (*externalapi.DomainHash, error) {
virtualGHOSTDAGData, err := csm.ghostdagDataStore.Get(csm.databaseContext, stagingArea, model.VirtualBlockHash, false) virtualGHOSTDAGData, err := csm.ghostdagDataStore.Get(csm.databaseContext, stagingArea, model.VirtualBlockHash, false)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -110,7 +110,7 @@ func (csm *consensusStateManager) updateSelectedTipUTXODiff(
onEnd := logger.LogAndMeasureExecutionTime(log, "updateSelectedTipUTXODiff") onEnd := logger.LogAndMeasureExecutionTime(log, "updateSelectedTipUTXODiff")
defer onEnd() defer onEnd()
selectedTip, err := csm.selectedTip(stagingArea) selectedTip, err := csm.virtualSelectedParent(stagingArea)
if err != nil { if err != nil {
return err return err
} }