Be more defensive at resolving virtual when adding a block

This commit is contained in:
msutton 2022-07-08 16:04:42 +03:00
parent d17f52d87a
commit bc0e74e357
3 changed files with 18 additions and 43 deletions

View File

@ -61,7 +61,6 @@ type consensus struct {
blocksWithTrustedDataDAAWindowStore model.BlocksWithTrustedDataDAAWindowStore
consensusEventsChan chan externalapi.ConsensusEvent
virtualNotUpdated bool
}
func (s *consensus) ValidateAndInsertBlockWithTrustedData(block *externalapi.BlockWithTrustedData, validateUTXO bool) error {
@ -194,11 +193,24 @@ func (s *consensus) BuildBlockTemplate(coinbaseData *externalapi.DomainCoinbaseD
// ValidateAndInsertBlock validates the given block and, if valid, applies it
// to the current state
func (s *consensus) ValidateAndInsertBlock(block *externalapi.DomainBlock, shouldValidateAgainstUTXO bool) error {
func (s *consensus) ValidateAndInsertBlock(block *externalapi.DomainBlock, updateVirtual bool) error {
if updateVirtual {
// Make sure virtual is resolved before adding the new block
err := s.ResolveVirtual(nil)
if err != nil {
return err
}
}
return s.validateAndInsertBlockWithLock(block, updateVirtual)
}
func (s *consensus) validateAndInsertBlockWithLock(block *externalapi.DomainBlock, updateVirtual bool) error {
s.lock.Lock()
defer s.lock.Unlock()
_, err := s.validateAndInsertBlockNoLock(block, shouldValidateAgainstUTXO)
_, err := s.validateAndInsertBlockNoLock(block, updateVirtual)
if err != nil {
return err
}
@ -206,29 +218,11 @@ func (s *consensus) ValidateAndInsertBlock(block *externalapi.DomainBlock, shoul
}
func (s *consensus) validateAndInsertBlockNoLock(block *externalapi.DomainBlock, updateVirtual bool) (*externalapi.VirtualChangeSet, error) {
// If virtual is in non-updated state, and the caller requests updating virtual -- then we must first
// resolve virtual so that the new block can be fully processed properly
if updateVirtual && s.virtualNotUpdated {
for s.virtualNotUpdated {
// We use 10000 << finality interval. See comment in `ResolveVirtual`.
// We give up responsiveness of consensus in this rare case.
_, err := s.resolveVirtualNoLock(10000) // Note `s.virtualNotUpdated` is updated within the call
if err != nil {
return nil, err
}
}
}
virtualChangeSet, blockStatus, err := s.blockProcessor.ValidateAndInsertBlock(block, updateVirtual)
if err != nil {
return nil, err
}
// If block has a body, and yet virtual was not updated -- signify that virtual is in non-updated state
if !updateVirtual && blockStatus != externalapi.StatusHeaderOnly {
s.virtualNotUpdated = true
}
err = s.sendBlockAddedEvent(block, blockStatus)
if err != nil {
return nil, err
@ -257,7 +251,7 @@ func (s *consensus) sendBlockAddedEvent(block *externalapi.DomainBlock, blockSta
}
func (s *consensus) sendVirtualChangedEvent(virtualChangeSet *externalapi.VirtualChangeSet, wasVirtualUpdated bool) error {
if !wasVirtualUpdated || s.consensusEventsChan == nil {
if !wasVirtualUpdated || s.consensusEventsChan == nil || virtualChangeSet == nil {
return nil
}
@ -895,7 +889,7 @@ func (s *consensus) ResolveVirtual(progressReportCallback func(uint64, uint64))
}
for i := 0; ; i++ {
if i%10 == 0 {
if i%10 == 0 && progressReportCallback != nil {
virtualDAAScore, err := s.GetVirtualDAAScore()
if err != nil {
return err
@ -931,7 +925,6 @@ func (s *consensus) resolveVirtualNoLock(maxBlocksToResolve uint64) (bool, error
if err != nil {
return false, err
}
s.virtualNotUpdated = !isCompletelyResolved
stagingArea := model.NewStagingArea()
err = s.pruningManager.UpdatePruningPointByVirtual(stagingArea)

View File

@ -5,7 +5,7 @@ type Consensus interface {
Init(skipAddingGenesis bool) error
BuildBlock(coinbaseData *DomainCoinbaseData, transactions []*DomainTransaction) (*DomainBlock, error)
BuildBlockTemplate(coinbaseData *DomainCoinbaseData, transactions []*DomainTransaction) (*DomainBlockTemplate, error)
ValidateAndInsertBlock(block *DomainBlock, shouldValidateAgainstUTXO bool) error
ValidateAndInsertBlock(block *DomainBlock, updateVirtual bool) error
ValidateAndInsertBlockWithTrustedData(block *BlockWithTrustedData, validateUTXO bool) error
ValidateTransactionAndPopulateWithConsensusData(transaction *DomainTransaction) error
ImportPruningPoints(pruningPoints []BlockHeader) error

View File

@ -63,24 +63,6 @@ func (csm *consensusStateManager) ResolveVirtual(maxBlocksToResolve uint64) (*ex
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 {
return nil, false, err