From 04330e71b943f2ce45e7755b41de8f03a9c18890 Mon Sep 17 00:00:00 2001 From: Ori Newman Date: Thu, 17 Dec 2020 17:57:51 +0200 Subject: [PATCH] Pruning fixes (#1243) * Pruning related fixes * Rename setBlockStatus->setBlockStatusAfterBlockValidation * Rename StatusValid->StatusUTXOValid * Add comment * Fix typo * Rename hasValidatedOnlyHeader->hasValidatedHeader * Rename checkBlockBodiesExist->checkParentBlockBodiesExist * Add comments and logs * Adding logs * Add logs and assert * Add comment * Fix typo * Fix log --- domain/consensus/consensus_test.go | 4 +- domain/consensus/finality_test.go | 20 +-- .../model/externalapi/blockstatus.go | 6 +- .../interface_processes_blockvalidator.go | 2 +- .../blockprocessor/blockprocessor.go | 2 +- .../blockprocessor/validateandinsertblock.go | 115 +++++++++++++----- .../validateandinsertpruningpoint.go | 17 ++- .../processes/blockprocessor/validateblock.go | 24 ++-- .../blockvalidator/block_body_in_context.go | 62 +++++++++- .../processes/blockvalidator/proof_of_work.go | 30 +---- .../add_block_to_virtual.go | 2 +- .../calculate_past_utxo.go | 4 +- .../pick_virtual_parents.go | 2 +- .../resolve_block_status.go | 6 +- .../resolve_block_status_test.go | 16 +-- .../update_pruning_utxo_set.go | 60 +++++---- 16 files changed, 243 insertions(+), 129 deletions(-) diff --git a/domain/consensus/consensus_test.go b/domain/consensus/consensus_test.go index d22a6df3e..d262ddb6f 100644 --- a/domain/consensus/consensus_test.go +++ b/domain/consensus/consensus_test.go @@ -62,8 +62,8 @@ func TestConsensus_GetBlockInfo(t *testing.T) { if !info.Exists { t.Fatal("The block is missing") } - if info.BlockStatus != externalapi.StatusValid { - t.Fatalf("Expected block status: %s, instead got: %s", externalapi.StatusValid, info.BlockStatus) + if info.BlockStatus != externalapi.StatusUTXOValid { + t.Fatalf("Expected block status: %s, instead got: %s", externalapi.StatusUTXOValid, info.BlockStatus) } }) diff --git a/domain/consensus/finality_test.go b/domain/consensus/finality_test.go index d9ffc7f41..0c5aa3189 100644 --- a/domain/consensus/finality_test.go +++ b/domain/consensus/finality_test.go @@ -55,9 +55,9 @@ func TestFinality(t *testing.T) { if err != nil { t.Fatalf("TestFinality: Block #%d failed to get info: %+v", i, err) } - if blockInfo.BlockStatus != externalapi.StatusValid { + if blockInfo.BlockStatus != externalapi.StatusUTXOValid { t.Fatalf("Block #%d in main chain expected to have status '%s', but got '%s'", - i, externalapi.StatusValid, blockInfo.BlockStatus) + i, externalapi.StatusUTXOValid, blockInfo.BlockStatus) } } @@ -99,9 +99,9 @@ func TestFinality(t *testing.T) { } else if !blockInfo.Exists { t.Fatalf("TestFinality: Failed getting block info, doesn't exists") } - if blockInfo.BlockStatus != externalapi.StatusValid { + if blockInfo.BlockStatus != externalapi.StatusUTXOValid { t.Fatalf("TestFinality: Overtaking block in side-chain expected to have status '%s', but got '%s'", - externalapi.StatusValid, blockInfo.BlockStatus) + externalapi.StatusUTXOValid, blockInfo.BlockStatus) } selectedTip, err := consensus.GetVirtualSelectedParent() if err != nil { @@ -387,14 +387,14 @@ func TestBoundedMergeDepth(t *testing.T) { } // Lets validate the status of all the interesting blocks - if getStatus(consensusReal, pointAtBlueKosherizing) != externalapi.StatusValid { - t.Fatalf("TestBoundedMergeDepth: pointAtBlueKosherizing expected status '%s' but got '%s'", externalapi.StatusValid, getStatus(consensusReal, pointAtBlueKosherizing)) + if getStatus(consensusReal, pointAtBlueKosherizing) != externalapi.StatusUTXOValid { + t.Fatalf("TestBoundedMergeDepth: pointAtBlueKosherizing expected status '%s' but got '%s'", externalapi.StatusUTXOValid, getStatus(consensusReal, pointAtBlueKosherizing)) } if getStatus(consensusReal, pointAtRedKosherizing) != externalapi.StatusInvalid { t.Fatalf("TestBoundedMergeDepth: pointAtRedKosherizing expected status '%s' but got '%s'", externalapi.StatusInvalid, getStatus(consensusReal, pointAtRedKosherizing)) } - if getStatus(consensusReal, transitiveBlueKosherizing) != externalapi.StatusValid { - t.Fatalf("TestBoundedMergeDepth: transitiveBlueKosherizing expected status '%s' but got '%s'", externalapi.StatusValid, getStatus(consensusReal, transitiveBlueKosherizing)) + if getStatus(consensusReal, transitiveBlueKosherizing) != externalapi.StatusUTXOValid { + t.Fatalf("TestBoundedMergeDepth: transitiveBlueKosherizing expected status '%s' but got '%s'", externalapi.StatusUTXOValid, getStatus(consensusReal, transitiveBlueKosherizing)) } if getStatus(consensusReal, mergeDepthViolatingBlockBottom) != externalapi.StatusInvalid { t.Fatalf("TestBoundedMergeDepth: mergeDepthViolatingBlockBottom expected status '%s' but got '%s'", externalapi.StatusInvalid, getStatus(consensusReal, mergeDepthViolatingBlockBottom)) @@ -412,8 +412,8 @@ func TestBoundedMergeDepth(t *testing.T) { } } for i, b := range selectedChain { - if getStatus(consensusReal, b) != externalapi.StatusValid { - t.Fatalf("selectedChain[%d] expected status '%s' but got '%s'", i, externalapi.StatusValid, getStatus(consensusReal, b)) + if getStatus(consensusReal, b) != externalapi.StatusUTXOValid { + t.Fatalf("selectedChain[%d] expected status '%s' but got '%s'", i, externalapi.StatusUTXOValid, getStatus(consensusReal, b)) } } }) diff --git a/domain/consensus/model/externalapi/blockstatus.go b/domain/consensus/model/externalapi/blockstatus.go index 8fc15cf3f..2c2fd408a 100644 --- a/domain/consensus/model/externalapi/blockstatus.go +++ b/domain/consensus/model/externalapi/blockstatus.go @@ -12,8 +12,8 @@ const ( // StatusInvalid indicates that the block is invalid. StatusInvalid BlockStatus = iota - // StatusValid indicates that the block has been fully validated. - StatusValid + // StatusUTXOValid indicates the block is valid from any UTXO related aspects and has passed all the other validations as well. + StatusUTXOValid // StatusUTXOPendingVerification indicates that the block is pending verification against its past UTXO-Set, either // because it was not yet verified since the block was never in the selected parent chain, or if the @@ -29,7 +29,7 @@ const ( var blockStatusStrings = map[BlockStatus]string{ StatusInvalid: "Invalid", - StatusValid: "Valid", + StatusUTXOValid: "Valid", StatusUTXOPendingVerification: "UTXOPendingVerification", StatusDisqualifiedFromChain: "DisqualifiedFromChain", StatusHeaderOnly: "HeaderOnly", diff --git a/domain/consensus/model/interface_processes_blockvalidator.go b/domain/consensus/model/interface_processes_blockvalidator.go index 12dde50d0..c7250cc0b 100644 --- a/domain/consensus/model/interface_processes_blockvalidator.go +++ b/domain/consensus/model/interface_processes_blockvalidator.go @@ -10,6 +10,6 @@ type BlockValidator interface { ValidateHeaderInIsolation(blockHash *externalapi.DomainHash) error ValidateBodyInIsolation(blockHash *externalapi.DomainHash) error ValidateHeaderInContext(blockHash *externalapi.DomainHash) error - ValidateBodyInContext(blockHash *externalapi.DomainHash) error + ValidateBodyInContext(blockHash *externalapi.DomainHash, isPruningPoint bool) error ValidatePruningPointViolationAndProofOfWorkAndDifficulty(blockHash *externalapi.DomainHash) error } diff --git a/domain/consensus/processes/blockprocessor/blockprocessor.go b/domain/consensus/processes/blockprocessor/blockprocessor.go index e1a6c8548..cb007a39a 100644 --- a/domain/consensus/processes/blockprocessor/blockprocessor.go +++ b/domain/consensus/processes/blockprocessor/blockprocessor.go @@ -126,7 +126,7 @@ func (bp *blockProcessor) ValidateAndInsertBlock(block *externalapi.DomainBlock) onEnd := logger.LogAndMeasureExecutionTime(log, "ValidateAndInsertBlock") defer onEnd() - return bp.validateAndInsertBlock(block) + return bp.validateAndInsertBlock(block, false) } func (bp *blockProcessor) ValidateAndInsertPruningPoint(newPruningPoint *externalapi.DomainBlock, serializedUTXOSet []byte) error { diff --git a/domain/consensus/processes/blockprocessor/validateandinsertblock.go b/domain/consensus/processes/blockprocessor/validateandinsertblock.go index 56865f462..63db91e55 100644 --- a/domain/consensus/processes/blockprocessor/validateandinsertblock.go +++ b/domain/consensus/processes/blockprocessor/validateandinsertblock.go @@ -11,19 +11,59 @@ import ( "github.com/pkg/errors" ) -func (bp *blockProcessor) validateAndInsertBlock(block *externalapi.DomainBlock) (*externalapi.BlockInsertionResult, error) { +func (bp *blockProcessor) setBlockStatusAfterBlockValidation(block *externalapi.DomainBlock, isPruningPoint bool) error { + blockHash := consensushashing.BlockHash(block) + + exists, err := bp.blockStatusStore.Exists(bp.databaseContext, blockHash) + if err != nil { + return err + } + if exists { + status, err := bp.blockStatusStore.Get(bp.databaseContext, blockHash) + if err != nil { + return err + } + + if status == externalapi.StatusUTXOValid { + // A block cannot have status StatusUTXOValid just after finishing bp.validateBlock, because + // if it's the case it should have been rejected as duplicate block. + // The only exception is the pruning point because its status is manually set before inserting + // the block. + if !isPruningPoint { + return errors.Errorf("block %s that is not the pruning point is not expected to be valid "+ + "before adding to to the consensus state manager", blockHash) + } + log.Tracef("Block %s is the pruning point and has status %s, so leaving its status untouched", + blockHash, status) + return nil + } + } + + isHeaderOnlyBlock := isHeaderOnlyBlock(block) + if isHeaderOnlyBlock { + log.Tracef("Block %s is a header-only block so setting its status as %s", + blockHash, externalapi.StatusHeaderOnly) + bp.blockStatusStore.Stage(blockHash, externalapi.StatusHeaderOnly) + } else { + log.Tracef("Block %s has body so setting its status as %s", + blockHash, externalapi.StatusUTXOPendingVerification) + bp.blockStatusStore.Stage(blockHash, externalapi.StatusUTXOPendingVerification) + } + + return nil +} + +func (bp *blockProcessor) validateAndInsertBlock(block *externalapi.DomainBlock, isPruningPoint bool) (*externalapi.BlockInsertionResult, error) { blockHash := consensushashing.HeaderHash(block.Header) - err := bp.validateBlock(block) + err := bp.validateBlock(block, isPruningPoint) if err != nil { bp.discardAllChanges() return nil, err } - isHeaderOnlyBlock := isHeaderOnlyBlock(block) - if isHeaderOnlyBlock { - bp.blockStatusStore.Stage(blockHash, externalapi.StatusHeaderOnly) - } else { - bp.blockStatusStore.Stage(blockHash, externalapi.StatusUTXOPendingVerification) + err = bp.setBlockStatusAfterBlockValidation(block, isPruningPoint) + if err != nil { + return nil, err } // Block validations passed, save whatever DAG data was @@ -34,8 +74,8 @@ func (bp *blockProcessor) validateAndInsertBlock(block *externalapi.DomainBlock) } var oldHeadersSelectedTip *externalapi.DomainHash - isGenesis := *blockHash != *bp.genesisHash - if isGenesis { + isGenesis := *blockHash == *bp.genesisHash + if !isGenesis { var err error oldHeadersSelectedTip, err = bp.headersSelectedTipStore.HeadersSelectedTip(bp.databaseContext) if err != nil { @@ -49,15 +89,21 @@ func (bp *blockProcessor) validateAndInsertBlock(block *externalapi.DomainBlock) } var selectedParentChainChanges *externalapi.SelectedParentChainChanges + isHeaderOnlyBlock := isHeaderOnlyBlock(block) if !isHeaderOnlyBlock { - // Attempt to add the block to the virtual - selectedParentChainChanges, err = bp.consensusStateManager.AddBlock(blockHash) - if err != nil { - return nil, err + // There's no need to update the consensus state manager when + // processing the pruning point since it was already handled + // in consensusStateManager.UpdatePruningPoint + if !isPruningPoint { + // Attempt to add the block to the virtual + selectedParentChainChanges, err = bp.consensusStateManager.AddBlock(blockHash) + if err != nil { + return nil, err + } } } - if isGenesis { + if !isGenesis { err := bp.updateReachabilityReindexRoot(oldHeadersSelectedTip) if err != nil { return nil, err @@ -137,14 +183,22 @@ func (bp *blockProcessor) checkBlockStatus(block *externalapi.DomainBlock) error return errors.Wrapf(ruleerrors.ErrKnownInvalid, "block %s is a known invalid block", hash) } - isBlockBodyAfterBlockHeader := !isHeaderOnlyBlock && status == externalapi.StatusHeaderOnly - if !isBlockBodyAfterBlockHeader { - return errors.Wrapf(ruleerrors.ErrDuplicateBlock, "block %s already exists", hash) - } - - isDuplicateHeader := isHeaderOnlyBlock && status == externalapi.StatusHeaderOnly - if isDuplicateHeader { - return errors.Wrapf(ruleerrors.ErrDuplicateBlock, "block %s already exists", hash) + if !isHeaderOnlyBlock { + hasBlock, err := bp.blockStore.HasBlock(bp.databaseContext, hash) + if err != nil { + return err + } + if hasBlock { + return errors.Wrapf(ruleerrors.ErrDuplicateBlock, "block %s already exists", hash) + } + } else { + hasHeader, err := bp.blockHeaderStore.HasBlockHeader(bp.databaseContext, hash) + if err != nil { + return err + } + if hasHeader { + return errors.Wrapf(ruleerrors.ErrDuplicateBlock, "block %s header already exists", hash) + } } return nil @@ -153,12 +207,12 @@ func (bp *blockProcessor) checkBlockStatus(block *externalapi.DomainBlock) error func (bp *blockProcessor) validatePreProofOfWork(block *externalapi.DomainBlock) error { blockHash := consensushashing.BlockHash(block) - hasValidatedOnlyHeader, err := bp.hasValidatedOnlyHeader(blockHash) + hasValidatedHeader, err := bp.hasValidatedHeader(blockHash) if err != nil { return err } - if hasValidatedOnlyHeader { + if hasValidatedHeader { log.Debugf("Block %s header was already validated, so skip the rest of validatePreProofOfWork", blockHash) return nil } @@ -170,7 +224,7 @@ func (bp *blockProcessor) validatePreProofOfWork(block *externalapi.DomainBlock) return nil } -func (bp *blockProcessor) validatePostProofOfWork(block *externalapi.DomainBlock) error { +func (bp *blockProcessor) validatePostProofOfWork(block *externalapi.DomainBlock, isPruningPoint bool) error { blockHash := consensushashing.BlockHash(block) isHeaderOnlyBlock := isHeaderOnlyBlock(block) @@ -182,7 +236,7 @@ func (bp *blockProcessor) validatePostProofOfWork(block *externalapi.DomainBlock } } - hasValidatedHeader, err := bp.hasValidatedOnlyHeader(blockHash) + hasValidatedHeader, err := bp.hasValidatedHeader(blockHash) if err != nil { return err } @@ -195,7 +249,7 @@ func (bp *blockProcessor) validatePostProofOfWork(block *externalapi.DomainBlock } if !isHeaderOnlyBlock { - err = bp.blockValidator.ValidateBodyInContext(blockHash) + err = bp.blockValidator.ValidateBodyInContext(blockHash, isPruningPoint) if err != nil { return err } @@ -206,7 +260,10 @@ func (bp *blockProcessor) validatePostProofOfWork(block *externalapi.DomainBlock return nil } -func (bp *blockProcessor) hasValidatedOnlyHeader(blockHash *externalapi.DomainHash) (bool, error) { +// hasValidatedHeader returns whether the block header was validated. It returns +// true in any case the block header was validated, whether it was validated as a +// header-only block or as a block with body. +func (bp *blockProcessor) hasValidatedHeader(blockHash *externalapi.DomainHash) (bool, error) { exists, err := bp.blockStatusStore.Exists(bp.databaseContext, blockHash) if err != nil { return false, err @@ -221,7 +278,7 @@ func (bp *blockProcessor) hasValidatedOnlyHeader(blockHash *externalapi.DomainHa return false, err } - return status == externalapi.StatusHeaderOnly, nil + return status != externalapi.StatusInvalid, nil } func (bp *blockProcessor) discardAllChanges() { diff --git a/domain/consensus/processes/blockprocessor/validateandinsertpruningpoint.go b/domain/consensus/processes/blockprocessor/validateandinsertpruningpoint.go index 902c8ec38..c2e60cc01 100644 --- a/domain/consensus/processes/blockprocessor/validateandinsertpruningpoint.go +++ b/domain/consensus/processes/blockprocessor/validateandinsertpruningpoint.go @@ -24,16 +24,27 @@ func (bp *blockProcessor) validateAndInsertPruningPoint(newPruningPoint *externa // We have to validate the pruning point block before we set the new pruning point in consensusStateManager. log.Infof("Validating the new pruning point %s", newPruningPointHash) - err = bp.validateBlockAndDiscardChanges(newPruningPoint) + err = bp.validateBlockAndDiscardChanges(newPruningPoint, true) if err != nil { return err } + log.Infof("Updating consensus state manager according to the new pruning point %s", newPruningPointHash) err = bp.consensusStateManager.UpdatePruningPoint(newPruningPoint, serializedUTXOSet) if err != nil { return err } - _, err = bp.ValidateAndInsertBlock(newPruningPoint) - return err + log.Infof("Inserting the new pruning point %s", newPruningPointHash) + _, err = bp.validateAndInsertBlock(newPruningPoint, true) + if err != nil { + if errors.As(err, &ruleerrors.RuleError{}) { + // This should never happen because we already validated the block with bp.validateBlockAndDiscardChanges. + // We use Errorf so it won't be identified later on to be a rule error and will eventually cause + // the program to panic. + return errors.Errorf("validateAndInsertBlock returned unexpected rule error while processing "+ + "the pruning point: %+v", err) + } + } + return nil } diff --git a/domain/consensus/processes/blockprocessor/validateblock.go b/domain/consensus/processes/blockprocessor/validateblock.go index f7bba7966..405c8e503 100644 --- a/domain/consensus/processes/blockprocessor/validateblock.go +++ b/domain/consensus/processes/blockprocessor/validateblock.go @@ -7,12 +7,12 @@ import ( "github.com/pkg/errors" ) -func (bp *blockProcessor) validateBlockAndDiscardChanges(block *externalapi.DomainBlock) error { +func (bp *blockProcessor) validateBlockAndDiscardChanges(block *externalapi.DomainBlock, isPruningPoint bool) error { defer bp.discardAllChanges() - return bp.validateBlock(block) + return bp.validateBlock(block, isPruningPoint) } -func (bp *blockProcessor) validateBlock(block *externalapi.DomainBlock) error { +func (bp *blockProcessor) validateBlock(block *externalapi.DomainBlock, isPruningPoint bool) error { blockHash := consensushashing.HeaderHash(block.Header) log.Debugf("Validating block %s", blockHash) @@ -21,7 +21,7 @@ func (bp *blockProcessor) validateBlock(block *externalapi.DomainBlock) error { return err } - hasValidatedHeader, err := bp.hasValidatedOnlyHeader(blockHash) + hasValidatedHeader, err := bp.hasValidatedHeader(blockHash) if err != nil { return err } @@ -50,15 +50,19 @@ func (bp *blockProcessor) validateBlock(block *externalapi.DomainBlock) error { // If in-context validations fail, discard all changes and store the // block with StatusInvalid. - err = bp.validatePostProofOfWork(block) + err = bp.validatePostProofOfWork(block, isPruningPoint) if err != nil { if errors.As(err, &ruleerrors.RuleError{}) { bp.discardAllChanges() - hash := consensushashing.BlockHash(block) - bp.blockStatusStore.Stage(hash, externalapi.StatusInvalid) - commitErr := bp.commitAllChanges() - if commitErr != nil { - return commitErr + // If we got ErrMissingParents the block shouldn't be considered as invalid + // because it could be added later on when its parents are present. + if !errors.As(err, &ruleerrors.ErrMissingParents{}) { + hash := consensushashing.BlockHash(block) + bp.blockStatusStore.Stage(hash, externalapi.StatusInvalid) + commitErr := bp.commitAllChanges() + if commitErr != nil { + return commitErr + } } } return err diff --git a/domain/consensus/processes/blockvalidator/block_body_in_context.go b/domain/consensus/processes/blockvalidator/block_body_in_context.go index dd6fc4540..d16421e93 100644 --- a/domain/consensus/processes/blockvalidator/block_body_in_context.go +++ b/domain/consensus/processes/blockvalidator/block_body_in_context.go @@ -13,11 +13,69 @@ import ( // ValidateBodyInContext validates block bodies in the context of the current // consensus state -func (v *blockValidator) ValidateBodyInContext(blockHash *externalapi.DomainHash) error { +func (v *blockValidator) ValidateBodyInContext(blockHash *externalapi.DomainHash, isPruningPoint bool) error { onEnd := logger.LogAndMeasureExecutionTime(log, "ValidateBodyInContext") defer onEnd() - return v.checkBlockTransactionsFinalized(blockHash) + err := v.checkBlockTransactionsFinalized(blockHash) + if err != nil { + return err + } + + if !isPruningPoint { + err := v.checkParentBlockBodiesExist(blockHash) + if err != nil { + return err + } + } + return nil +} + +func (v *blockValidator) checkParentBlockBodiesExist(blockHash *externalapi.DomainHash) error { + missingParentHashes := []*externalapi.DomainHash{} + header, err := v.blockHeaderStore.BlockHeader(v.databaseContext, blockHash) + if err != nil { + return err + } + for _, parent := range header.ParentHashes { + hasBlock, err := v.blockStore.HasBlock(v.databaseContext, parent) + if err != nil { + return err + } + + if !hasBlock { + pruningPoint, err := v.pruningStore.PruningPoint(v.databaseContext) + if err != nil { + return err + } + + isInPastOfPruningPoint, err := v.dagTopologyManager.IsAncestorOf(parent, pruningPoint) + if err != nil { + return err + } + + // If a block parent is in the past of the pruning point + // it means its body will never be used, so it's ok if + // it's missing. + // This will usually happen during IBD when getting the blocks + // in the pruning point anticone. + if isInPastOfPruningPoint { + log.Debugf("Block %s parent %s is missing a body, but is in the past of the pruning point", + blockHash, parent) + continue + } + + log.Debugf("Block %s parent %s is missing a body", blockHash, parent) + + missingParentHashes = append(missingParentHashes, parent) + } + } + + if len(missingParentHashes) > 0 { + return ruleerrors.NewErrMissingParents(missingParentHashes) + } + + return nil } func (v *blockValidator) checkBlockTransactionsFinalized(blockHash *externalapi.DomainHash) error { diff --git a/domain/consensus/processes/blockvalidator/proof_of_work.go b/domain/consensus/processes/blockvalidator/proof_of_work.go index ffb38ba8a..08b4548f9 100644 --- a/domain/consensus/processes/blockvalidator/proof_of_work.go +++ b/domain/consensus/processes/blockvalidator/proof_of_work.go @@ -19,7 +19,7 @@ func (v *blockValidator) ValidatePruningPointViolationAndProofOfWorkAndDifficult return err } - err = v.checkParentsExist(blockHash, header) + err = v.checkParentHeadersExist(header) if err != nil { return err } @@ -103,14 +103,8 @@ func (v *blockValidator) checkProofOfWork(header *externalapi.DomainBlockHeader) return nil } -func (v *blockValidator) checkParentsExist(blockHash *externalapi.DomainHash, header *externalapi.DomainBlockHeader) error { +func (v *blockValidator) checkParentHeadersExist(header *externalapi.DomainBlockHeader) error { missingParentHashes := []*externalapi.DomainHash{} - - hasBlockBody, err := v.blockStore.HasBlock(v.databaseContext, blockHash) - if err != nil { - return err - } - for _, parent := range header.ParentHashes { parentHeaderExists, err := v.blockHeaderStore.HasBlockHeader(v.databaseContext, parent) if err != nil { @@ -129,26 +123,6 @@ func (v *blockValidator) checkParentsExist(blockHash *externalapi.DomainHash, he if parentStatus == externalapi.StatusInvalid { return errors.Wrapf(ruleerrors.ErrInvalidAncestorBlock, "parent %s is invalid", parent) } - - if hasBlockBody { - if parentStatus == externalapi.StatusHeaderOnly { - pruningPoint, err := v.pruningStore.PruningPoint(v.databaseContext) - if err != nil { - return err - } - - isInPastOfPruningPoint, err := v.dagTopologyManager.IsAncestorOf(parent, pruningPoint) - if err != nil { - return err - } - - if isInPastOfPruningPoint { - continue - } - - missingParentHashes = append(missingParentHashes, parent) - } - } } if len(missingParentHashes) > 0 { diff --git a/domain/consensus/processes/consensusstatemanager/add_block_to_virtual.go b/domain/consensus/processes/consensusstatemanager/add_block_to_virtual.go index 58dcec929..aab8210ae 100644 --- a/domain/consensus/processes/consensusstatemanager/add_block_to_virtual.go +++ b/domain/consensus/processes/consensusstatemanager/add_block_to_virtual.go @@ -5,7 +5,7 @@ import ( "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" ) -// AddBlockToVirtual submits the given block to be added to the +// AddBlock submits the given block to be added to the // current virtual. This process may result in a new virtual block // getting created func (csm *consensusStateManager) AddBlock(blockHash *externalapi.DomainHash) (*externalapi.SelectedParentChainChanges, error) { diff --git a/domain/consensus/processes/consensusstatemanager/calculate_past_utxo.go b/domain/consensus/processes/consensusstatemanager/calculate_past_utxo.go index 1e3dbb1c2..c092a9b51 100644 --- a/domain/consensus/processes/consensusstatemanager/calculate_past_utxo.go +++ b/domain/consensus/processes/consensusstatemanager/calculate_past_utxo.go @@ -256,10 +256,10 @@ func (csm *consensusStateManager) RestorePastUTXOSetIterator(blockHash *external if err != nil { return nil, err } - if blockStatus != externalapi.StatusValid { + if blockStatus != externalapi.StatusUTXOValid { return nil, errors.Errorf( "block %s, has status '%s', and therefore can't restore it's UTXO set. Only blocks with status '%s' can be restored.", - blockHash, blockStatus, externalapi.StatusValid) + blockHash, blockStatus, externalapi.StatusUTXOValid) } log.Tracef("RestorePastUTXOSetIterator start for block %s", blockHash) diff --git a/domain/consensus/processes/consensusstatemanager/pick_virtual_parents.go b/domain/consensus/processes/consensusstatemanager/pick_virtual_parents.go index c85f03892..103da8975 100644 --- a/domain/consensus/processes/consensusstatemanager/pick_virtual_parents.go +++ b/domain/consensus/processes/consensusstatemanager/pick_virtual_parents.go @@ -88,7 +88,7 @@ func (csm *consensusStateManager) selectVirtualSelectedParent( if err != nil { return nil, err } - if selectedParentCandidateStatus == externalapi.StatusValid { + if selectedParentCandidateStatus == externalapi.StatusUTXOValid { log.Tracef("Block %s is valid. Returning it as the selected parent", selectedParentCandidate) return selectedParentCandidate, nil } diff --git a/domain/consensus/processes/consensusstatemanager/resolve_block_status.go b/domain/consensus/processes/consensusstatemanager/resolve_block_status.go index 2aa6156b0..6f28d7312 100644 --- a/domain/consensus/processes/consensusstatemanager/resolve_block_status.go +++ b/domain/consensus/processes/consensusstatemanager/resolve_block_status.go @@ -72,8 +72,8 @@ func (csm *consensusStateManager) findSelectedParentStatus(unverifiedBlocks []*e lastUnverifiedBlock := unverifiedBlocks[len(unverifiedBlocks)-1] if *lastUnverifiedBlock == *csm.genesisHash { log.Tracef("the most recent unverified block is the genesis block, "+ - "which by definition has status: %s", externalapi.StatusValid) - return externalapi.StatusValid, nil + "which by definition has status: %s", externalapi.StatusUTXOValid) + return externalapi.StatusUTXOValid, nil } lastUnverifiedBlockGHOSTDAGData, err := csm.ghostdagDataStore.Get(csm.databaseContext, lastUnverifiedBlock) if err != nil { @@ -164,7 +164,7 @@ func (csm *consensusStateManager) resolveSingleBlockStatus(blockHash *externalap return 0, err } - return externalapi.StatusValid, nil + return externalapi.StatusUTXOValid, nil } func (csm *consensusStateManager) removeAncestorsFromVirtualDiffParentsAndAssignDiffChild( diff --git a/domain/consensus/processes/consensusstatemanager/resolve_block_status_test.go b/domain/consensus/processes/consensusstatemanager/resolve_block_status_test.go index f825e8025..52dc97612 100644 --- a/domain/consensus/processes/consensusstatemanager/resolve_block_status_test.go +++ b/domain/consensus/processes/consensusstatemanager/resolve_block_status_test.go @@ -70,8 +70,8 @@ func TestDoubleSpends(t *testing.T) { if err != nil { t.Fatalf("Error getting status of goodBlock1: %+v", err) } - if goodBlock1Status != externalapi.StatusValid { - t.Fatalf("GoodBlock1 status expected to be '%s', but is '%s'", externalapi.StatusValid, goodBlock1Status) + if goodBlock1Status != externalapi.StatusUTXOValid { + t.Fatalf("GoodBlock1 status expected to be '%s', but is '%s'", externalapi.StatusUTXOValid, goodBlock1Status) } // To check that a block containing the same transaction already in it's past is disqualified: @@ -145,8 +145,8 @@ func TestDoubleSpends(t *testing.T) { if err != nil { t.Fatalf("Error getting status of goodBlock: %+v", err) } - if goodBlock2Status != externalapi.StatusValid { - t.Fatalf("GoodBlock2 status expected to be '%s', but is '%s'", externalapi.StatusValid, goodBlock2Status) + if goodBlock2Status != externalapi.StatusUTXOValid { + t.Fatalf("GoodBlock2 status expected to be '%s', but is '%s'", externalapi.StatusUTXOValid, goodBlock2Status) } }) } @@ -167,7 +167,7 @@ func TestResolveBlockStatusSanity(t *testing.T) { if err != nil { t.Fatalf("error getting genesis status: %s", err) } - if genesisStatus != externalapi.StatusValid { + if genesisStatus != externalapi.StatusUTXOValid { t.Fatalf("genesis is unexpectedly non-valid. Its status is: %s", genesisStatus) } @@ -185,7 +185,7 @@ func TestResolveBlockStatusSanity(t *testing.T) { if err != nil { t.Fatalf("error getting block %d (%s) status: %s", i, addedBlockHash, err) } - if blockStatus != externalapi.StatusValid { + if blockStatus != externalapi.StatusUTXOValid { t.Fatalf("block %d (%s) is unexpectedly non-valid. Its status is: %s", i, addedBlockHash, blockStatus) } currentHash = addedBlockHash @@ -224,13 +224,13 @@ func TestResolveBlockStatusSanity(t *testing.T) { allHashes = append(allHashes, addedBlockHash) } - // Make sure that all the blocks in the DAG now have StatusValid + // Make sure that all the blocks in the DAG now have StatusUTXOValid for _, hash := range allHashes { blockStatus, err := consensus.BlockStatusStore().Get(consensus.DatabaseContext(), hash) if err != nil { t.Fatalf("error getting block %s status: %s", hash, err) } - if blockStatus != externalapi.StatusValid { + if blockStatus != externalapi.StatusUTXOValid { t.Fatalf("block %s is unexpectedly non-valid. Its status is: %s", hash, blockStatus) } } diff --git a/domain/consensus/processes/consensusstatemanager/update_pruning_utxo_set.go b/domain/consensus/processes/consensusstatemanager/update_pruning_utxo_set.go index cbca2911e..87847d699 100644 --- a/domain/consensus/processes/consensusstatemanager/update_pruning_utxo_set.go +++ b/domain/consensus/processes/consensusstatemanager/update_pruning_utxo_set.go @@ -54,59 +54,69 @@ func (csm *consensusStateManager) updatePruningPoint(newPruningPoint *externalap if err != nil { return err } - log.Tracef("Calculated multiset for given UTXO set: %s", utxoSetMultiSet.Hash()) + log.Debugf("Calculated multiset for given UTXO set: %s", utxoSetMultiSet.Hash()) newPruningPointHeader, err := csm.blockHeaderStore.BlockHeader(csm.databaseContext, newPruningPointHash) if err != nil { return err } - log.Tracef("The UTXO commitment of the pruning point: %s", + log.Debugf("The UTXO commitment of the pruning point: %s", newPruningPointHeader.UTXOCommitment) if newPruningPointHeader.UTXOCommitment != *utxoSetMultiSet.Hash() { return errors.Wrapf(ruleerrors.ErrBadPruningPointUTXOSet, "the expected multiset hash of the pruning "+ "point UTXO set is %s but got %s", newPruningPointHeader.UTXOCommitment, *utxoSetMultiSet.Hash()) } - log.Tracef("The new pruning point UTXO commitment validation passed") + log.Debugf("The new pruning point UTXO commitment validation passed") - log.Tracef("Staging the parent hashes for pruning point as the DAG tips") - csm.consensusStateStore.StageTips(newPruningPointHeader.ParentHashes) + newTips := []*externalapi.DomainHash{newPruningPointHash} - log.Tracef("Setting the parent hashes for the header tips pruning point as the virtual parents") - err = csm.dagTopologyManager.SetParents(model.VirtualBlockHash, newPruningPointHeader.ParentHashes) - if err != nil { - return err - } - - log.Tracef("Staging the virtual UTXO set") - err = csm.consensusStateStore.StageVirtualUTXOSet(protoUTXOSetToReadOnlyUTXOSetIterator(protoUTXOSet)) - if err != nil { - return err - } - - // Before we manually mark the new pruning point as valid, we validate that all of its transactions are valid - // against the provided UTXO set. - err = csm.validateBlockTransactionsAgainstPastUTXO(newPruningPoint, utxo.NewUTXODiff()) + log.Debugf("Staging the the pruning point as the only DAG tip") + csm.consensusStateStore.StageTips(newTips) + + log.Debugf("Setting the pruning point as the only virtual parent") + err = csm.dagTopologyManager.SetParents(model.VirtualBlockHash, newTips) if err != nil { return err } + log.Debugf("Calculating GHOSTDAG for the new virtual") err = csm.ghostdagManager.GHOSTDAG(model.VirtualBlockHash) if err != nil { return err } - log.Tracef("Updating the header tips pruning point diff parents with an empty UTXO diff") - err = csm.updateVirtualDiffParents(utxo.NewUTXODiff()) + log.Debugf("Staging the virtual UTXO set") + err = csm.consensusStateStore.StageVirtualUTXOSet(protoUTXOSetToReadOnlyUTXOSetIterator(protoUTXOSet)) if err != nil { return err } - log.Tracef("Staging the new pruning point and its UTXO set") + log.Debugf("Deleting all the existing virtual diff parents") + csm.consensusStateStore.StageVirtualDiffParents(nil) + + log.Debugf("Updating the new pruning point to be the new virtual diff parent with an empty diff") + err = csm.stageDiff(newPruningPointHash, utxo.NewUTXODiff(), nil) + if err != nil { + return err + } + + log.Debugf("Staging the new pruning point and its UTXO set") csm.pruningStore.Stage(newPruningPointHash, serializedUTXOSet) - log.Tracef("Staging the new pruning point as %s", externalapi.StatusValid) - csm.blockStatusStore.Stage(newPruningPointHash, externalapi.StatusValid) + // Before we manually mark the new pruning point as valid, we validate that all of its transactions are valid + // against the provided UTXO set. + log.Debugf("Validating that the pruning point is UTXO valid") + err = csm.validateBlockTransactionsAgainstPastUTXO(newPruningPoint, utxo.NewUTXODiff()) + if err != nil { + return err + } + + log.Debugf("Staging the new pruning point as %s", externalapi.StatusUTXOValid) + csm.blockStatusStore.Stage(newPruningPointHash, externalapi.StatusUTXOValid) + + log.Debugf("Staging the new pruning point multiset") + csm.multisetStore.Stage(newPruningPointHash, utxoSetMultiSet) return nil }