From bc0e74e3570d4a0b74ca676193acfb060e47de35 Mon Sep 17 00:00:00 2001 From: msutton Date: Fri, 8 Jul 2022 16:04:42 +0300 Subject: [PATCH] Be more defensive at resolving virtual when adding a block --- domain/consensus/consensus.go | 41 ++++++++----------- .../consensus/model/externalapi/consensus.go | 2 +- .../consensusstatemanager/resolve.go | 18 -------- 3 files changed, 18 insertions(+), 43 deletions(-) diff --git a/domain/consensus/consensus.go b/domain/consensus/consensus.go index 85a638cc3..ef45183ed 100644 --- a/domain/consensus/consensus.go +++ b/domain/consensus/consensus.go @@ -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) diff --git a/domain/consensus/model/externalapi/consensus.go b/domain/consensus/model/externalapi/consensus.go index 89f8056eb..d7aadc203 100644 --- a/domain/consensus/model/externalapi/consensus.go +++ b/domain/consensus/model/externalapi/consensus.go @@ -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 diff --git a/domain/consensus/processes/consensusstatemanager/resolve.go b/domain/consensus/processes/consensusstatemanager/resolve.go index 22fb3b1a6..5c7084484 100644 --- a/domain/consensus/processes/consensusstatemanager/resolve.go +++ b/domain/consensus/processes/consensusstatemanager/resolve.go @@ -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