diff --git a/domain/consensus/datastructures/utxodiffstore/utxo_diff_store.go b/domain/consensus/datastructures/utxodiffstore/utxo_diff_store.go index 03e546952..b6ec53dfc 100644 --- a/domain/consensus/datastructures/utxodiffstore/utxo_diff_store.go +++ b/domain/consensus/datastructures/utxodiffstore/utxo_diff_store.go @@ -28,7 +28,9 @@ func New(cacheSize int, preallocate bool) model.UTXODiffStore { } // Stage stages the given utxoDiff for the given blockHash -func (uds *utxoDiffStore) Stage(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash, utxoDiff externalapi.UTXODiff, utxoDiffChild *externalapi.DomainHash) { +func (uds *utxoDiffStore) Stage(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash, + utxoDiff externalapi.UTXODiff, utxoDiffChild *externalapi.DomainHash) { + stagingShard := uds.stagingShard(stagingArea) stagingShard.utxoDiffToAdd[*blockHash] = utxoDiff diff --git a/domain/consensus/finality_test.go b/domain/consensus/finality_test.go index 357cbf099..cd8725988 100644 --- a/domain/consensus/finality_test.go +++ b/domain/consensus/finality_test.go @@ -67,7 +67,7 @@ func TestFinality(t *testing.T) { for i := uint64(0); i < finalityInterval-2; i++ { sideChainTip, err = buildAndInsertBlock([]*externalapi.DomainHash{sideChainTipHash}) if err != nil { - t.Fatalf("TestFinality: Failed to process sidechain Block #%d: %v", i, err) + t.Fatalf("TestFinality: Failed to process sidechain Block #%d: %+v", i, err) } sideChainTipHash = consensushashing.BlockHash(sideChainTip) diff --git a/domain/consensus/model/externalapi/utxodiff.go b/domain/consensus/model/externalapi/utxodiff.go index 0fb19c6c8..30d7e1f3a 100644 --- a/domain/consensus/model/externalapi/utxodiff.go +++ b/domain/consensus/model/externalapi/utxodiff.go @@ -14,6 +14,7 @@ type UTXODiff interface { ToRemove() UTXOCollection WithDiff(other UTXODiff) (UTXODiff, error) DiffFrom(other UTXODiff) (UTXODiff, error) + Reversed() UTXODiff CloneMutable() MutableUTXODiff } diff --git a/domain/consensus/model/interface_processes_consensusstatemanager.go b/domain/consensus/model/interface_processes_consensusstatemanager.go index bf0f939c8..60e916cc0 100644 --- a/domain/consensus/model/interface_processes_consensusstatemanager.go +++ b/domain/consensus/model/interface_processes_consensusstatemanager.go @@ -4,11 +4,12 @@ import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" // ConsensusStateManager manages the node's consensus state type ConsensusStateManager interface { - AddBlock(stagingArea *StagingArea, blockHash *externalapi.DomainHash) (*externalapi.SelectedChainPath, externalapi.UTXODiff, error) + AddBlock(stagingArea *StagingArea, blockHash *externalapi.DomainHash) (*externalapi.SelectedChainPath, externalapi.UTXODiff, *UTXODiffReversalData, error) PopulateTransactionWithUTXOEntries(stagingArea *StagingArea, transaction *externalapi.DomainTransaction) error ImportPruningPoint(stagingArea *StagingArea, newPruningPoint *externalapi.DomainBlock) error RestorePastUTXOSetIterator(stagingArea *StagingArea, blockHash *externalapi.DomainHash) (externalapi.ReadOnlyUTXOSetIterator, error) CalculatePastUTXOAndAcceptanceData(stagingArea *StagingArea, blockHash *externalapi.DomainHash) (externalapi.UTXODiff, externalapi.AcceptanceData, Multiset, error) GetVirtualSelectedParentChainFromBlock(stagingArea *StagingArea, blockHash *externalapi.DomainHash) (*externalapi.SelectedChainPath, error) RecoverUTXOIfRequired() error + ReverseUTXODiffs(tipHash *externalapi.DomainHash, reversalData *UTXODiffReversalData) error } diff --git a/domain/consensus/model/testapi/test_consensus_state_manager.go b/domain/consensus/model/testapi/test_consensus_state_manager.go index ff44e9d90..9ae50c357 100644 --- a/domain/consensus/model/testapi/test_consensus_state_manager.go +++ b/domain/consensus/model/testapi/test_consensus_state_manager.go @@ -11,5 +11,5 @@ type TestConsensusStateManager interface { AddUTXOToMultiset(multiset model.Multiset, entry externalapi.UTXOEntry, outpoint *externalapi.DomainOutpoint) error ResolveBlockStatus(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash, - useSeparateStagingAreasPerBlock bool) (externalapi.BlockStatus, error) + useSeparateStagingAreaPerBlock bool) (externalapi.BlockStatus, error) } diff --git a/domain/consensus/model/utxo_diff_reversal_data.go b/domain/consensus/model/utxo_diff_reversal_data.go new file mode 100644 index 000000000..c6fed8dd5 --- /dev/null +++ b/domain/consensus/model/utxo_diff_reversal_data.go @@ -0,0 +1,9 @@ +package model + +import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" + +// UTXODiffReversalData is used by ConsensusStateManager to reverse the UTXODiffs during a re-org +type UTXODiffReversalData struct { + SelectedParentHash *externalapi.DomainHash + SelectedParentUTXODiff externalapi.UTXODiff +} diff --git a/domain/consensus/processes/blockprocessor/validateandinsertblock.go b/domain/consensus/processes/blockprocessor/validateandinsertblock.go index 5026ef0c4..bcf250a77 100644 --- a/domain/consensus/processes/blockprocessor/validateandinsertblock.go +++ b/domain/consensus/processes/blockprocessor/validateandinsertblock.go @@ -90,6 +90,7 @@ func (bp *blockProcessor) validateAndInsertBlock(stagingArea *model.StagingArea, var selectedParentChainChanges *externalapi.SelectedChainPath var virtualUTXODiff externalapi.UTXODiff + var reversalData *model.UTXODiffReversalData isHeaderOnlyBlock := isHeaderOnlyBlock(block) if !isHeaderOnlyBlock { // There's no need to update the consensus state manager when @@ -97,7 +98,7 @@ func (bp *blockProcessor) validateAndInsertBlock(stagingArea *model.StagingArea, // in consensusStateManager.ImportPruningPoint if !isPruningPoint { // Attempt to add the block to the virtual - selectedParentChainChanges, virtualUTXODiff, err = bp.consensusStateManager.AddBlock(stagingArea, blockHash) + selectedParentChainChanges, virtualUTXODiff, reversalData, err = bp.consensusStateManager.AddBlock(stagingArea, blockHash) if err != nil { return nil, err } @@ -124,6 +125,13 @@ func (bp *blockProcessor) validateAndInsertBlock(stagingArea *model.StagingArea, return nil, err } + if reversalData != nil { + err = bp.consensusStateManager.ReverseUTXODiffs(blockHash, reversalData) + if err != nil { + return nil, err + } + } + err = bp.pruningManager.UpdatePruningPointIfRequired() if err != nil { return nil, err diff --git a/domain/consensus/processes/consensusstatemanager/add_block_to_virtual.go b/domain/consensus/processes/consensusstatemanager/add_block_to_virtual.go index 06f72ba7d..629a4bd4b 100644 --- a/domain/consensus/processes/consensusstatemanager/add_block_to_virtual.go +++ b/domain/consensus/processes/consensusstatemanager/add_block_to_virtual.go @@ -10,7 +10,7 @@ import ( // current virtual. This process may result in a new virtual block // getting created func (csm *consensusStateManager) AddBlock(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash) ( - *externalapi.SelectedChainPath, externalapi.UTXODiff, error) { + *externalapi.SelectedChainPath, externalapi.UTXODiff, *model.UTXODiffReversalData, error) { onEnd := logger.LogAndMeasureExecutionTime(log, "csm.AddBlock") defer onEnd() @@ -18,9 +18,10 @@ func (csm *consensusStateManager) AddBlock(stagingArea *model.StagingArea, block log.Debugf("Resolving whether the block %s is the next virtual selected parent", blockHash) isCandidateToBeNextVirtualSelectedParent, err := csm.isCandidateToBeNextVirtualSelectedParent(stagingArea, blockHash) if err != nil { - return nil, nil, err + return nil, nil, nil, err } + var reversalData *model.UTXODiffReversalData if isCandidateToBeNextVirtualSelectedParent { // It's important to check for finality violation before resolving the block status, because the status of // blocks with a selected chain that doesn't contain the pruning point cannot be resolved because they will @@ -29,7 +30,7 @@ func (csm *consensusStateManager) AddBlock(stagingArea *model.StagingArea, block "finality", blockHash) isViolatingFinality, shouldNotify, err := csm.isViolatingFinality(stagingArea, blockHash) if err != nil { - return nil, nil, err + return nil, nil, nil, err } if shouldNotify { @@ -39,9 +40,10 @@ func (csm *consensusStateManager) AddBlock(stagingArea *model.StagingArea, block if !isViolatingFinality { log.Debugf("Block %s doesn't violate finality. Resolving its block status", blockHash) - blockStatus, err := csm.resolveBlockStatus(stagingArea, blockHash, true) + var blockStatus externalapi.BlockStatus + blockStatus, reversalData, err = csm.resolveBlockStatus(stagingArea, blockHash, true) if err != nil { - return nil, nil, err + return nil, nil, nil, err } log.Debugf("Block %s resolved to status `%s`", blockHash, blockStatus) @@ -54,17 +56,17 @@ func (csm *consensusStateManager) AddBlock(stagingArea *model.StagingArea, block log.Debugf("Adding block %s to the DAG tips", blockHash) newTips, err := csm.addTip(stagingArea, blockHash) if err != nil { - return nil, nil, err + return nil, nil, nil, err } log.Debugf("After adding %s, the amount of new tips are %d", blockHash, len(newTips)) log.Debugf("Updating the virtual with the new tips") selectedParentChainChanges, virtualUTXODiff, err := csm.updateVirtual(stagingArea, blockHash, newTips) if err != nil { - return nil, nil, err + return nil, nil, nil, err } - return selectedParentChainChanges, virtualUTXODiff, nil + return selectedParentChainChanges, virtualUTXODiff, reversalData, nil } func (csm *consensusStateManager) isCandidateToBeNextVirtualSelectedParent( diff --git a/domain/consensus/processes/consensusstatemanager/calculate_past_utxo.go b/domain/consensus/processes/consensusstatemanager/calculate_past_utxo.go index c004a3e7b..d1cb3bb49 100644 --- a/domain/consensus/processes/consensusstatemanager/calculate_past_utxo.go +++ b/domain/consensus/processes/consensusstatemanager/calculate_past_utxo.go @@ -39,14 +39,26 @@ func (csm *consensusStateManager) CalculatePastUTXOAndAcceptanceData(stagingArea return nil, nil, nil, err } - daaScore, err := csm.daaBlocksStore.DAAScore(csm.databaseContext, stagingArea, blockHash) + log.Debugf("Restored the past UTXO of block %s with selectedParent %s. "+ + "Diff toAdd length: %d, toRemove length: %d", blockHash, blockGHOSTDAGData.SelectedParent(), + selectedParentPastUTXO.ToAdd().Len(), selectedParentPastUTXO.ToRemove().Len()) + + return csm.calculatePastUTXOAndAcceptanceDataWithSelectedParentUTXO(stagingArea, blockHash, selectedParentPastUTXO) +} + +func (csm *consensusStateManager) calculatePastUTXOAndAcceptanceDataWithSelectedParentUTXO(stagingArea *model.StagingArea, + blockHash *externalapi.DomainHash, selectedParentPastUTXO externalapi.UTXODiff) ( + externalapi.UTXODiff, externalapi.AcceptanceData, model.Multiset, error) { + + blockGHOSTDAGData, err := csm.ghostdagDataStore.Get(csm.databaseContext, stagingArea, blockHash) if err != nil { return nil, nil, nil, err } - log.Debugf("Restored the past UTXO of block %s with selectedParent %s. "+ - "Diff toAdd length: %d, toRemove length: %d", blockHash, blockGHOSTDAGData.SelectedParent(), - selectedParentPastUTXO.ToAdd().Len(), selectedParentPastUTXO.ToRemove().Len()) + daaScore, err := csm.daaBlocksStore.DAAScore(csm.databaseContext, stagingArea, blockHash) + if err != nil { + return nil, nil, nil, err + } log.Debugf("Applying blue blocks to the selected parent past UTXO of block %s", blockHash) acceptanceData, utxoDiff, err := csm.applyMergeSetBlocks( @@ -66,7 +78,7 @@ func (csm *consensusStateManager) CalculatePastUTXOAndAcceptanceData(stagingArea } func (csm *consensusStateManager) restorePastUTXO( - stagingArea *model.StagingArea, blockHash *externalapi.DomainHash) (externalapi.MutableUTXODiff, error) { + stagingArea *model.StagingArea, blockHash *externalapi.DomainHash) (externalapi.UTXODiff, error) { onEnd := logger.LogAndMeasureExecutionTime(log, "restorePastUTXO") defer onEnd() @@ -120,11 +132,11 @@ func (csm *consensusStateManager) restorePastUTXO( } log.Tracef("The accumulated diff for block %s is: %s", blockHash, accumulatedDiff) - return accumulatedDiff, nil + return accumulatedDiff.ToImmutable(), nil } func (csm *consensusStateManager) applyMergeSetBlocks(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash, - selectedParentPastUTXODiff externalapi.MutableUTXODiff, ghostdagData *model.BlockGHOSTDAGData, daaScore uint64) ( + selectedParentPastUTXODiff externalapi.UTXODiff, ghostdagData *model.BlockGHOSTDAGData, daaScore uint64) ( externalapi.AcceptanceData, externalapi.MutableUTXODiff, error) { log.Debugf("applyMergeSetBlocks start for block %s", blockHash) @@ -144,7 +156,7 @@ func (csm *consensusStateManager) applyMergeSetBlocks(stagingArea *model.Staging log.Tracef("The past median time for block %s is: %d", blockHash, selectedParentMedianTime) multiblockAcceptanceData := make(externalapi.AcceptanceData, len(mergeSetBlocks)) - accumulatedUTXODiff := selectedParentPastUTXODiff + accumulatedUTXODiff := selectedParentPastUTXODiff.CloneMutable() accumulatedMass := uint64(0) for i, mergeSetBlock := range mergeSetBlocks { @@ -277,12 +289,13 @@ func (csm *consensusStateManager) checkTransactionMass( } // RestorePastUTXOSetIterator restores the given block's UTXOSet iterator, and returns it as a externalapi.ReadOnlyUTXOSetIterator -func (csm *consensusStateManager) RestorePastUTXOSetIterator(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash) (externalapi.ReadOnlyUTXOSetIterator, error) { +func (csm *consensusStateManager) RestorePastUTXOSetIterator(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash) ( + externalapi.ReadOnlyUTXOSetIterator, error) { onEnd := logger.LogAndMeasureExecutionTime(log, "RestorePastUTXOSetIterator") defer onEnd() - blockStatus, err := csm.resolveBlockStatus(stagingArea, blockHash, true) + blockStatus, _, err := csm.resolveBlockStatus(stagingArea, blockHash, true) if err != nil { return nil, err } @@ -306,5 +319,5 @@ func (csm *consensusStateManager) RestorePastUTXOSetIterator(stagingArea *model. return nil, err } - return utxo.IteratorWithDiff(virtualUTXOSetIterator, blockDiff.ToImmutable()) + return utxo.IteratorWithDiff(virtualUTXOSetIterator, blockDiff) } diff --git a/domain/consensus/processes/consensusstatemanager/resolve_block_status.go b/domain/consensus/processes/consensusstatemanager/resolve_block_status.go index bc2a5e647..6bf677838 100644 --- a/domain/consensus/processes/consensusstatemanager/resolve_block_status.go +++ b/domain/consensus/processes/consensusstatemanager/resolve_block_status.go @@ -3,6 +3,8 @@ package consensusstatemanager import ( "fmt" + "github.com/kaspanet/kaspad/domain/consensus/utils/utxo" + "github.com/kaspanet/kaspad/util/staging" "github.com/kaspanet/kaspad/domain/consensus/model" @@ -13,7 +15,7 @@ import ( ) func (csm *consensusStateManager) resolveBlockStatus(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash, - useSeparateStagingAreasPerBlock bool) (externalapi.BlockStatus, error) { + useSeparateStagingAreaPerBlock bool) (externalapi.BlockStatus, *model.UTXODiffReversalData, error) { onEnd := logger.LogAndMeasureExecutionTime(log, fmt.Sprintf("resolveBlockStatus for %s", blockHash)) defer onEnd() @@ -22,7 +24,7 @@ func (csm *consensusStateManager) resolveBlockStatus(stagingArea *model.StagingA "parent chain of %s that have no yet resolved their status", blockHash) unverifiedBlocks, err := csm.getUnverifiedChainBlocks(stagingArea, blockHash) if err != nil { - return 0, err + return 0, nil, err } log.Debugf("Got %d unverified blocks in the selected parent "+ "chain of %s: %s", len(unverifiedBlocks), blockHash, unverifiedBlocks) @@ -34,26 +36,33 @@ func (csm *consensusStateManager) resolveBlockStatus(stagingArea *model.StagingA "This means that the block already has a UTXO-verified status.", blockHash) status, err := csm.blockStatusStore.Get(csm.databaseContext, stagingArea, blockHash) if err != nil { - return 0, err + return 0, nil, err } log.Debugf("Block %s's status resolved to: %s", blockHash, status) - return status, nil + return status, nil, nil } log.Debugf("Finding the status of the selected parent of %s", blockHash) - selectedParentStatus, err := csm.findSelectedParentStatus(stagingArea, unverifiedBlocks) + selectedParentHash, selectedParentStatus, selectedParentUTXOSet, err := csm.selectedParentInfo(stagingArea, unverifiedBlocks) if err != nil { - return 0, err + return 0, nil, err } log.Debugf("The status of the selected parent of %s is: %s", blockHash, selectedParentStatus) log.Debugf("Resolving the unverified blocks' status in reverse order (past to present)") var blockStatus externalapi.BlockStatus + + previousBlockHash := selectedParentHash + previousBlockUTXOSet := selectedParentUTXOSet + var oneBeforeLastResolvedBlockUTXOSet externalapi.UTXODiff + var oneBeforeLastResolvedBlockHash *externalapi.DomainHash + for i := len(unverifiedBlocks) - 1; i >= 0; i-- { unverifiedBlockHash := unverifiedBlocks[i] stagingAreaForCurrentBlock := stagingArea - useSeparateStagingArea := useSeparateStagingAreasPerBlock && (i != 0) + isResolveTip := i == 0 + useSeparateStagingArea := useSeparateStagingAreaPerBlock && !isResolveTip if useSeparateStagingArea { stagingAreaForCurrentBlock = model.NewStagingArea() } @@ -61,9 +70,13 @@ func (csm *consensusStateManager) resolveBlockStatus(stagingArea *model.StagingA if selectedParentStatus == externalapi.StatusDisqualifiedFromChain { blockStatus = externalapi.StatusDisqualifiedFromChain } else { - blockStatus, err = csm.resolveSingleBlockStatus(stagingAreaForCurrentBlock, unverifiedBlockHash) + oneBeforeLastResolvedBlockUTXOSet = previousBlockUTXOSet + oneBeforeLastResolvedBlockHash = previousBlockHash + + blockStatus, previousBlockUTXOSet, err = csm.resolveSingleBlockStatus( + stagingAreaForCurrentBlock, unverifiedBlockHash, previousBlockHash, previousBlockUTXOSet, isResolveTip) if err != nil { - return 0, err + return 0, nil, err } } @@ -75,17 +88,39 @@ func (csm *consensusStateManager) resolveBlockStatus(stagingArea *model.StagingA if useSeparateStagingArea { err := staging.CommitAllChanges(csm.databaseContext, stagingAreaForCurrentBlock) if err != nil { - return 0, err + return 0, nil, err } } + previousBlockHash = unverifiedBlockHash + } + + var reversalData *model.UTXODiffReversalData + if blockStatus == externalapi.StatusUTXOValid && len(unverifiedBlocks) > 1 { + log.Debugf("Preparing data for reversing the UTXODiff") + // During resolveSingleBlockStatus, all unverifiedBlocks (excluding the tip) were assigned their selectedParent + // as their UTXODiffChild. + // Now that the whole chain has been resolved - we can reverse the UTXODiffs, to create shorter UTXODiffChild paths. + // However, we can't do this right now, because the tip of the chain is not yet committed, so we prepare the + // needed data (tip's selectedParent and selectedParent's UTXODiff) + selectedParentUTXODiff, err := previousBlockUTXOSet.DiffFrom(oneBeforeLastResolvedBlockUTXOSet) + if err != nil { + return 0, nil, err + } + + reversalData = &model.UTXODiffReversalData{ + SelectedParentHash: oneBeforeLastResolvedBlockHash, + SelectedParentUTXODiff: selectedParentUTXODiff, + } } - return blockStatus, nil + return blockStatus, reversalData, nil } -// findSelectedParentStatus returns the status of the selectedParent of the last block in the unverifiedBlocks chain -func (csm *consensusStateManager) findSelectedParentStatus( - stagingArea *model.StagingArea, unverifiedBlocks []*externalapi.DomainHash) (externalapi.BlockStatus, error) { +// selectedParentInfo returns the hash and status of the selectedParent of the last block in the unverifiedBlocks +// chain, in addition, if the status is UTXOValid, it return it's pastUTXOSet +func (csm *consensusStateManager) selectedParentInfo( + stagingArea *model.StagingArea, unverifiedBlocks []*externalapi.DomainHash) ( + *externalapi.DomainHash, externalapi.BlockStatus, externalapi.UTXODiff, error) { log.Debugf("findSelectedParentStatus start") defer log.Debugf("findSelectedParentStatus end") @@ -94,13 +129,26 @@ func (csm *consensusStateManager) findSelectedParentStatus( if lastUnverifiedBlock.Equal(csm.genesisHash) { log.Debugf("the most recent unverified block is the genesis block, "+ "which by definition has status: %s", externalapi.StatusUTXOValid) - return externalapi.StatusUTXOValid, nil + return lastUnverifiedBlock, externalapi.StatusUTXOValid, utxo.NewUTXODiff(), nil } lastUnverifiedBlockGHOSTDAGData, err := csm.ghostdagDataStore.Get(csm.databaseContext, stagingArea, lastUnverifiedBlock) if err != nil { - return 0, err + return nil, 0, nil, err } - return csm.blockStatusStore.Get(csm.databaseContext, stagingArea, lastUnverifiedBlockGHOSTDAGData.SelectedParent()) + selectedParent := lastUnverifiedBlockGHOSTDAGData.SelectedParent() + selectedParentStatus, err := csm.blockStatusStore.Get(csm.databaseContext, stagingArea, selectedParent) + if err != nil { + return nil, 0, nil, err + } + if selectedParentStatus != externalapi.StatusUTXOValid { + return selectedParent, selectedParentStatus, nil, nil + } + + selectedParentUTXOSet, err := csm.restorePastUTXO(stagingArea, selectedParent) + if err != nil { + return nil, 0, nil, err + } + return selectedParent, selectedParentStatus, selectedParentUTXOSet, nil } func (csm *consensusStateManager) getUnverifiedChainBlocks(stagingArea *model.StagingArea, @@ -142,15 +190,17 @@ func (csm *consensusStateManager) getUnverifiedChainBlocks(stagingArea *model.St } func (csm *consensusStateManager) resolveSingleBlockStatus(stagingArea *model.StagingArea, - blockHash *externalapi.DomainHash) (externalapi.BlockStatus, error) { + blockHash, selectedParentHash *externalapi.DomainHash, selectedParentPastUTXOSet externalapi.UTXODiff, isResolveTip bool) ( + externalapi.BlockStatus, externalapi.UTXODiff, error) { onEnd := logger.LogAndMeasureExecutionTime(log, fmt.Sprintf("resolveSingleBlockStatus for %s", blockHash)) defer onEnd() log.Tracef("Calculating pastUTXO and acceptance data and multiset for block %s", blockHash) - pastUTXODiff, acceptanceData, multiset, err := csm.CalculatePastUTXOAndAcceptanceData(stagingArea, blockHash) + pastUTXOSet, acceptanceData, multiset, err := csm.calculatePastUTXOAndAcceptanceDataWithSelectedParentUTXO( + stagingArea, blockHash, selectedParentPastUTXOSet) if err != nil { - return 0, err + return 0, nil, err } log.Tracef("Staging the calculated acceptance data of block %s", blockHash) @@ -158,17 +208,17 @@ func (csm *consensusStateManager) resolveSingleBlockStatus(stagingArea *model.St block, err := csm.blockStore.Block(csm.databaseContext, stagingArea, blockHash) if err != nil { - return 0, err + return 0, nil, err } log.Tracef("verifying the UTXO of block %s", blockHash) - err = csm.verifyUTXO(stagingArea, block, blockHash, pastUTXODiff, acceptanceData, multiset) + err = csm.verifyUTXO(stagingArea, block, blockHash, pastUTXOSet, acceptanceData, multiset) if err != nil { if errors.As(err, &ruleerrors.RuleError{}) { log.Debugf("UTXO verification for block %s failed: %s", blockHash, err) - return externalapi.StatusDisqualifiedFromChain, nil + return externalapi.StatusDisqualifiedFromChain, nil, nil } - return 0, err + return 0, nil, err } log.Debugf("UTXO verification for block %s passed", blockHash) @@ -177,45 +227,62 @@ func (csm *consensusStateManager) resolveSingleBlockStatus(stagingArea *model.St if csm.genesisHash.Equal(blockHash) { log.Tracef("Staging the utxoDiff of genesis") - csm.stageDiff(stagingArea, blockHash, pastUTXODiff, nil) - return externalapi.StatusUTXOValid, nil + csm.stageDiff(stagingArea, blockHash, pastUTXOSet, nil) + return externalapi.StatusUTXOValid, nil, nil } oldSelectedTip, err := csm.selectedTip(stagingArea) if err != nil { - return 0, err + return 0, nil, err } - isNewSelectedTip, err := csm.isNewSelectedTip(stagingArea, blockHash, oldSelectedTip) - if err != nil { - return 0, err - } - oldSelectedTipUTXOSet, err := csm.restorePastUTXO(stagingArea, oldSelectedTip) - if err != nil { - return 0, err - } - if isNewSelectedTip { - log.Debugf("Block %s is the new SelectedTip, therefore setting it as old selectedTip's diffChild", blockHash) - oldSelectedTipUTXOSet, err := pastUTXODiff.DiffFrom(oldSelectedTipUTXOSet.ToImmutable()) + if isResolveTip { + oldSelectedTipUTXOSet, err := csm.restorePastUTXO(stagingArea, oldSelectedTip) if err != nil { - return 0, err + return 0, nil, err + } + isNewSelectedTip, err := csm.isNewSelectedTip(stagingArea, blockHash, oldSelectedTip) + if err != nil { + return 0, nil, err } - csm.stageDiff(stagingArea, oldSelectedTip, oldSelectedTipUTXOSet, blockHash) - log.Tracef("Staging the utxoDiff of block %s", blockHash) - csm.stageDiff(stagingArea, blockHash, pastUTXODiff, nil) + if isNewSelectedTip { + log.Debugf("Block %s is the new selected tip, therefore setting it as old selected tip's diffChild", blockHash) + + updatedOldSelectedTipUTXOSet, err := pastUTXOSet.DiffFrom(oldSelectedTipUTXOSet) + if err != nil { + return 0, nil, err + } + log.Debugf("Setting the old selected tip's (%s) diffChild to be the new selected tip (%s)", + oldSelectedTip, blockHash) + csm.stageDiff(stagingArea, oldSelectedTip, updatedOldSelectedTipUTXOSet, blockHash) + + log.Tracef("Staging the utxoDiff of block %s, with virtual as diffChild", blockHash) + csm.stageDiff(stagingArea, blockHash, pastUTXOSet, nil) + } else { + log.Debugf("Block %s is the tip of currently resolved chain, but not the new selected tip,"+ + "therefore setting it's utxoDiffChild to be the current selectedTip %s", blockHash, oldSelectedTip) + utxoDiff, err := oldSelectedTipUTXOSet.DiffFrom(pastUTXOSet) + if err != nil { + return 0, nil, err + } + csm.stageDiff(stagingArea, blockHash, utxoDiff, oldSelectedTip) + } } else { - log.Debugf("Block %s is not the new SelectedTip, therefore setting old selectedTip as it's diffChild", blockHash) - pastUTXODiff, err = oldSelectedTipUTXOSet.DiffFrom(pastUTXODiff) + // If the block is not the tip of the currently resolved chain, we set it's diffChild to be the selectedParent, + // this is a temporary measure to ensure there's a restore path to all blocks at all times. + // Later down the process, the diff will be reversed in reverseUTXODiffs. + log.Debugf("Block %s is not the new selected tip, and is not the tip of the currently verified chain, "+ + "therefore temporarily setting selectedParent as it's diffChild", blockHash) + utxoDiff, err := selectedParentPastUTXOSet.DiffFrom(pastUTXOSet) if err != nil { - return 0, err + return 0, nil, err } - log.Tracef("Staging the utxoDiff of block %s", blockHash) - csm.stageDiff(stagingArea, blockHash, pastUTXODiff, oldSelectedTip) + csm.stageDiff(stagingArea, blockHash, utxoDiff, selectedParentHash) } - return externalapi.StatusUTXOValid, nil + return externalapi.StatusUTXOValid, pastUTXOSet, nil } func (csm *consensusStateManager) isNewSelectedTip(stagingArea *model.StagingArea, diff --git a/domain/consensus/processes/consensusstatemanager/reverse_utxo_diffs.go b/domain/consensus/processes/consensusstatemanager/reverse_utxo_diffs.go new file mode 100644 index 000000000..054cbcc0a --- /dev/null +++ b/domain/consensus/processes/consensusstatemanager/reverse_utxo_diffs.go @@ -0,0 +1,94 @@ +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" +) + +func (csm *consensusStateManager) ReverseUTXODiffs(tipHash *externalapi.DomainHash, + reversalData *model.UTXODiffReversalData) error { + + // During the process of resolving a chain of blocks, we temporarily set all blocks' (except the tip) + // UTXODiffChild to be the selected parent. + // Once the process is complete, we can reverse said chain, to now go directly to virtual through the relevant tip + onEnd := logger.LogAndMeasureExecutionTime(log, "reverseUTXODiffs") + defer onEnd() + + readStagingArea := model.NewStagingArea() + + log.Debugf("Reversing utxoDiffs") + + // Set previousUTXODiff and previousBlock to tip.SelectedParent before we start touching them, + // since previousBlock's UTXODiff is going to be over-written in the next step + previousBlock := reversalData.SelectedParentHash + previousUTXODiff, err := csm.utxoDiffStore.UTXODiff(csm.databaseContext, readStagingArea, previousBlock) + if err != nil { + return err + } + + // tip.selectedParent is special in the sense that we don't have it's diff available in reverse, however, + // we were able to calculate it when the tip's and tip.selectedParent's UTXOSets were known during resolveBlockStatus. + // Therefore - we treat it separately + err = csm.commitUTXODiffInSeparateStagingArea(previousBlock, reversalData.SelectedParentUTXODiff, tipHash) + if err != nil { + return err + } + + log.Trace("Reversed 1 utxoDiff") + + previousBlockGHOSTDAGData, err := csm.ghostdagDataStore.Get(csm.databaseContext, readStagingArea, previousBlock) + if err != nil { + return err + } + // Now go over the rest of the blocks and assign for every block Bi.UTXODiff = Bi+1.UTXODiff.Reversed() + for i := 1; ; i++ { + currentBlock := previousBlockGHOSTDAGData.SelectedParent() + + currentBlockUTXODiffChild, err := csm.utxoDiffStore.UTXODiffChild(csm.databaseContext, readStagingArea, currentBlock) + if err != nil { + return err + } + currentBlockGHOSTDAGData, err := csm.ghostdagDataStore.Get(csm.databaseContext, readStagingArea, currentBlock) + if err != nil { + return err + } + + // We stop reversing when current's UTXODiffChild is not current's SelectedParent + if !currentBlockGHOSTDAGData.SelectedParent().Equal(currentBlockUTXODiffChild) { + log.Debugf("Block %s's UTXODiffChild is not it's selected parent - finish reversing", currentBlock) + break + } + + currentUTXODiff := previousUTXODiff.Reversed() + + // retrieve current utxoDiff for Bi, to be used by next block + previousUTXODiff, err = csm.utxoDiffStore.UTXODiff(csm.databaseContext, readStagingArea, currentBlock) + if err != nil { + return err + } + + err = csm.commitUTXODiffInSeparateStagingArea(currentBlock, currentUTXODiff, previousBlock) + if err != nil { + return err + } + + previousBlock = currentBlock + previousBlockGHOSTDAGData = currentBlockGHOSTDAGData + + log.Tracef("Reversed %d utxoDiffs", i) + } + + return nil +} + +func (csm *consensusStateManager) commitUTXODiffInSeparateStagingArea( + blockHash *externalapi.DomainHash, utxoDiff externalapi.UTXODiff, utxoDiffChild *externalapi.DomainHash) error { + + stagingAreaForCurrentBlock := model.NewStagingArea() + + csm.utxoDiffStore.Stage(stagingAreaForCurrentBlock, blockHash, utxoDiff, utxoDiffChild) + + return staging.CommitAllChanges(csm.databaseContext, stagingAreaForCurrentBlock) +} diff --git a/domain/consensus/processes/consensusstatemanager/reverse_utxo_diffs_test.go b/domain/consensus/processes/consensusstatemanager/reverse_utxo_diffs_test.go new file mode 100644 index 000000000..796c77704 --- /dev/null +++ b/domain/consensus/processes/consensusstatemanager/reverse_utxo_diffs_test.go @@ -0,0 +1,115 @@ +package consensusstatemanager_test + +import ( + "testing" + + "github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing" + + "github.com/kaspanet/kaspad/domain/consensus/model" + + "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" + + "github.com/kaspanet/kaspad/domain/consensus" + "github.com/kaspanet/kaspad/domain/consensus/utils/testutils" +) + +func TestReverseUTXODiffs(t *testing.T) { + // This test doesn't check ReverseUTXODiffs directly, since that would be quite complicated, + // instead, it creates a situation where a reversal would defenitely happen - a reorg of 5 blocks, + // then verifies that the resulting utxo-diffs and utxo-diff-children are all correct. + + testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) { + factory := consensus.NewFactory() + + tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestUTXOCommitment") + if err != nil { + t.Fatalf("Error setting up consensus: %+v", err) + } + defer teardown(false) + + // Create a chain of 5 blocks + const initialChainLength = 5 + previousBlockHash := consensusConfig.GenesisHash + for i := 0; i < initialChainLength; i++ { + previousBlockHash, _, err = tc.AddBlock([]*externalapi.DomainHash{previousBlockHash}, nil, nil) + if err != nil { + t.Fatalf("Error mining block no. %d in initial chain: %+v", i, err) + } + } + + // Mine a chain of 6 blocks, to re-organize the DAG + const reorgChainLength = initialChainLength + 1 + reorgChain := make([]*externalapi.DomainHash, reorgChainLength) + previousBlockHash = consensusConfig.GenesisHash + for i := 0; i < reorgChainLength; i++ { + previousBlockHash, _, err = tc.AddBlock([]*externalapi.DomainHash{previousBlockHash}, nil, nil) + reorgChain[i] = previousBlockHash + if err != nil { + t.Fatalf("Error mining block no. %d in re-org chain: %+v", i, err) + } + } + + stagingArea := model.NewStagingArea() + // Check that every block in the reorg chain has the next block as it's UTXODiffChild, + // except that tip that has virtual, And that the diff is only `{ toRemove: { coinbase } }` + for i, currentBlockHash := range reorgChain { + if i == reorgChainLength-1 { + hasUTXODiffChild, err := tc.UTXODiffStore().HasUTXODiffChild(tc.DatabaseContext(), stagingArea, currentBlockHash) + if err != nil { + t.Fatalf("Error getting HasUTXODiffChild of %s: %+v", currentBlockHash, err) + } + if hasUTXODiffChild { + t.Errorf("Block %s expected utxoDiffChild is virtual, but HasUTXODiffChild returned true", + currentBlockHash) + } + } else { + utxoDiffChild, err := tc.UTXODiffStore().UTXODiffChild(tc.DatabaseContext(), stagingArea, currentBlockHash) + if err != nil { + t.Fatalf("Error getting utxoDiffChild of block No. %d, %s: %+v", i, currentBlockHash, err) + } + expectedUTXODiffChild := reorgChain[i+1] + if !expectedUTXODiffChild.Equal(utxoDiffChild) { + t.Errorf("Block %s expected utxoDiffChild is %s, but got %s instead", + currentBlockHash, expectedUTXODiffChild, utxoDiffChild) + continue + } + } + + // skip the first block, since it's coinbase doesn't create outputs + if i == 0 { + continue + } + + currentBlock, err := tc.BlockStore().Block(tc.DatabaseContext(), stagingArea, currentBlockHash) + if err != nil { + t.Fatalf("Error getting block %s: %+v", currentBlockHash, err) + } + utxoDiff, err := tc.UTXODiffStore().UTXODiff(tc.DatabaseContext(), stagingArea, currentBlockHash) + if err != nil { + t.Fatalf("Error getting utxoDiffChild of %s: %+v", currentBlockHash, err) + } + if !checkIsUTXODiffOnlyRemoveCoinbase(t, utxoDiff, currentBlock) { + t.Errorf("Expected %s to only have toRemove: {%s}, but got %s instead", + currentBlockHash, consensushashing.TransactionID(currentBlock.Transactions[0]), utxoDiff) + } + } + }) +} + +func checkIsUTXODiffOnlyRemoveCoinbase(t *testing.T, utxoDiff externalapi.UTXODiff, currentBlock *externalapi.DomainBlock) bool { + if utxoDiff.ToAdd().Len() > 0 || utxoDiff.ToRemove().Len() > 1 { + return false + } + + iterator := utxoDiff.ToRemove().Iterator() + iterator.First() + outpoint, _, err := iterator.Get() + if err != nil { + t.Fatalf("Error getting from UTXODiff's iterator: %+v", err) + } + if !outpoint.TransactionID.Equal(consensushashing.TransactionID(currentBlock.Transactions[0])) { + return false + } + + return true +} diff --git a/domain/consensus/processes/consensusstatemanager/test_consensus_state_manager.go b/domain/consensus/processes/consensusstatemanager/test_consensus_state_manager.go index 0eb9c2d54..79114e4cf 100644 --- a/domain/consensus/processes/consensusstatemanager/test_consensus_state_manager.go +++ b/domain/consensus/processes/consensusstatemanager/test_consensus_state_manager.go @@ -22,7 +22,8 @@ func (csm *testConsensusStateManager) AddUTXOToMultiset( } func (csm *testConsensusStateManager) ResolveBlockStatus(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash, - useSeparateStagingAreasPerBlock bool) (externalapi.BlockStatus, error) { + useSeparateStagingAreaPerBlock bool) (externalapi.BlockStatus, error) { - return csm.resolveBlockStatus(stagingArea, blockHash, useSeparateStagingAreasPerBlock) + status, _, err := csm.resolveBlockStatus(stagingArea, blockHash, useSeparateStagingAreaPerBlock) + return status, err } diff --git a/domain/consensus/processes/consensusstatemanager/virtual_parents_test.go b/domain/consensus/processes/consensusstatemanager/virtual_parents_test.go index a201ebe64..c7320c0ec 100644 --- a/domain/consensus/processes/consensusstatemanager/virtual_parents_test.go +++ b/domain/consensus/processes/consensusstatemanager/virtual_parents_test.go @@ -48,7 +48,7 @@ func TestConsensusStateManager_pickVirtualParents(t *testing.T) { for j := 0; j <= i; j++ { lastBlock, _, err = tc.AddBlock([]*externalapi.DomainHash{lastBlock}, nil, nil) if err != nil { - t.Fatalf("Failed Adding block to tc: %v", err) + t.Fatalf("Failed Adding block to tc: %+v", err) } } parents = append(parents, lastBlock) @@ -98,7 +98,7 @@ func TestConsensusStateManager_pickVirtualParents(t *testing.T) { for i := 0; i < int(consensusConfig.MaxBlockParents); i++ { block, _, err := tc.AddBlock([]*externalapi.DomainHash{virtualSelectedParent}, nil, nil) if err != nil { - t.Fatalf("Failed Adding block to tc: %v", err) + t.Fatalf("Failed Adding block to tc: %+v", err) } parents = append(parents, block) } diff --git a/domain/consensus/test_consensus_render_to_dot.go b/domain/consensus/test_consensus_render_to_dot.go index 9e676dd42..34593b225 100644 --- a/domain/consensus/test_consensus_render_to_dot.go +++ b/domain/consensus/test_consensus_render_to_dot.go @@ -7,6 +7,8 @@ import ( "io/ioutil" "os/exec" "strings" + + "github.com/kaspanet/kaspad/domain/consensus/model" ) // RenderDAGToDot is a helper function for debugging tests. @@ -28,6 +30,7 @@ func (tc *testConsensus) convertToDot() (string, error) { } defer blocksIterator.Close() + stagingArea := model.NewStagingArea() for ok := blocksIterator.First(); ok; ok = blocksIterator.Next() { hash, err := blocksIterator.Get() if err != nil { @@ -35,7 +38,7 @@ func (tc *testConsensus) convertToDot() (string, error) { } dotScriptBuilder.WriteString(fmt.Sprintf("\t\"%s\";\n", hash)) - parents, err := tc.dagTopologyManager.Parents(nil, hash) + parents, err := tc.dagTopologyManager.Parents(stagingArea, hash) if err != nil { return "", err } diff --git a/domain/consensus/utils/utxo/diff_algebra.go b/domain/consensus/utils/utxo/diff_algebra.go index e7500bdc1..a8ba8f26d 100644 --- a/domain/consensus/utils/utxo/diff_algebra.go +++ b/domain/consensus/utils/utxo/diff_algebra.go @@ -34,23 +34,6 @@ func checkIntersectionWithRule(collection1 utxoCollection, collection2 utxoColle return nil, false } -// minInt returns the smaller of x or y integer values -func minInt(a, b int) int { - if a < b { - return a - } - return b -} - -// intersectionWithRemainderHavingDAAScore calculates an intersection between two utxoCollections -// having same DAA score, returns the result and the remainder from collection1 -func intersectionWithRemainderHavingDAAScore(collection1, collection2 utxoCollection) (result, remainder utxoCollection) { - result = make(utxoCollection, minInt(len(collection1), len(collection2))) - remainder = make(utxoCollection, len(collection1)) - intersectionWithRemainderHavingDAAScoreInPlace(collection1, collection2, result, remainder) - return -} - // intersectionWithRemainderHavingDAAScoreInPlace calculates an intersection between two utxoCollections // having same DAA score, puts it into result and into remainder from collection1 func intersectionWithRemainderHavingDAAScoreInPlace(collection1, collection2, result, remainder utxoCollection) { @@ -63,15 +46,6 @@ func intersectionWithRemainderHavingDAAScoreInPlace(collection1, collection2, re } } -// subtractionHavingDAAScore calculates a subtraction between collection1 and collection2 -// having same DAA score, returns the result -func subtractionHavingDAAScore(collection1, collection2 utxoCollection) (result utxoCollection) { - result = make(utxoCollection, len(collection1)) - - subtractionHavingDAAScoreInPlace(collection1, collection2, result) - return -} - // subtractionHavingDAAScoreInPlace calculates a subtraction between collection1 and collection2 // having same DAA score, puts it into result func subtractionHavingDAAScoreInPlace(collection1, collection2, result utxoCollection) { @@ -82,16 +56,6 @@ func subtractionHavingDAAScoreInPlace(collection1, collection2, result utxoColle } } -// subtractionWithRemainderHavingDAAScore calculates a subtraction between collection1 and collection2 -// having same DAA score, returns the result and the remainder from collection1 -func subtractionWithRemainderHavingDAAScore(collection1, collection2 utxoCollection) (result, remainder utxoCollection) { - result = make(utxoCollection, len(collection1)) - remainder = make(utxoCollection, len(collection1)) - - subtractionWithRemainderHavingDAAScoreInPlace(collection1, collection2, result, remainder) - return -} - // subtractionWithRemainderHavingDAAScoreInPlace calculates a subtraction between collection1 and collection2 // having same DAA score, puts it into result and into remainder from collection1 func subtractionWithRemainderHavingDAAScoreInPlace(collection1, collection2, result, remainder utxoCollection) { @@ -175,13 +139,13 @@ func diffFrom(this, other *mutableUTXODiff) (*mutableUTXODiff, error) { } result := &mutableUTXODiff{ - toAdd: make(utxoCollection, len(this.toRemove)+len(other.toAdd)), - toRemove: make(utxoCollection, len(this.toAdd)+len(other.toRemove)), + toAdd: make(utxoCollection), + toRemove: make(utxoCollection), } // All transactions in this.toAdd: // If they are not in other.toAdd - should be added in result.toRemove - inBothToAdd := make(utxoCollection, len(this.toAdd)) + inBothToAdd := make(utxoCollection) subtractionWithRemainderHavingDAAScoreInPlace(this.toAdd, other.toAdd, result.toRemove, inBothToAdd) // If they are in other.toRemove - base utxoSet is not the same if checkIntersection(inBothToAdd, this.toRemove) != checkIntersection(inBothToAdd, other.toRemove) { @@ -223,13 +187,13 @@ func withDiffInPlace(this *mutableUTXODiff, other *mutableUTXODiff) error { "withDiffInPlace: outpoint %s both in this.toAdd and in other.toAdd", offendingOutpoint) } - intersection := make(utxoCollection, minInt(len(other.toRemove), len(this.toAdd))) + intersection := make(utxoCollection) // If not exists neither in toAdd nor in toRemove - add to toRemove intersectionWithRemainderHavingDAAScoreInPlace(other.toRemove, this.toAdd, intersection, this.toRemove) // If already exists in toAdd with the same DAA score - remove from toAdd this.toAdd.removeMultiple(intersection) - intersection = make(utxoCollection, minInt(len(other.toAdd), len(this.toRemove))) + intersection = make(utxoCollection) // If not exists neither in toAdd nor in toRemove, or exists in toRemove with different DAA score - add to toAdd intersectionWithRemainderHavingDAAScoreInPlace(other.toAdd, this.toRemove, intersection, this.toAdd) // If already exists in toRemove with the same DAA score - remove from toRemove diff --git a/domain/consensus/utils/utxo/immutable_utxo_diff.go b/domain/consensus/utils/utxo/immutable_utxo_diff.go index ac5730242..3c0f6d0c2 100644 --- a/domain/consensus/utils/utxo/immutable_utxo_diff.go +++ b/domain/consensus/utils/utxo/immutable_utxo_diff.go @@ -13,7 +13,7 @@ type immutableUTXODiff struct { func (iud *immutableUTXODiff) ToAdd() externalapi.UTXOCollection { if iud.isInvalidated { - panic("Attempt to read from an invalidated UTXODiff") + panic(errors.New("Attempt to read from an invalidated UTXODiff")) } return iud.mutableUTXODiff.ToAdd() @@ -21,7 +21,7 @@ func (iud *immutableUTXODiff) ToAdd() externalapi.UTXOCollection { func (iud *immutableUTXODiff) ToRemove() externalapi.UTXOCollection { if iud.isInvalidated { - panic("Attempt to read from an invalidated UTXODiff") + panic(errors.New("Attempt to read from an invalidated UTXODiff")) } return iud.mutableUTXODiff.ToRemove() @@ -29,7 +29,7 @@ func (iud *immutableUTXODiff) ToRemove() externalapi.UTXOCollection { func (iud *immutableUTXODiff) WithDiff(other externalapi.UTXODiff) (externalapi.UTXODiff, error) { if iud.isInvalidated { - panic("Attempt to read from an invalidated UTXODiff") + panic(errors.New("Attempt to read from an invalidated UTXODiff")) } return iud.mutableUTXODiff.WithDiff(other) @@ -37,7 +37,7 @@ func (iud *immutableUTXODiff) WithDiff(other externalapi.UTXODiff) (externalapi. func (iud *immutableUTXODiff) DiffFrom(other externalapi.UTXODiff) (externalapi.UTXODiff, error) { if iud.isInvalidated { - panic("Attempt to read from an invalidated UTXODiff") + panic(errors.New("Attempt to read from an invalidated UTXODiff")) } return iud.mutableUTXODiff.DiffFrom(other) @@ -74,9 +74,22 @@ func NewUTXODiffFromCollections(toAdd, toRemove externalapi.UTXOCollection) (ext } func (iud *immutableUTXODiff) CloneMutable() externalapi.MutableUTXODiff { + if iud.isInvalidated { + panic(errors.New("Attempt to read from an invalidated UTXODiff")) + } return iud.cloneMutable() } +func (iud *immutableUTXODiff) Reversed() externalapi.UTXODiff { + if iud.isInvalidated { + panic(errors.New("Attempt to read from an invalidated UTXODiff")) + } + return &immutableUTXODiff{ + mutableUTXODiff: iud.mutableUTXODiff.Reversed(), + isInvalidated: false, + } +} + func (iud *immutableUTXODiff) cloneMutable() *mutableUTXODiff { if iud == nil { return nil @@ -84,3 +97,7 @@ func (iud *immutableUTXODiff) cloneMutable() *mutableUTXODiff { return iud.mutableUTXODiff.clone() } + +func (iud immutableUTXODiff) String() string { + return iud.mutableUTXODiff.String() +} diff --git a/domain/consensus/utils/utxo/mutable_utxo_diff.go b/domain/consensus/utils/utxo/mutable_utxo_diff.go index 81add666d..2f328f985 100644 --- a/domain/consensus/utils/utxo/mutable_utxo_diff.go +++ b/domain/consensus/utils/utxo/mutable_utxo_diff.go @@ -157,3 +157,11 @@ func (mud *mutableUTXODiff) clone() *mutableUTXODiff { func (mud *mutableUTXODiff) String() string { return fmt.Sprintf("toAdd: %s; toRemove: %s", mud.toAdd, mud.toRemove) } + +func (mud *mutableUTXODiff) Reversed() *mutableUTXODiff { + return &mutableUTXODiff{ + toAdd: mud.toRemove, + toRemove: mud.toAdd, + immutableReferences: mud.immutableReferences, + } +} diff --git a/stability-tests/netsync/dags-slow/wide-dag-blocks--2^16-delay-factor--1-k--18.json.gz b/stability-tests/netsync/dags-slow/wide-dag-blocks--2^16-delay-factor--1-k--18.json.gz new file mode 100644 index 000000000..fcd885f4e Binary files /dev/null and b/stability-tests/netsync/dags-slow/wide-dag-blocks--2^16-delay-factor--1-k--18.json.gz differ diff --git a/stability-tests/reorg/reorg.go b/stability-tests/reorg/reorg.go index 3f1fbbdfb..aac842608 100644 --- a/stability-tests/reorg/reorg.go +++ b/stability-tests/reorg/reorg.go @@ -19,6 +19,7 @@ import ( func testReorg(cfg *configFlags) { consensusConfig := consensus.Config{Params: dagconfig.DevnetParams} consensusConfig.SkipProofOfWork = true + consensusConfig.DisableDifficultyAdjustment = true factory := consensus.NewFactory() tc, teardown, err := factory.NewTestConsensus(&consensusConfig, "ReorgHonest") @@ -130,7 +131,7 @@ func testReorg(cfg *configFlags) { doneChan <- struct{}{} }) - const timeout = 10 * time.Minute + const timeout = 12 * time.Hour select { case <-doneChan: case <-time.After(timeout): diff --git a/stability-tests/reorg/run/run-full-finality-window-reorg.sh b/stability-tests/reorg/run/run-full-finality-window-reorg.sh new file mode 100755 index 000000000..a72a46806 --- /dev/null +++ b/stability-tests/reorg/run/run-full-finality-window-reorg.sh @@ -0,0 +1,12 @@ +reorg --dag-file ../../netsync/dags-slow/wide-dag-blocks--2^16-delay-factor--1-k--18.json.gz --profile=6061 + +TEST_EXIT_CODE=$? +echo "Exit code: $TEST_EXIT_CODE" + + +if [ $TEST_EXIT_CODE -eq 0 ]; then + echo "reorg test: PASSED" + exit 0 +fi +echo "reorg test: FAILED" +exit 1 diff --git a/stability-tests/run/run-slow.sh b/stability-tests/run/run-slow.sh index 896020413..509404eb5 100755 --- a/stability-tests/run/run-slow.sh +++ b/stability-tests/run/run-slow.sh @@ -35,7 +35,7 @@ cd "${PROJECT_ROOT}/orphans/run" && ./run.sh || failedTests+=("orphans") echo "Done running orphans" echo "Running reorg" -cd "${PROJECT_ROOT}/reorg/run" && ./run.sh || failedTests+=("reorg") +cd "${PROJECT_ROOT}/reorg/run" && ./run-full-finality-window-reorg.sh || failedTests+=("reorg") echo "Done running reorg" echo "Running mempool-limits"