From 48e1a2c396a25f9f84b01b06c1da1f65e2d0387a Mon Sep 17 00:00:00 2001 From: Ori Newman Date: Mon, 14 Dec 2020 17:53:08 +0200 Subject: [PATCH] New headers first flow (#1211) * Get rid of insertMode * Rename AddBlockToVirtual->AddBlock * When F is not in the future of P, enforce finality with P and not with F. * Don't allow blocks with invalid parents or with missing block body * Check finality violation before checking block status * Implement CalculateIndependentPruningPoint * Move checkBlockStatus to validateBlock * Add ValidateBlock to block processor interface * Adjust SetPruningPoint to the new IBD flow * Add pruning store to CSM's constructor * Flip wrong condition on AddHeaderTip * Fix func (hts *headerSelectedTipStore) Has * Fix block stage order * Call to ValidateBodyInContext from validatePostProofOfWork * Enable overrideDAGParams * Update log * Rename SetPruningPoint to ValidateAndInsertPruningPoint and move most of its logic inside block processor * Rename hasValidatedHeader->hasValidatedOnlyHeader * Fix typo * Name return values for fetchMissingUTXOSet * Add comment * Return ErrMissingParents when block body is missing * Add logs and comments * Fix merge error * Fix pruning point calculation to be by virtual selected parent * Replace CalculateIndependentPruningPoint to CalculatePruningPointByHeaderSelectedTip * Fix isAwaitingUTXOSet to check pruning point by headers * Change isAwaitingUTXOSet indication * Remove IsBlockInHeaderPruningPointFuture from BlockInfo * Fix LowestChainBlockAboveOrEqualToBlueScore * Add validateNewPruningPointTransactions * Add validateNewPruningAgainstPastUTXO * Rename set_pruning_utxo_set.go to update_pruning_utxo_set.go * Check missing block body hashes by missing block instead of status * Validate pruning point against past UTXO with the pruning point as block hash * Remove virtualHeaderHash * Fix comment * Fix imports --- .../flows/blockrelay/handle_relay_invs.go | 1 - app/protocol/flows/blockrelay/ibd.go | 26 ++- domain/consensus/consensus.go | 80 ++++--- domain/consensus/consensus_test.go | 6 - .../headertipsstore.go | 100 +++++++++ .../headertipsstore/headertipsstore.go | 101 --------- domain/consensus/factory.go | 47 ++-- .../consensus/model/externalapi/blockinfo.go | 2 - .../consensus/model/externalapi/consensus.go | 2 +- domain/consensus/model/externalapi/sync.go | 31 +-- ...nterface_datastructures_headertipsstore.go | 10 +- .../interface_processes_blockprocessor.go | 1 + ...terface_processes_consensusstatemanager.go | 5 +- .../interface_processes_finalitymanager.go | 1 - .../interface_processes_headertipsmanager.go | 5 +- .../interface_processes_pruningmanager.go | 5 +- .../model/interface_processes_syncmanager.go | 1 - .../consensus/model/testapi/test_consensus.go | 4 +- .../blockprocessor/blockprocessor.go | 69 +++--- .../blockprocessor/validateandinsertblock.go | 200 ++++-------------- .../validateandinsertpruningpoint.go | 38 ++++ .../processes/blockprocessor/validateblock.go | 67 ++++++ .../blockvalidator/block_body_in_context.go | 4 + .../blockvalidator/block_body_in_isolation.go | 4 + .../blockvalidator/block_header_in_context.go | 10 +- .../block_header_in_isolation.go | 4 + .../consensus/processes/blockvalidator/log.go | 7 + .../processes/blockvalidator/proof_of_work.go | 35 ++- .../add_block_to_virtual.go | 52 ++--- .../check_finality_violation.go | 66 ++++-- .../consensus_state_manager.go | 48 +++-- ...utxo_set.go => update_pruning_utxo_set.go} | 95 ++++----- .../verify_and_build_utxo.go | 5 +- .../dagtraversalmanager.go | 9 +- .../finalitymanager/finality_manager.go | 25 --- .../headertipsmanager.go | 55 +++++ .../headertipsmanager/headertipsmanager.go | 59 ------ .../headertipsmanager/selected_tip.go | 17 -- .../consensus/processes/pruningmanager/log.go | 5 + .../pruningmanager/pruningmanager.go | 98 ++++++--- .../reachability_external_test.go | 2 +- .../processes/syncmanager/antipast.go | 31 +-- .../processes/syncmanager/syncinfo.go | 69 ++---- .../processes/syncmanager/syncmanager.go | 44 ++-- domain/consensus/ruleerrors/rule_error.go | 2 + domain/consensus/test_consensus_getters.go | 6 +- infrastructure/config/network.go | 5 + infrastructure/logger/logger.go | 10 +- 48 files changed, 769 insertions(+), 800 deletions(-) create mode 100644 domain/consensus/datastructures/headersselectedtipstore/headertipsstore.go delete mode 100644 domain/consensus/datastructures/headertipsstore/headertipsstore.go create mode 100644 domain/consensus/processes/blockprocessor/validateandinsertpruningpoint.go create mode 100644 domain/consensus/processes/blockprocessor/validateblock.go create mode 100644 domain/consensus/processes/blockvalidator/log.go rename domain/consensus/processes/consensusstatemanager/{set_pruning_utxo_set.go => update_pruning_utxo_set.go} (53%) create mode 100644 domain/consensus/processes/headersselectedtipmanager/headertipsmanager.go delete mode 100644 domain/consensus/processes/headertipsmanager/headertipsmanager.go delete mode 100644 domain/consensus/processes/headertipsmanager/selected_tip.go create mode 100644 domain/consensus/processes/pruningmanager/log.go diff --git a/app/protocol/flows/blockrelay/handle_relay_invs.go b/app/protocol/flows/blockrelay/handle_relay_invs.go index 3d0546d02..86096196e 100644 --- a/app/protocol/flows/blockrelay/handle_relay_invs.go +++ b/app/protocol/flows/blockrelay/handle_relay_invs.go @@ -212,7 +212,6 @@ func (flow *handleRelayInvsFlow) processBlock(block *externalapi.DomainBlock) ([ return missingParentsError.MissingParentHashes, nil } log.Warnf("Rejected block %s from %s: %s", blockHash, flow.peer, err) - return nil, protocolerrors.Wrapf(true, err, "got invalid block %s from relay", blockHash) } return nil, nil diff --git a/app/protocol/flows/blockrelay/ibd.go b/app/protocol/flows/blockrelay/ibd.go index e5fc983c8..62b5fc157 100644 --- a/app/protocol/flows/blockrelay/ibd.go +++ b/app/protocol/flows/blockrelay/ibd.go @@ -35,7 +35,7 @@ func (flow *handleRelayInvsFlow) runIBDIfNotRunning(highHash *externalapi.Domain if err != nil { return err } - if syncInfo.State == externalapi.SyncStateAwaitingUTXOSet { + if syncInfo.IsAwaitingUTXOSet { found, err := flow.fetchMissingUTXOSet(syncInfo.IBDRootUTXOBlockHash) if err != nil { return err @@ -190,8 +190,8 @@ func (flow *handleRelayInvsFlow) processHeader(msgBlockHeader *appmessage.MsgBlo return nil } -func (flow *handleRelayInvsFlow) fetchMissingUTXOSet(ibdRootHash *externalapi.DomainHash) (bool, error) { - err := flow.outgoingRoute.Enqueue(appmessage.NewMsgRequestIBDRootUTXOSetAndBlock(ibdRootHash)) +func (flow *handleRelayInvsFlow) fetchMissingUTXOSet(ibdRootHash *externalapi.DomainHash) (succeed bool, err error) { + err = flow.outgoingRoute.Enqueue(appmessage.NewMsgRequestIBDRootUTXOSetAndBlock(ibdRootHash)) if err != nil { return false, err } @@ -205,17 +205,23 @@ func (flow *handleRelayInvsFlow) fetchMissingUTXOSet(ibdRootHash *externalapi.Do return false, nil } - err = flow.Domain().Consensus().ValidateAndInsertBlock(block) - if err != nil { - blockHash := consensushashing.BlockHash(block) - return false, protocolerrors.ConvertToBanningProtocolErrorIfRuleError(err, "got invalid block %s during IBD", blockHash) - } - - err = flow.Domain().Consensus().SetPruningPointUTXOSet(utxoSet) + err = flow.Domain().Consensus().ValidateAndInsertPruningPoint(block, utxoSet) if err != nil { return false, protocolerrors.ConvertToBanningProtocolErrorIfRuleError(err, "error with IBD root UTXO set") } + syncInfo, err := flow.Domain().Consensus().GetSyncInfo() + if err != nil { + return false, err + } + + // TODO: Find a better way to deal with finality conflicts. + if syncInfo.IsAwaitingUTXOSet { + log.Warnf("Still awaiting for UTXO set. This can happen only because the given pruning point violates " + + "finality. If this keeps happening delete the data directory and restart your node.") + return false, nil + } + return true, nil } diff --git a/domain/consensus/consensus.go b/domain/consensus/consensus.go index 007e5a7a0..3ecfe1b99 100644 --- a/domain/consensus/consensus.go +++ b/domain/consensus/consensus.go @@ -25,25 +25,25 @@ type consensus struct { dagTraversalManager model.DAGTraversalManager difficultyManager model.DifficultyManager ghostdagManager model.GHOSTDAGManager - headerTipsManager model.HeaderTipsManager + headerTipsManager model.HeadersSelectedTipManager mergeDepthManager model.MergeDepthManager pruningManager model.PruningManager reachabilityManager model.ReachabilityManager finalityManager model.FinalityManager - acceptanceDataStore model.AcceptanceDataStore - blockStore model.BlockStore - blockHeaderStore model.BlockHeaderStore - pruningStore model.PruningStore - ghostdagDataStore model.GHOSTDAGDataStore - blockRelationStore model.BlockRelationStore - blockStatusStore model.BlockStatusStore - consensusStateStore model.ConsensusStateStore - headerTipsStore model.HeaderTipsStore - multisetStore model.MultisetStore - reachabilityDataStore model.ReachabilityDataStore - utxoDiffStore model.UTXODiffStore - finalityStore model.FinalityStore + acceptanceDataStore model.AcceptanceDataStore + blockStore model.BlockStore + blockHeaderStore model.BlockHeaderStore + pruningStore model.PruningStore + ghostdagDataStore model.GHOSTDAGDataStore + blockRelationStore model.BlockRelationStore + blockStatusStore model.BlockStatusStore + consensusStateStore model.ConsensusStateStore + headersSelectedTipStore model.HeaderSelectedTipStore + multisetStore model.MultisetStore + reachabilityDataStore model.ReachabilityDataStore + utxoDiffStore model.UTXODiffStore + finalityStore model.FinalityStore } // BuildBlock builds a block over the current state, with the transactions @@ -138,12 +138,6 @@ func (s *consensus) GetBlockInfo(blockHash *externalapi.DomainHash) (*externalap blockInfo.BlueScore = ghostdagData.BlueScore() - isBlockInHeaderPruningPointFuture, err := s.syncManager.IsBlockInHeaderPruningPointFuture(blockHash) - if err != nil { - return nil, err - } - blockInfo.IsBlockInHeaderPruningPointFuture = isBlockInHeaderPruningPointFuture - return blockInfo, nil } @@ -183,11 +177,11 @@ func (s *consensus) GetPruningPointUTXOSet(expectedPruningPointHash *externalapi return serializedUTXOSet, nil } -func (s *consensus) SetPruningPointUTXOSet(serializedUTXOSet []byte) error { +func (s *consensus) ValidateAndInsertPruningPoint(newPruningPoint *externalapi.DomainBlock, serializedUTXOSet []byte) error { s.lock.Lock() defer s.lock.Unlock() - return s.consensusStateManager.SetPruningPointUTXOSet(serializedUTXOSet) + return s.blockProcessor.ValidateAndInsertPruningPoint(newPruningPoint, serializedUTXOSet) } func (s *consensus) GetVirtualSelectedParent() (*externalapi.DomainBlock, error) { @@ -201,27 +195,6 @@ func (s *consensus) GetVirtualSelectedParent() (*externalapi.DomainBlock, error) return s.blockStore.Block(s.databaseContext, virtualGHOSTDAGData.SelectedParent()) } -func (s *consensus) CreateBlockLocator(lowHash, highHash *externalapi.DomainHash, limit uint32) (externalapi.BlockLocator, error) { - s.lock.Lock() - defer s.lock.Unlock() - - return s.syncManager.CreateBlockLocator(lowHash, highHash, limit) -} - -func (s *consensus) FindNextBlockLocatorBoundaries(blockLocator externalapi.BlockLocator) (lowHash, highHash *externalapi.DomainHash, err error) { - s.lock.Lock() - defer s.lock.Unlock() - - return s.syncManager.FindNextBlockLocatorBoundaries(blockLocator) -} - -func (s *consensus) GetSyncInfo() (*externalapi.SyncInfo, error) { - s.lock.Lock() - defer s.lock.Unlock() - - return s.syncManager.GetSyncInfo() -} - func (s *consensus) Tips() ([]*externalapi.DomainHash, error) { return s.consensusStateStore.Tips(s.databaseContext) } @@ -246,3 +219,24 @@ func (s *consensus) GetVirtualInfo() (*externalapi.VirtualInfo, error) { PastMedianTime: pastMedianTime, }, nil } + +func (s *consensus) CreateBlockLocator(lowHash, highHash *externalapi.DomainHash, limit uint32) (externalapi.BlockLocator, error) { + s.lock.Lock() + defer s.lock.Unlock() + + return s.syncManager.CreateBlockLocator(lowHash, highHash, limit) +} + +func (s *consensus) FindNextBlockLocatorBoundaries(blockLocator externalapi.BlockLocator) (lowHash, highHash *externalapi.DomainHash, err error) { + s.lock.Lock() + defer s.lock.Unlock() + + return s.syncManager.FindNextBlockLocatorBoundaries(blockLocator) +} + +func (s *consensus) GetSyncInfo() (*externalapi.SyncInfo, error) { + s.lock.Lock() + defer s.lock.Unlock() + + return s.syncManager.GetSyncInfo() +} diff --git a/domain/consensus/consensus_test.go b/domain/consensus/consensus_test.go index 67662b591..964580e64 100644 --- a/domain/consensus/consensus_test.go +++ b/domain/consensus/consensus_test.go @@ -42,9 +42,6 @@ func TestConsensus_GetBlockInfo(t *testing.T) { if info.BlockStatus != externalapi.StatusInvalid { t.Fatalf("Expected block status: %s, instead got: %s", externalapi.StatusInvalid, info.BlockStatus) } - if info.IsBlockInHeaderPruningPointFuture != false { - t.Fatalf("Expected IsBlockInHeaderPruningPointFuture=false, instead found: %t", info.IsBlockInHeaderPruningPointFuture) - } emptyCoinbase := externalapi.DomainCoinbaseData{} validBlock, err := consensus.BuildBlock(&emptyCoinbase, nil) @@ -68,9 +65,6 @@ func TestConsensus_GetBlockInfo(t *testing.T) { if info.BlockStatus != externalapi.StatusValid { t.Fatalf("Expected block status: %s, instead got: %s", externalapi.StatusValid, info.BlockStatus) } - if info.IsBlockInHeaderPruningPointFuture != true { - t.Fatalf("Expected IsBlockInHeaderPruningPointFuture=true, instead found: %t", info.IsBlockInHeaderPruningPointFuture) - } }) } diff --git a/domain/consensus/datastructures/headersselectedtipstore/headertipsstore.go b/domain/consensus/datastructures/headersselectedtipstore/headertipsstore.go new file mode 100644 index 000000000..8926c1c12 --- /dev/null +++ b/domain/consensus/datastructures/headersselectedtipstore/headertipsstore.go @@ -0,0 +1,100 @@ +package headersselectedtipstore + +import ( + "github.com/golang/protobuf/proto" + "github.com/kaspanet/kaspad/domain/consensus/database/serialization" + "github.com/kaspanet/kaspad/domain/consensus/model" + "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" + "github.com/kaspanet/kaspad/domain/consensus/utils/dbkeys" +) + +var headerSelectedTipKey = dbkeys.MakeBucket().Key([]byte("headers-selected-tip")) + +type headerSelectedTipStore struct { + staging *externalapi.DomainHash + cache *externalapi.DomainHash +} + +// New instantiates a new HeaderSelectedTipStore +func New() model.HeaderSelectedTipStore { + return &headerSelectedTipStore{} +} + +func (hts *headerSelectedTipStore) Has(dbContext model.DBReader) (bool, error) { + if hts.staging != nil { + return true, nil + } + + if hts.cache != nil { + return true, nil + } + + return dbContext.Has(headerSelectedTipKey) +} + +func (hts *headerSelectedTipStore) Discard() { + hts.staging = nil +} + +func (hts *headerSelectedTipStore) Commit(dbTx model.DBTransaction) error { + if hts.staging == nil { + return nil + } + + selectedTipBytes, err := hts.serializeHeadersSelectedTip(hts.staging) + if err != nil { + return err + } + err = dbTx.Put(headerSelectedTipKey, selectedTipBytes) + if err != nil { + return err + } + hts.cache = hts.staging + + hts.Discard() + return nil +} + +func (hts *headerSelectedTipStore) Stage(selectedTip *externalapi.DomainHash) { + hts.staging = selectedTip.Clone() +} + +func (hts *headerSelectedTipStore) IsStaged() bool { + return hts.staging != nil +} + +func (hts *headerSelectedTipStore) HeadersSelectedTip(dbContext model.DBReader) (*externalapi.DomainHash, error) { + if hts.staging != nil { + return hts.staging.Clone(), nil + } + + if hts.cache != nil { + return hts.cache.Clone(), nil + } + + selectedTipBytes, err := dbContext.Get(headerSelectedTipKey) + if err != nil { + return nil, err + } + + selectedTip, err := hts.deserializeHeadersSelectedTip(selectedTipBytes) + if err != nil { + return nil, err + } + hts.cache = selectedTip + return hts.cache.Clone(), nil +} + +func (hts *headerSelectedTipStore) serializeHeadersSelectedTip(selectedTip *externalapi.DomainHash) ([]byte, error) { + return proto.Marshal(serialization.DomainHashToDbHash(selectedTip)) +} + +func (hts *headerSelectedTipStore) deserializeHeadersSelectedTip(selectedTipBytes []byte) (*externalapi.DomainHash, error) { + dbHash := &serialization.DbHash{} + err := proto.Unmarshal(selectedTipBytes, dbHash) + if err != nil { + return nil, err + } + + return serialization.DbHashToDomainHash(dbHash) +} diff --git a/domain/consensus/datastructures/headertipsstore/headertipsstore.go b/domain/consensus/datastructures/headertipsstore/headertipsstore.go deleted file mode 100644 index cb36b6ee1..000000000 --- a/domain/consensus/datastructures/headertipsstore/headertipsstore.go +++ /dev/null @@ -1,101 +0,0 @@ -package headertipsstore - -import ( - "github.com/golang/protobuf/proto" - "github.com/kaspanet/kaspad/domain/consensus/database/serialization" - "github.com/kaspanet/kaspad/domain/consensus/model" - "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" - "github.com/kaspanet/kaspad/domain/consensus/utils/dbkeys" -) - -var headerTipsKey = dbkeys.MakeBucket().Key([]byte("header-tips")) - -type headerTipsStore struct { - staging []*externalapi.DomainHash - cache []*externalapi.DomainHash -} - -// New instantiates a new HeaderTipsStore -func New() model.HeaderTipsStore { - return &headerTipsStore{} -} - -func (hts *headerTipsStore) HasTips(dbContext model.DBReader) (bool, error) { - if len(hts.staging) > 0 { - return true, nil - } - - if len(hts.cache) > 0 { - return true, nil - } - - return dbContext.Has(headerTipsKey) -} - -func (hts *headerTipsStore) Discard() { - hts.staging = nil -} - -func (hts *headerTipsStore) Commit(dbTx model.DBTransaction) error { - if hts.staging == nil { - return nil - } - - tipsBytes, err := hts.serializeTips(hts.staging) - if err != nil { - return err - } - err = dbTx.Put(headerTipsKey, tipsBytes) - if err != nil { - return err - } - hts.cache = hts.staging - - hts.Discard() - return nil -} - -func (hts *headerTipsStore) Stage(tips []*externalapi.DomainHash) { - hts.staging = externalapi.CloneHashes(tips) -} - -func (hts *headerTipsStore) IsStaged() bool { - return hts.staging != nil -} - -func (hts *headerTipsStore) Tips(dbContext model.DBReader) ([]*externalapi.DomainHash, error) { - if hts.staging != nil { - return externalapi.CloneHashes(hts.staging), nil - } - - if hts.cache != nil { - return externalapi.CloneHashes(hts.cache), nil - } - - tipsBytes, err := dbContext.Get(headerTipsKey) - if err != nil { - return nil, err - } - - tips, err := hts.deserializeTips(tipsBytes) - if err != nil { - return nil, err - } - hts.cache = tips - return externalapi.CloneHashes(tips), nil -} - -func (hts *headerTipsStore) serializeTips(tips []*externalapi.DomainHash) ([]byte, error) { - dbTips := serialization.HeaderTipsToDBHeaderTips(tips) - return proto.Marshal(dbTips) -} - -func (hts *headerTipsStore) deserializeTips(tipsBytes []byte) ([]*externalapi.DomainHash, error) { - dbTips := &serialization.DbHeaderTips{} - err := proto.Unmarshal(tipsBytes, dbTips) - if err != nil { - return nil, err - } - - return serialization.DBHeaderTipsToHeaderTips(dbTips) -} diff --git a/domain/consensus/factory.go b/domain/consensus/factory.go index ee8f93414..dcfdf3a81 100644 --- a/domain/consensus/factory.go +++ b/domain/consensus/factory.go @@ -17,7 +17,7 @@ import ( "github.com/kaspanet/kaspad/domain/consensus/datastructures/consensusstatestore" "github.com/kaspanet/kaspad/domain/consensus/datastructures/finalitystore" "github.com/kaspanet/kaspad/domain/consensus/datastructures/ghostdagdatastore" - "github.com/kaspanet/kaspad/domain/consensus/datastructures/headertipsstore" + "github.com/kaspanet/kaspad/domain/consensus/datastructures/headersselectedtipstore" "github.com/kaspanet/kaspad/domain/consensus/datastructures/multisetstore" "github.com/kaspanet/kaspad/domain/consensus/datastructures/pruningstore" "github.com/kaspanet/kaspad/domain/consensus/datastructures/reachabilitydatastore" @@ -32,7 +32,7 @@ import ( "github.com/kaspanet/kaspad/domain/consensus/processes/dagtopologymanager" "github.com/kaspanet/kaspad/domain/consensus/processes/difficultymanager" "github.com/kaspanet/kaspad/domain/consensus/processes/ghostdagmanager" - "github.com/kaspanet/kaspad/domain/consensus/processes/headertipsmanager" + "github.com/kaspanet/kaspad/domain/consensus/processes/headersselectedtipmanager" "github.com/kaspanet/kaspad/domain/consensus/processes/mergedepthmanager" "github.com/kaspanet/kaspad/domain/consensus/processes/pastmediantimemanager" "github.com/kaspanet/kaspad/domain/consensus/processes/pruningmanager" @@ -81,7 +81,7 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat utxoDiffStore := utxodiffstore.New(200) consensusStateStore := consensusstatestore.New() ghostdagDataStore := ghostdagdatastore.New(10_000) - headerTipsStore := headertipsstore.New() + headersSelectedTipStore := headersselectedtipstore.New() finalityStore := finalitystore.New(200) // Processes @@ -139,7 +139,7 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat dagParams.CoinbasePayloadScriptPublicKeyMaxLength, ghostdagDataStore, acceptanceDataStore) - headerTipsManager := headertipsmanager.New(dbManager, dagTopologyManager, ghostdagManager, headerTipsStore) + headerTipsManager := headersselectedtipmanager.New(dbManager, dagTopologyManager, ghostdagManager, headersSelectedTipStore) genesisHash := dagParams.GenesisHash finalityManager := finalitymanager.New( dbManager, @@ -211,7 +211,8 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat blockRelationStore, acceptanceDataStore, blockHeaderStore, - headerTipsStore) + headersSelectedTipStore, + pruningStore) if err != nil { return nil, err } @@ -225,6 +226,7 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat ghostdagDataStore, pruningStore, blockStatusStore, + headersSelectedTipStore, multisetStore, acceptanceDataStore, blockStore, @@ -236,17 +238,16 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat syncManager := syncmanager.New( dbManager, genesisHash, - dagParams.TargetTimePerBlock.Milliseconds(), dagTraversalManager, dagTopologyManager, ghostdagManager, - consensusStateManager, + pruningManager, ghostdagDataStore, blockStatusStore, blockHeaderStore, - headerTipsStore, - blockStore) + blockStore, + pruningStore) blockBuilder := blockbuilder.New( dbManager, @@ -287,7 +288,7 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat reachabilityDataStore, utxoDiffStore, blockHeaderStore, - headerTipsStore, + headersSelectedTipStore, finalityStore) c := &consensus{ @@ -312,19 +313,19 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat reachabilityManager: reachabilityManager, finalityManager: finalityManager, - acceptanceDataStore: acceptanceDataStore, - blockStore: blockStore, - blockHeaderStore: blockHeaderStore, - pruningStore: pruningStore, - ghostdagDataStore: ghostdagDataStore, - blockStatusStore: blockStatusStore, - blockRelationStore: blockRelationStore, - consensusStateStore: consensusStateStore, - headerTipsStore: headerTipsStore, - multisetStore: multisetStore, - reachabilityDataStore: reachabilityDataStore, - utxoDiffStore: utxoDiffStore, - finalityStore: finalityStore, + acceptanceDataStore: acceptanceDataStore, + blockStore: blockStore, + blockHeaderStore: blockHeaderStore, + pruningStore: pruningStore, + ghostdagDataStore: ghostdagDataStore, + blockStatusStore: blockStatusStore, + blockRelationStore: blockRelationStore, + consensusStateStore: consensusStateStore, + headersSelectedTipStore: headersSelectedTipStore, + multisetStore: multisetStore, + reachabilityDataStore: reachabilityDataStore, + utxoDiffStore: utxoDiffStore, + finalityStore: finalityStore, } genesisInfo, err := c.GetBlockInfo(genesisHash) diff --git a/domain/consensus/model/externalapi/blockinfo.go b/domain/consensus/model/externalapi/blockinfo.go index 8df85f63f..dcd7128de 100644 --- a/domain/consensus/model/externalapi/blockinfo.go +++ b/domain/consensus/model/externalapi/blockinfo.go @@ -5,6 +5,4 @@ type BlockInfo struct { Exists bool BlockStatus BlockStatus BlueScore uint64 - - IsBlockInHeaderPruningPointFuture bool } diff --git a/domain/consensus/model/externalapi/consensus.go b/domain/consensus/model/externalapi/consensus.go index ccf94de6b..ef63cd2a2 100644 --- a/domain/consensus/model/externalapi/consensus.go +++ b/domain/consensus/model/externalapi/consensus.go @@ -13,7 +13,7 @@ type Consensus interface { GetHashesBetween(lowHash, highHash *DomainHash) ([]*DomainHash, error) GetMissingBlockBodyHashes(highHash *DomainHash) ([]*DomainHash, error) GetPruningPointUTXOSet(expectedPruningPointHash *DomainHash) ([]byte, error) - SetPruningPointUTXOSet(serializedUTXOSet []byte) error + ValidateAndInsertPruningPoint(newPruningPoint *DomainBlock, serializedUTXOSet []byte) error GetVirtualSelectedParent() (*DomainBlock, error) CreateBlockLocator(lowHash, highHash *DomainHash, limit uint32) (BlockLocator, error) FindNextBlockLocatorBoundaries(blockLocator BlockLocator) (lowHash, highHash *DomainHash, err error) diff --git a/domain/consensus/model/externalapi/sync.go b/domain/consensus/model/externalapi/sync.go index 758581dc4..d5f11a499 100644 --- a/domain/consensus/model/externalapi/sync.go +++ b/domain/consensus/model/externalapi/sync.go @@ -1,37 +1,8 @@ package externalapi -import "fmt" - -// Each of the following represent one of the possible sync -// states of the consensus -const ( - SyncStateSynced SyncState = iota - SyncStateAwaitingGenesis - SyncStateAwaitingUTXOSet - SyncStateAwaitingBlockBodies -) - -// SyncState represents the current sync state of the consensus -type SyncState uint8 - -func (s SyncState) String() string { - switch s { - case SyncStateSynced: - return "SyncStateSynced" - case SyncStateAwaitingGenesis: - return "SyncStateAwaitingGenesis" - case SyncStateAwaitingUTXOSet: - return "SyncStateAwaitingUTXOSet" - case SyncStateAwaitingBlockBodies: - return "SyncStateAwaitingBlockBodies" - } - - return fmt.Sprintf("", s) -} - // SyncInfo holds info about the current sync state of the consensus type SyncInfo struct { - State SyncState + IsAwaitingUTXOSet bool IBDRootUTXOBlockHash *DomainHash HeaderCount uint64 BlockCount uint64 diff --git a/domain/consensus/model/interface_datastructures_headertipsstore.go b/domain/consensus/model/interface_datastructures_headertipsstore.go index df3bc6dca..b4788af75 100644 --- a/domain/consensus/model/interface_datastructures_headertipsstore.go +++ b/domain/consensus/model/interface_datastructures_headertipsstore.go @@ -2,11 +2,11 @@ package model import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" -// HeaderTipsStore represents a store of the header tips -type HeaderTipsStore interface { +// HeaderSelectedTipStore represents a store of the headers selected tip +type HeaderSelectedTipStore interface { Store - Stage(tips []*externalapi.DomainHash) + Stage(selectedTip *externalapi.DomainHash) IsStaged() bool - Tips(dbContext DBReader) ([]*externalapi.DomainHash, error) - HasTips(dbContext DBReader) (bool, error) + HeadersSelectedTip(dbContext DBReader) (*externalapi.DomainHash, error) + Has(dbContext DBReader) (bool, error) } diff --git a/domain/consensus/model/interface_processes_blockprocessor.go b/domain/consensus/model/interface_processes_blockprocessor.go index 0f7df8090..471358249 100644 --- a/domain/consensus/model/interface_processes_blockprocessor.go +++ b/domain/consensus/model/interface_processes_blockprocessor.go @@ -5,4 +5,5 @@ import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" // BlockProcessor is responsible for processing incoming blocks type BlockProcessor interface { ValidateAndInsertBlock(block *externalapi.DomainBlock) error + ValidateAndInsertPruningPoint(newPruningPoint *externalapi.DomainBlock, serializedUTXOSet []byte) error } diff --git a/domain/consensus/model/interface_processes_consensusstatemanager.go b/domain/consensus/model/interface_processes_consensusstatemanager.go index 208eef3ec..78fb0474b 100644 --- a/domain/consensus/model/interface_processes_consensusstatemanager.go +++ b/domain/consensus/model/interface_processes_consensusstatemanager.go @@ -4,10 +4,9 @@ import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" // ConsensusStateManager manages the node's consensus state type ConsensusStateManager interface { - AddBlockToVirtual(blockHash *externalapi.DomainHash) error + AddBlock(blockHash *externalapi.DomainHash) error PopulateTransactionWithUTXOEntries(transaction *externalapi.DomainTransaction) error - SetPruningPointUTXOSet(serializedUTXOSet []byte) error + UpdatePruningPoint(newPruningPoint *externalapi.DomainBlock, serializedUTXOSet []byte) error RestorePastUTXOSetIterator(blockHash *externalapi.DomainHash) (ReadOnlyUTXOSetIterator, error) - HeaderTipsPruningPoint() (*externalapi.DomainHash, error) CalculatePastUTXOAndAcceptanceData(blockHash *externalapi.DomainHash) (UTXODiff, AcceptanceData, Multiset, error) } diff --git a/domain/consensus/model/interface_processes_finalitymanager.go b/domain/consensus/model/interface_processes_finalitymanager.go index c58a5c339..04efa0ab5 100644 --- a/domain/consensus/model/interface_processes_finalitymanager.go +++ b/domain/consensus/model/interface_processes_finalitymanager.go @@ -4,7 +4,6 @@ import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" // FinalityManager provides method to validate that a block does not violate finality type FinalityManager interface { - IsViolatingFinality(blockHash *externalapi.DomainHash) (bool, error) VirtualFinalityPoint() (*externalapi.DomainHash, error) FinalityPoint(blockHash *externalapi.DomainHash) (*externalapi.DomainHash, error) } diff --git a/domain/consensus/model/interface_processes_headertipsmanager.go b/domain/consensus/model/interface_processes_headertipsmanager.go index deea0f4c8..9ff6ed68e 100644 --- a/domain/consensus/model/interface_processes_headertipsmanager.go +++ b/domain/consensus/model/interface_processes_headertipsmanager.go @@ -2,8 +2,7 @@ package model import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" -// HeaderTipsManager manages the state of the header tips -type HeaderTipsManager interface { +// HeadersSelectedTipManager manages the state of the headers selected tip +type HeadersSelectedTipManager interface { AddHeaderTip(hash *externalapi.DomainHash) error - SelectedTip() (*externalapi.DomainHash, error) } diff --git a/domain/consensus/model/interface_processes_pruningmanager.go b/domain/consensus/model/interface_processes_pruningmanager.go index 09322ebad..6df77895b 100644 --- a/domain/consensus/model/interface_processes_pruningmanager.go +++ b/domain/consensus/model/interface_processes_pruningmanager.go @@ -1,6 +1,9 @@ package model +import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" + // PruningManager resolves and manages the current pruning point type PruningManager interface { - FindNextPruningPoint() error + UpdatePruningPointByVirtual() error + CalculatePruningPointByHeaderSelectedTip() (*externalapi.DomainHash, error) } diff --git a/domain/consensus/model/interface_processes_syncmanager.go b/domain/consensus/model/interface_processes_syncmanager.go index cc0a32567..165659c53 100644 --- a/domain/consensus/model/interface_processes_syncmanager.go +++ b/domain/consensus/model/interface_processes_syncmanager.go @@ -8,6 +8,5 @@ type SyncManager interface { GetMissingBlockBodyHashes(highHash *externalapi.DomainHash) ([]*externalapi.DomainHash, error) CreateBlockLocator(lowHash, highHash *externalapi.DomainHash, limit uint32) (externalapi.BlockLocator, error) FindNextBlockLocatorBoundaries(blockLocator externalapi.BlockLocator) (lowHash, highHash *externalapi.DomainHash, err error) - IsBlockInHeaderPruningPointFuture(blockHash *externalapi.DomainHash) (bool, error) GetSyncInfo() (*externalapi.SyncInfo, error) } diff --git a/domain/consensus/model/testapi/test_consensus.go b/domain/consensus/model/testapi/test_consensus.go index 9800400ab..e361bff40 100644 --- a/domain/consensus/model/testapi/test_consensus.go +++ b/domain/consensus/model/testapi/test_consensus.go @@ -30,7 +30,7 @@ type TestConsensus interface { BlockStore() model.BlockStore ConsensusStateStore() model.ConsensusStateStore GHOSTDAGDataStore() model.GHOSTDAGDataStore - HeaderTipsStore() model.HeaderTipsStore + HeaderTipsStore() model.HeaderSelectedTipStore MultisetStore() model.MultisetStore PruningStore() model.PruningStore ReachabilityDataStore() model.ReachabilityDataStore @@ -46,7 +46,7 @@ type TestConsensus interface { DAGTraversalManager() model.DAGTraversalManager DifficultyManager() model.DifficultyManager GHOSTDAGManager() model.GHOSTDAGManager - HeaderTipsManager() model.HeaderTipsManager + HeaderTipsManager() model.HeadersSelectedTipManager MergeDepthManager() model.MergeDepthManager PastMedianTimeManager() model.PastMedianTimeManager PruningManager() model.PruningManager diff --git a/domain/consensus/processes/blockprocessor/blockprocessor.go b/domain/consensus/processes/blockprocessor/blockprocessor.go index e3e9ed460..926952c30 100644 --- a/domain/consensus/processes/blockprocessor/blockprocessor.go +++ b/domain/consensus/processes/blockprocessor/blockprocessor.go @@ -21,22 +21,22 @@ type blockProcessor struct { ghostdagManager model.GHOSTDAGManager pastMedianTimeManager model.PastMedianTimeManager coinbaseManager model.CoinbaseManager - headerTipsManager model.HeaderTipsManager + headerTipsManager model.HeadersSelectedTipManager syncManager model.SyncManager - acceptanceDataStore model.AcceptanceDataStore - blockStore model.BlockStore - blockStatusStore model.BlockStatusStore - blockRelationStore model.BlockRelationStore - multisetStore model.MultisetStore - ghostdagDataStore model.GHOSTDAGDataStore - consensusStateStore model.ConsensusStateStore - pruningStore model.PruningStore - reachabilityDataStore model.ReachabilityDataStore - utxoDiffStore model.UTXODiffStore - blockHeaderStore model.BlockHeaderStore - headerTipsStore model.HeaderTipsStore - finalityStore model.FinalityStore + acceptanceDataStore model.AcceptanceDataStore + blockStore model.BlockStore + blockStatusStore model.BlockStatusStore + blockRelationStore model.BlockRelationStore + multisetStore model.MultisetStore + ghostdagDataStore model.GHOSTDAGDataStore + consensusStateStore model.ConsensusStateStore + pruningStore model.PruningStore + reachabilityDataStore model.ReachabilityDataStore + utxoDiffStore model.UTXODiffStore + blockHeaderStore model.BlockHeaderStore + headersSelectedTipStore model.HeaderSelectedTipStore + finalityStore model.FinalityStore stores []model.Store } @@ -54,7 +54,7 @@ func New( pastMedianTimeManager model.PastMedianTimeManager, ghostdagManager model.GHOSTDAGManager, coinbaseManager model.CoinbaseManager, - headerTipsManager model.HeaderTipsManager, + headerTipsManager model.HeadersSelectedTipManager, syncManager model.SyncManager, acceptanceDataStore model.AcceptanceDataStore, @@ -68,7 +68,7 @@ func New( reachabilityDataStore model.ReachabilityDataStore, utxoDiffStore model.UTXODiffStore, blockHeaderStore model.BlockHeaderStore, - headerTipsStore model.HeaderTipsStore, + headersSelectedTipStore model.HeaderSelectedTipStore, finalityStore model.FinalityStore, ) model.BlockProcessor { @@ -86,20 +86,20 @@ func New( headerTipsManager: headerTipsManager, syncManager: syncManager, - consensusStateManager: consensusStateManager, - acceptanceDataStore: acceptanceDataStore, - blockStore: blockStore, - blockStatusStore: blockStatusStore, - blockRelationStore: blockRelationStore, - multisetStore: multisetStore, - ghostdagDataStore: ghostdagDataStore, - consensusStateStore: consensusStateStore, - pruningStore: pruningStore, - reachabilityDataStore: reachabilityDataStore, - utxoDiffStore: utxoDiffStore, - blockHeaderStore: blockHeaderStore, - headerTipsStore: headerTipsStore, - finalityStore: finalityStore, + consensusStateManager: consensusStateManager, + acceptanceDataStore: acceptanceDataStore, + blockStore: blockStore, + blockStatusStore: blockStatusStore, + blockRelationStore: blockRelationStore, + multisetStore: multisetStore, + ghostdagDataStore: ghostdagDataStore, + consensusStateStore: consensusStateStore, + pruningStore: pruningStore, + reachabilityDataStore: reachabilityDataStore, + utxoDiffStore: utxoDiffStore, + blockHeaderStore: blockHeaderStore, + headersSelectedTipStore: headersSelectedTipStore, + finalityStore: finalityStore, stores: []model.Store{ consensusStateStore, @@ -114,7 +114,7 @@ func New( reachabilityDataStore, utxoDiffStore, blockHeaderStore, - headerTipsStore, + headersSelectedTipStore, finalityStore, }, } @@ -128,3 +128,10 @@ func (bp *blockProcessor) ValidateAndInsertBlock(block *externalapi.DomainBlock) return bp.validateAndInsertBlock(block) } + +func (bp *blockProcessor) ValidateAndInsertPruningPoint(newPruningPoint *externalapi.DomainBlock, serializedUTXOSet []byte) error { + onEnd := logger.LogAndMeasureExecutionTime(log, "ValidateAndInsertPruningPoint") + defer onEnd() + + return bp.validateAndInsertPruningPoint(newPruningPoint, serializedUTXOSet) +} diff --git a/domain/consensus/processes/blockprocessor/validateandinsertblock.go b/domain/consensus/processes/blockprocessor/validateandinsertblock.go index 0cdff5ec7..5ab0aba0d 100644 --- a/domain/consensus/processes/blockprocessor/validateandinsertblock.go +++ b/domain/consensus/processes/blockprocessor/validateandinsertblock.go @@ -11,36 +11,16 @@ import ( "github.com/pkg/errors" ) -type insertMode uint8 - -const ( - insertModeGenesis insertMode = iota - insertModeHeader - insertModeBlockBody - insertModeBlock -) - func (bp *blockProcessor) validateAndInsertBlock(block *externalapi.DomainBlock) error { blockHash := consensushashing.HeaderHash(block.Header) - log.Debugf("Validating block %s", blockHash) - - insertMode, err := bp.validateAgainstSyncStateAndResolveInsertMode(block) - if err != nil { - return err - } - - err = bp.checkBlockStatus(blockHash, insertMode) - if err != nil { - return err - } - - err = bp.validateBlock(block, insertMode) + err := bp.validateBlock(block) if err != nil { bp.discardAllChanges() return err } - if insertMode == insertModeHeader { + isHeaderOnlyBlock := isHeaderOnlyBlock(block) + if isHeaderOnlyBlock { bp.blockStatusStore.Stage(blockHash, externalapi.StatusHeaderOnly) } else { bp.blockStatusStore.Stage(blockHash, externalapi.StatusUTXOPendingVerification) @@ -54,43 +34,38 @@ func (bp *blockProcessor) validateAndInsertBlock(block *externalapi.DomainBlock) } var oldHeadersSelectedTip *externalapi.DomainHash - if insertMode != insertModeGenesis { + isGenesis := *blockHash != *bp.genesisHash + if isGenesis { var err error - oldHeadersSelectedTip, err = bp.headerTipsManager.SelectedTip() + oldHeadersSelectedTip, err = bp.headersSelectedTipStore.HeadersSelectedTip(bp.databaseContext) if err != nil { return err } } - if insertMode == insertModeHeader { - err = bp.headerTipsManager.AddHeaderTip(blockHash) - if err != nil { - return err - } - } else if insertMode == insertModeBlock || insertMode == insertModeGenesis { + err = bp.headerTipsManager.AddHeaderTip(blockHash) + if err != nil { + return err + } + + if !isHeaderOnlyBlock { // Attempt to add the block to the virtual - err = bp.consensusStateManager.AddBlockToVirtual(blockHash) + err = bp.consensusStateManager.AddBlock(blockHash) if err != nil { return err } - - tips, err := bp.consensusStateStore.Tips(bp.databaseContext) - if err != nil { - return err - } - bp.headerTipsStore.Stage(tips) } - if insertMode != insertModeGenesis { + if isGenesis { err := bp.updateReachabilityReindexRoot(oldHeadersSelectedTip) if err != nil { return err } } - if insertMode == insertModeBlock { + if !isHeaderOnlyBlock { // Trigger pruning, which will check if the pruning point changed and delete the data if it did. - err = bp.pruningManager.FindNextPruningPoint() + err = bp.pruningManager.UpdatePruningPointByVirtual() if err != nil { return err } @@ -115,8 +90,8 @@ func (bp *blockProcessor) validateAndInsertBlock(block *externalapi.DomainBlock) logClosureErr = err return fmt.Sprintf("Failed to get sync info: %s", err) } - return fmt.Sprintf("New virtual's blue score: %d. Sync state: %s. Block count: %d. Header count: %d", - virtualGhostDAGData.BlueScore(), syncInfo.State, syncInfo.BlockCount, syncInfo.HeaderCount) + return fmt.Sprintf("New virtual's blue score: %d. Is awaiting UTXO set: %t. Block count: %d. Header count: %d", + virtualGhostDAGData.BlueScore(), syncInfo.IsAwaitingUTXOSet, syncInfo.BlockCount, syncInfo.HeaderCount) })) if logClosureErr != nil { return logClosureErr @@ -125,64 +100,12 @@ func (bp *blockProcessor) validateAndInsertBlock(block *externalapi.DomainBlock) return nil } -func (bp *blockProcessor) validateAgainstSyncStateAndResolveInsertMode(block *externalapi.DomainBlock) (insertMode, error) { - syncInfo, err := bp.syncManager.GetSyncInfo() - if err != nil { - return 0, err - } - syncState := syncInfo.State - - isHeaderOnlyBlock := isHeaderOnlyBlock(block) - blockHash := consensushashing.HeaderHash(block.Header) - if syncState == externalapi.SyncStateAwaitingGenesis { - if isHeaderOnlyBlock { - return 0, errors.Errorf("Got a header-only block while awaiting genesis") - } - if *blockHash != *bp.genesisHash { - return 0, errors.Errorf("Received a non-genesis block while awaiting genesis") - } - return insertModeGenesis, nil - } - - if isHeaderOnlyBlock { - return insertModeHeader, nil - } - - if syncState == externalapi.SyncStateAwaitingUTXOSet { - headerTipsPruningPoint, err := bp.consensusStateManager.HeaderTipsPruningPoint() - if err != nil { - return 0, err - } - if *blockHash != *headerTipsPruningPoint { - return 0, errors.Errorf("cannot insert blocks other than the header pruning point " + - "while awaiting the UTXO set") - } - return insertModeBlock, nil - } - - if syncState == externalapi.SyncStateAwaitingBlockBodies { - headerTips, err := bp.headerTipsStore.Tips(bp.databaseContext) - if err != nil { - return 0, err - } - selectedHeaderTip, err := bp.ghostdagManager.ChooseSelectedParent(headerTips...) - if err != nil { - return 0, err - } - if *selectedHeaderTip != *blockHash { - return insertModeBlockBody, nil - } - } - - return insertModeBlock, nil -} - func isHeaderOnlyBlock(block *externalapi.DomainBlock) bool { return len(block.Transactions) == 0 } func (bp *blockProcessor) updateReachabilityReindexRoot(oldHeadersSelectedTip *externalapi.DomainHash) error { - headersSelectedTip, err := bp.headerTipsManager.SelectedTip() + headersSelectedTip, err := bp.headersSelectedTipStore.HeadersSelectedTip(bp.databaseContext) if err != nil { return err } @@ -194,7 +117,9 @@ func (bp *blockProcessor) updateReachabilityReindexRoot(oldHeadersSelectedTip *e return bp.reachabilityManager.UpdateReindexRoot(headersSelectedTip) } -func (bp *blockProcessor) checkBlockStatus(hash *externalapi.DomainHash, mode insertMode) error { +func (bp *blockProcessor) checkBlockStatus(block *externalapi.DomainBlock) error { + hash := consensushashing.BlockHash(block) + isHeaderOnlyBlock := isHeaderOnlyBlock(block) exists, err := bp.blockStatusStore.Exists(bp.databaseContext, hash) if err != nil { return err @@ -212,12 +137,12 @@ func (bp *blockProcessor) checkBlockStatus(hash *externalapi.DomainHash, mode in return errors.Wrapf(ruleerrors.ErrKnownInvalid, "block %s is a known invalid block", hash) } - isBlockBodyAfterBlockHeader := mode != insertModeHeader && status == externalapi.StatusHeaderOnly + isBlockBodyAfterBlockHeader := !isHeaderOnlyBlock && status == externalapi.StatusHeaderOnly if !isBlockBodyAfterBlockHeader { return errors.Wrapf(ruleerrors.ErrDuplicateBlock, "block %s already exists", hash) } - isDuplicateHeader := mode == insertModeHeader && status == externalapi.StatusHeaderOnly + isDuplicateHeader := isHeaderOnlyBlock && status == externalapi.StatusHeaderOnly if isDuplicateHeader { return errors.Wrapf(ruleerrors.ErrDuplicateBlock, "block %s already exists", hash) } @@ -225,57 +150,16 @@ func (bp *blockProcessor) checkBlockStatus(hash *externalapi.DomainHash, mode in return nil } -func (bp *blockProcessor) validateBlock(block *externalapi.DomainBlock, mode insertMode) error { - blockHash := consensushashing.HeaderHash(block.Header) - hasHeader, err := bp.hasHeader(blockHash) - if err != nil { - return err - } - - if !hasHeader { - bp.blockHeaderStore.Stage(blockHash, block.Header) - } - - // If any validation until (included) proof-of-work fails, simply - // return an error without writing anything in the database. - // This is to prevent spamming attacks. - err = bp.validatePreProofOfWork(block) - if err != nil { - return err - } - - err = bp.validatePruningPointViolationAndProofOfWorkAndDifficulty(block, mode) - if err != nil { - return err - } - - // If in-context validations fail, discard all changes and store the - // block with StatusInvalid. - err = bp.validatePostProofOfWork(block, mode) - 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 - } - } - return err - } - return nil -} - func (bp *blockProcessor) validatePreProofOfWork(block *externalapi.DomainBlock) error { blockHash := consensushashing.BlockHash(block) - hasHeader, err := bp.hasHeader(blockHash) + hasValidatedOnlyHeader, err := bp.hasValidatedOnlyHeader(blockHash) if err != nil { return err } - if hasHeader { + if hasValidatedOnlyHeader { + log.Debugf("Block %s header was already validated, so skip the rest of validatePreProofOfWork", blockHash) return nil } @@ -286,41 +170,43 @@ func (bp *blockProcessor) validatePreProofOfWork(block *externalapi.DomainBlock) return nil } -func (bp *blockProcessor) validatePruningPointViolationAndProofOfWorkAndDifficulty(block *externalapi.DomainBlock, mode insertMode) error { - blockHash := consensushashing.HeaderHash(block.Header) - if mode != insertModeHeader { - // We stage the block here since we need it for parent validation - bp.blockStore.Stage(blockHash, block) - } - return bp.blockValidator.ValidatePruningPointViolationAndProofOfWorkAndDifficulty(blockHash) -} - -func (bp *blockProcessor) validatePostProofOfWork(block *externalapi.DomainBlock, mode insertMode) error { +func (bp *blockProcessor) validatePostProofOfWork(block *externalapi.DomainBlock) error { blockHash := consensushashing.BlockHash(block) - if mode != insertModeHeader { + isHeaderOnlyBlock := isHeaderOnlyBlock(block) + if !isHeaderOnlyBlock { + bp.blockStore.Stage(blockHash, block) err := bp.blockValidator.ValidateBodyInIsolation(blockHash) if err != nil { return err } } - hasHeader, err := bp.hasHeader(blockHash) + hasValidatedHeader, err := bp.hasValidatedOnlyHeader(blockHash) if err != nil { return err } - if !hasHeader { + if !hasValidatedHeader { err = bp.blockValidator.ValidateHeaderInContext(blockHash) if err != nil { return err } } + if !isHeaderOnlyBlock { + err = bp.blockValidator.ValidateBodyInContext(blockHash) + if err != nil { + return err + } + } else { + log.Tracef("Skipping ValidateBodyInContext for block %s because it's header only", blockHash) + } + return nil } -func (bp *blockProcessor) hasHeader(blockHash *externalapi.DomainHash) (bool, error) { +func (bp *blockProcessor) hasValidatedOnlyHeader(blockHash *externalapi.DomainHash) (bool, error) { exists, err := bp.blockStatusStore.Exists(bp.databaseContext, blockHash) if err != nil { return false, err diff --git a/domain/consensus/processes/blockprocessor/validateandinsertpruningpoint.go b/domain/consensus/processes/blockprocessor/validateandinsertpruningpoint.go new file mode 100644 index 000000000..f900bd96e --- /dev/null +++ b/domain/consensus/processes/blockprocessor/validateandinsertpruningpoint.go @@ -0,0 +1,38 @@ +package blockprocessor + +import ( + "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" + "github.com/kaspanet/kaspad/domain/consensus/ruleerrors" + "github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing" + "github.com/pkg/errors" +) + +func (bp *blockProcessor) validateAndInsertPruningPoint(newPruningPoint *externalapi.DomainBlock, serializedUTXOSet []byte) error { + log.Info("Checking that the given pruning point is the expected pruning point") + + expectedNewPruningPointHash, err := bp.pruningManager.CalculatePruningPointByHeaderSelectedTip() + if err != nil { + return err + } + + newPruningPointHash := consensushashing.BlockHash(newPruningPoint) + + if *expectedNewPruningPointHash != *newPruningPointHash { + return errors.Wrapf(ruleerrors.ErrUnexpectedPruningPoint, "expected pruning point %s but got %s", + expectedNewPruningPointHash, newPruningPointHash) + } + + // 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) + if err != nil { + return err + } + + err = bp.consensusStateManager.UpdatePruningPoint(newPruningPoint, serializedUTXOSet) + if err != nil { + return err + } + + return bp.ValidateAndInsertBlock(newPruningPoint) +} diff --git a/domain/consensus/processes/blockprocessor/validateblock.go b/domain/consensus/processes/blockprocessor/validateblock.go new file mode 100644 index 000000000..f7bba7966 --- /dev/null +++ b/domain/consensus/processes/blockprocessor/validateblock.go @@ -0,0 +1,67 @@ +package blockprocessor + +import ( + "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" + "github.com/kaspanet/kaspad/domain/consensus/ruleerrors" + "github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing" + "github.com/pkg/errors" +) + +func (bp *blockProcessor) validateBlockAndDiscardChanges(block *externalapi.DomainBlock) error { + defer bp.discardAllChanges() + return bp.validateBlock(block) +} + +func (bp *blockProcessor) validateBlock(block *externalapi.DomainBlock) error { + blockHash := consensushashing.HeaderHash(block.Header) + log.Debugf("Validating block %s", blockHash) + + err := bp.checkBlockStatus(block) + if err != nil { + return err + } + + hasValidatedHeader, err := bp.hasValidatedOnlyHeader(blockHash) + if err != nil { + return err + } + + if !hasValidatedHeader { + log.Tracef("Staging block %s header", blockHash) + bp.blockHeaderStore.Stage(blockHash, block.Header) + } else { + log.Tracef("Block %s header is already known, so no need to stage it", blockHash) + } + + // If any validation until (included) proof-of-work fails, simply + // return an error without writing anything in the database. + // This is to prevent spamming attacks. + err = bp.validatePreProofOfWork(block) + if err != nil { + return err + } + + if !hasValidatedHeader { + err = bp.blockValidator.ValidatePruningPointViolationAndProofOfWorkAndDifficulty(blockHash) + if err != nil { + return err + } + } + + // If in-context validations fail, discard all changes and store the + // block with StatusInvalid. + err = bp.validatePostProofOfWork(block) + 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 + } + } + return err + } + return nil +} diff --git a/domain/consensus/processes/blockvalidator/block_body_in_context.go b/domain/consensus/processes/blockvalidator/block_body_in_context.go index 6e577a6ce..dd6fc4540 100644 --- a/domain/consensus/processes/blockvalidator/block_body_in_context.go +++ b/domain/consensus/processes/blockvalidator/block_body_in_context.go @@ -1,6 +1,7 @@ package blockvalidator import ( + "github.com/kaspanet/kaspad/infrastructure/logger" "math" "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" @@ -13,6 +14,9 @@ import ( // ValidateBodyInContext validates block bodies in the context of the current // consensus state func (v *blockValidator) ValidateBodyInContext(blockHash *externalapi.DomainHash) error { + onEnd := logger.LogAndMeasureExecutionTime(log, "ValidateBodyInContext") + defer onEnd() + return v.checkBlockTransactionsFinalized(blockHash) } diff --git a/domain/consensus/processes/blockvalidator/block_body_in_isolation.go b/domain/consensus/processes/blockvalidator/block_body_in_isolation.go index 0e74e5f4a..5b9367cf1 100644 --- a/domain/consensus/processes/blockvalidator/block_body_in_isolation.go +++ b/domain/consensus/processes/blockvalidator/block_body_in_isolation.go @@ -8,12 +8,16 @@ import ( "github.com/kaspanet/kaspad/domain/consensus/utils/merkle" "github.com/kaspanet/kaspad/domain/consensus/utils/subnetworks" "github.com/kaspanet/kaspad/domain/consensus/utils/transactionhelper" + "github.com/kaspanet/kaspad/infrastructure/logger" "github.com/pkg/errors" ) // ValidateBodyInIsolation validates block bodies in isolation from the current // consensus state func (v *blockValidator) ValidateBodyInIsolation(blockHash *externalapi.DomainHash) error { + onEnd := logger.LogAndMeasureExecutionTime(log, "ValidateBodyInContext") + defer onEnd() + block, err := v.blockStore.Block(v.databaseContext, blockHash) if err != nil { return err diff --git a/domain/consensus/processes/blockvalidator/block_header_in_context.go b/domain/consensus/processes/blockvalidator/block_header_in_context.go index 0d2f3fdfc..19f92a2a1 100644 --- a/domain/consensus/processes/blockvalidator/block_header_in_context.go +++ b/domain/consensus/processes/blockvalidator/block_header_in_context.go @@ -4,23 +4,27 @@ import ( "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" "github.com/kaspanet/kaspad/domain/consensus/ruleerrors" "github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing" + "github.com/kaspanet/kaspad/infrastructure/logger" "github.com/pkg/errors" ) // ValidateHeaderInContext validates block headers in the context of the current // consensus state func (v *blockValidator) ValidateHeaderInContext(blockHash *externalapi.DomainHash) error { + onEnd := logger.LogAndMeasureExecutionTime(log, "ValidateHeaderInContext") + defer onEnd() + header, err := v.blockHeaderStore.BlockHeader(v.databaseContext, blockHash) if err != nil { return err } - isHeadersOnlyBlock, err := v.isHeadersOnlyBlock(blockHash) + hasValidatedHeader, err := v.hasValidatedHeader(blockHash) if err != nil { return err } - if !isHeadersOnlyBlock { + if !hasValidatedHeader { err = v.ghostdagManager.GHOSTDAG(blockHash) if err != nil { return err @@ -60,7 +64,7 @@ func (v *blockValidator) ValidateHeaderInContext(blockHash *externalapi.DomainHa return nil } -func (v *blockValidator) isHeadersOnlyBlock(blockHash *externalapi.DomainHash) (bool, error) { +func (v *blockValidator) hasValidatedHeader(blockHash *externalapi.DomainHash) (bool, error) { exists, err := v.blockStatusStore.Exists(v.databaseContext, blockHash) if err != nil { return false, err diff --git a/domain/consensus/processes/blockvalidator/block_header_in_isolation.go b/domain/consensus/processes/blockvalidator/block_header_in_isolation.go index 8fe275a82..01622825d 100644 --- a/domain/consensus/processes/blockvalidator/block_header_in_isolation.go +++ b/domain/consensus/processes/blockvalidator/block_header_in_isolation.go @@ -4,6 +4,7 @@ import ( "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" "github.com/kaspanet/kaspad/domain/consensus/ruleerrors" "github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing" + "github.com/kaspanet/kaspad/infrastructure/logger" "github.com/kaspanet/kaspad/util/mstime" "github.com/pkg/errors" ) @@ -11,6 +12,9 @@ import ( // ValidateHeaderInIsolation validates block headers in isolation from the current // consensus state func (v *blockValidator) ValidateHeaderInIsolation(blockHash *externalapi.DomainHash) error { + onEnd := logger.LogAndMeasureExecutionTime(log, "ValidateHeaderInIsolation") + defer onEnd() + header, err := v.blockHeaderStore.BlockHeader(v.databaseContext, blockHash) if err != nil { return err diff --git a/domain/consensus/processes/blockvalidator/log.go b/domain/consensus/processes/blockvalidator/log.go new file mode 100644 index 000000000..a2c8cf5d3 --- /dev/null +++ b/domain/consensus/processes/blockvalidator/log.go @@ -0,0 +1,7 @@ +package blockvalidator + +import ( + "github.com/kaspanet/kaspad/infrastructure/logger" +) + +var log, _ = logger.Get(logger.SubsystemTags.BLVL) diff --git a/domain/consensus/processes/blockvalidator/proof_of_work.go b/domain/consensus/processes/blockvalidator/proof_of_work.go index 96426f244..b83076c79 100644 --- a/domain/consensus/processes/blockvalidator/proof_of_work.go +++ b/domain/consensus/processes/blockvalidator/proof_of_work.go @@ -5,11 +5,15 @@ import ( "github.com/kaspanet/kaspad/domain/consensus/model/pow" "github.com/kaspanet/kaspad/domain/consensus/ruleerrors" "github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing" + "github.com/kaspanet/kaspad/infrastructure/logger" "github.com/kaspanet/kaspad/util" "github.com/pkg/errors" ) func (v *blockValidator) ValidatePruningPointViolationAndProofOfWorkAndDifficulty(blockHash *externalapi.DomainHash) error { + onEnd := logger.LogAndMeasureExecutionTime(log, "ValidatePruningPointViolationAndProofOfWorkAndDifficulty") + defer onEnd() + header, err := v.blockHeaderStore.BlockHeader(v.databaseContext, blockHash) if err != nil { return err @@ -102,7 +106,7 @@ func (v *blockValidator) checkProofOfWork(header *externalapi.DomainBlockHeader) func (v *blockValidator) checkParentsExist(blockHash *externalapi.DomainHash, header *externalapi.DomainBlockHeader) error { missingParentHashes := []*externalapi.DomainHash{} - isFullBlock, err := v.blockStore.HasBlock(v.databaseContext, blockHash) + hasBlockBody, err := v.blockStore.HasBlock(v.databaseContext, blockHash) if err != nil { return err } @@ -117,12 +121,31 @@ func (v *blockValidator) checkParentsExist(blockHash *externalapi.DomainHash, he continue } - if isFullBlock { - parentStatus, err := v.blockStatusStore.Get(v.databaseContext, parent) - if err != nil { - return err - } + parentStatus, err := v.blockStatusStore.Get(v.databaseContext, parent) + if err != nil { + return err + } + + 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) } } diff --git a/domain/consensus/processes/consensusstatemanager/add_block_to_virtual.go b/domain/consensus/processes/consensusstatemanager/add_block_to_virtual.go index 11b5d63c0..4fdf3773f 100644 --- a/domain/consensus/processes/consensusstatemanager/add_block_to_virtual.go +++ b/domain/consensus/processes/consensusstatemanager/add_block_to_virtual.go @@ -8,41 +8,41 @@ import ( // AddBlockToVirtual 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) AddBlockToVirtual(blockHash *externalapi.DomainHash) error { - log.Tracef("AddBlockToVirtual start for block %s", blockHash) - defer log.Tracef("AddBlockToVirtual end for block %s", blockHash) +func (csm *consensusStateManager) AddBlock(blockHash *externalapi.DomainHash) error { + log.Tracef("AddBlock start for block %s", blockHash) + defer log.Tracef("AddBlock end for block %s", blockHash) log.Tracef("Resolving whether the block %s is the next virtual selected parent", blockHash) - isNextVirtualSelectedParent, err := csm.isNextVirtualSelectedParent(blockHash) + isCandidateToBeNextVirtualSelectedParent, err := csm.isCandidateToBeNextVirtualSelectedParent(blockHash) if err != nil { return err } - if isNextVirtualSelectedParent { - log.Tracef("Block %s is the new virtual. Resolving its block status", blockHash) - blockStatus, err := csm.resolveBlockStatus(blockHash) + 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 + // eventually try to fetch UTXO diffs from the past of the pruning point. + log.Tracef("Block %s is candidate to be the next virtual selected parent. Resolving whether it violates "+ + "finality", blockHash) + isViolatingFinality, shouldNotify, err := csm.isViolatingFinality(blockHash) if err != nil { return err } - if blockStatus == externalapi.StatusValid { - log.Tracef("Block %s is tentatively valid. Resolving whether it violates finality", blockHash) - err = csm.checkFinalityViolation(blockHash) - if err != nil { - return err - } - - // Re-fetch the block status for logging purposes - // because it could've been changed in - // checkFinalityViolation - blockStatus, err = csm.blockStatusStore.Get(csm.databaseContext, blockHash) - if err != nil { - return err - } + if shouldNotify { + //TODO: Send finality conflict notification + log.Warnf("Finality Violation Detected! Block %s violates finality!", blockHash) } - log.Debugf("Block %s is the next virtual selected parent. "+ - "Its resolved status is `%s`", blockHash, blockStatus) + if !isViolatingFinality { + log.Tracef("Block %s doesn't violate finality. Resolving its block status", blockHash) + blockStatus, err := csm.resolveBlockStatus(blockHash) + if err != nil { + return err + } + + log.Debugf("Block %s resolved to status `%s`", blockHash, blockStatus) + } } else { log.Debugf("Block %s is not the next virtual selected parent, "+ "therefore its status remains `%s`", blockHash, externalapi.StatusUTXOPendingVerification) @@ -64,9 +64,9 @@ func (csm *consensusStateManager) AddBlockToVirtual(blockHash *externalapi.Domai return nil } -func (csm *consensusStateManager) isNextVirtualSelectedParent(blockHash *externalapi.DomainHash) (bool, error) { - log.Tracef("isNextVirtualSelectedParent start for block %s", blockHash) - defer log.Tracef("isNextVirtualSelectedParent end for block %s", blockHash) +func (csm *consensusStateManager) isCandidateToBeNextVirtualSelectedParent(blockHash *externalapi.DomainHash) (bool, error) { + log.Tracef("isCandidateToBeNextVirtualSelectedParent start for block %s", blockHash) + defer log.Tracef("isCandidateToBeNextVirtualSelectedParent end for block %s", blockHash) if *blockHash == *csm.genesisHash { log.Tracef("Block %s is the genesis block, therefore it is "+ diff --git a/domain/consensus/processes/consensusstatemanager/check_finality_violation.go b/domain/consensus/processes/consensusstatemanager/check_finality_violation.go index c8c9e022e..9774276fe 100644 --- a/domain/consensus/processes/consensusstatemanager/check_finality_violation.go +++ b/domain/consensus/processes/consensusstatemanager/check_finality_violation.go @@ -2,23 +2,65 @@ package consensusstatemanager import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" -func (csm *consensusStateManager) checkFinalityViolation( - blockHash *externalapi.DomainHash) error { +func (csm *consensusStateManager) isViolatingFinality(blockHash *externalapi.DomainHash) (isViolatingFinality bool, + shouldSendNotification bool, err error) { - log.Tracef("checkFinalityViolation start for block %s", blockHash) - defer log.Tracef("checkFinalityViolation end for block %s", blockHash) + log.Tracef("isViolatingFinality start for block %s", blockHash) + defer log.Tracef("isViolatingFinality end for block %s", blockHash) - isViolatingFinality, err := csm.finalityManager.IsViolatingFinality(blockHash) - if err != nil { - return err + if *blockHash == *csm.genesisHash { + log.Tracef("Block %s is the genesis block, "+ + "and does not violate finality by definition", blockHash) + return false, false, nil } - if isViolatingFinality { - csm.blockStatusStore.Stage(blockHash, externalapi.StatusUTXOPendingVerification) - log.Warnf("Finality Violation Detected! Block %s violates finality!", blockHash) - return nil + var finalityPoint *externalapi.DomainHash + virtualFinalityPoint, err := csm.finalityManager.VirtualFinalityPoint() + if err != nil { + return false, false, err + } + log.Tracef("The virtual finality point is: %s", virtualFinalityPoint) + + // There can be a situation where the virtual points close to the pruning point (or even in the past + // of the pruning point before calling validateAndInsertBlock for the pruning point block) and the + // finality point from the virtual point-of-view is in the past of the pruning point. + // In such situation we override the finality point to be the pruning point to avoid situations where + // the virtual selected parent chain don't include the pruning point. + pruningPoint, err := csm.pruningStore.PruningPoint(csm.databaseContext) + if err != nil { + return false, false, err + } + log.Tracef("The pruning point is: %s", pruningPoint) + + isFinalityPointInPastOfPruningPoint, err := csm.dagTopologyManager.IsAncestorOf(virtualFinalityPoint, pruningPoint) + if err != nil { + return false, false, err + } + + if !isFinalityPointInPastOfPruningPoint { + finalityPoint = virtualFinalityPoint + } else { + log.Tracef("The virtual finality point is %s in the past of the pruning point, so finality is validated "+ + "using the pruning point", virtualFinalityPoint) + finalityPoint = pruningPoint + } + + isInSelectedParentChainOfFinalityPoint, err := csm.dagTopologyManager.IsInSelectedParentChainOf(finalityPoint, + blockHash) + if err != nil { + return false, false, err + } + + if !isInSelectedParentChainOfFinalityPoint { + if !isFinalityPointInPastOfPruningPoint { + return true, true, nil + } + // On IBD it's pretty normal to get blocks in the anticone of the pruning + // point, so we don't notify on cases when the pruning point is in the future + // of the finality point. + return true, false, nil } log.Tracef("Block %s does not violate finality", blockHash) - return nil + return false, false, nil } diff --git a/domain/consensus/processes/consensusstatemanager/consensus_state_manager.go b/domain/consensus/processes/consensusstatemanager/consensus_state_manager.go index c0c8ab738..fddbe7472 100644 --- a/domain/consensus/processes/consensusstatemanager/consensus_state_manager.go +++ b/domain/consensus/processes/consensusstatemanager/consensus_state_manager.go @@ -25,16 +25,17 @@ type consensusStateManager struct { mergeDepthManager model.MergeDepthManager finalityManager model.FinalityManager - headerTipsStore model.HeaderTipsStore - blockStatusStore model.BlockStatusStore - ghostdagDataStore model.GHOSTDAGDataStore - consensusStateStore model.ConsensusStateStore - multisetStore model.MultisetStore - blockStore model.BlockStore - utxoDiffStore model.UTXODiffStore - blockRelationStore model.BlockRelationStore - acceptanceDataStore model.AcceptanceDataStore - blockHeaderStore model.BlockHeaderStore + headersSelectedTipStore model.HeaderSelectedTipStore + blockStatusStore model.BlockStatusStore + ghostdagDataStore model.GHOSTDAGDataStore + consensusStateStore model.ConsensusStateStore + multisetStore model.MultisetStore + blockStore model.BlockStore + utxoDiffStore model.UTXODiffStore + blockRelationStore model.BlockRelationStore + acceptanceDataStore model.AcceptanceDataStore + blockHeaderStore model.BlockHeaderStore + pruningStore model.PruningStore stores []model.Store } @@ -68,7 +69,8 @@ func New( blockRelationStore model.BlockRelationStore, acceptanceDataStore model.AcceptanceDataStore, blockHeaderStore model.BlockHeaderStore, - headerTipsStore model.HeaderTipsStore) (model.ConsensusStateManager, error) { + headersSelectedTipStore model.HeaderSelectedTipStore, + pruningStore model.PruningStore) (model.ConsensusStateManager, error) { csm := &consensusStateManager{ pruningDepth: pruningDepth, @@ -89,16 +91,17 @@ func New( mergeDepthManager: mergeDepthManager, finalityManager: finalityManager, - multisetStore: multisetStore, - blockStore: blockStore, - blockStatusStore: blockStatusStore, - ghostdagDataStore: ghostdagDataStore, - consensusStateStore: consensusStateStore, - utxoDiffStore: utxoDiffStore, - blockRelationStore: blockRelationStore, - acceptanceDataStore: acceptanceDataStore, - blockHeaderStore: blockHeaderStore, - headerTipsStore: headerTipsStore, + multisetStore: multisetStore, + blockStore: blockStore, + blockStatusStore: blockStatusStore, + ghostdagDataStore: ghostdagDataStore, + consensusStateStore: consensusStateStore, + utxoDiffStore: utxoDiffStore, + blockRelationStore: blockRelationStore, + acceptanceDataStore: acceptanceDataStore, + blockHeaderStore: blockHeaderStore, + headersSelectedTipStore: headersSelectedTipStore, + pruningStore: pruningStore, stores: []model.Store{ consensusStateStore, @@ -111,7 +114,8 @@ func New( consensusStateStore, utxoDiffStore, blockHeaderStore, - headerTipsStore, + headersSelectedTipStore, + pruningStore, }, } diff --git a/domain/consensus/processes/consensusstatemanager/set_pruning_utxo_set.go b/domain/consensus/processes/consensusstatemanager/update_pruning_utxo_set.go similarity index 53% rename from domain/consensus/processes/consensusstatemanager/set_pruning_utxo_set.go rename to domain/consensus/processes/consensusstatemanager/update_pruning_utxo_set.go index fd211c9b2..0365b661e 100644 --- a/domain/consensus/processes/consensusstatemanager/set_pruning_utxo_set.go +++ b/domain/consensus/processes/consensusstatemanager/update_pruning_utxo_set.go @@ -5,6 +5,7 @@ import ( "github.com/kaspanet/kaspad/domain/consensus/model" "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" "github.com/kaspanet/kaspad/domain/consensus/ruleerrors" + "github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing" "github.com/kaspanet/kaspad/domain/consensus/utils/serialization" "github.com/kaspanet/kaspad/domain/consensus/utils/utxo" "github.com/kaspanet/kaspad/domain/consensus/utils/utxoserialization" @@ -12,18 +13,11 @@ import ( "github.com/pkg/errors" ) -var virtualHeaderHash = &externalapi.DomainHash{ - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, -} - -func (csm *consensusStateManager) SetPruningPointUTXOSet(serializedUTXOSet []byte) error { - onEnd := logger.LogAndMeasureExecutionTime(log, "SetPruningPointUTXOSet") +func (csm *consensusStateManager) UpdatePruningPoint(newPruningPoint *externalapi.DomainBlock, serializedUTXOSet []byte) error { + onEnd := logger.LogAndMeasureExecutionTime(log, "UpdatePruningPoint") defer onEnd() - err := csm.setPruningPointUTXOSet(serializedUTXOSet) + err := csm.updatePruningPoint(newPruningPoint, serializedUTXOSet) if err != nil { csm.discardSetPruningPointUTXOSetChanges() return err @@ -32,15 +26,23 @@ func (csm *consensusStateManager) SetPruningPointUTXOSet(serializedUTXOSet []byt return csm.commitSetPruningPointUTXOSetAll() } -func (csm *consensusStateManager) setPruningPointUTXOSet(serializedUTXOSet []byte) error { - log.Tracef("setPruningPointUTXOSet start") - defer log.Tracef("setPruningPointUTXOSet end") +func (csm *consensusStateManager) updatePruningPoint(newPruningPoint *externalapi.DomainBlock, serializedUTXOSet []byte) error { + log.Tracef("updatePruningPoint start") + defer log.Tracef("updatePruningPoint end") - headerTipsPruningPoint, err := csm.HeaderTipsPruningPoint() + newPruningPointHash := consensushashing.BlockHash(newPruningPoint) + + // We ignore the shouldSendNotification return value because we always want to send finality conflict notification + // in case the new pruning point violates finality + isViolatingFinality, _, err := csm.isViolatingFinality(newPruningPointHash) if err != nil { return err } - log.Tracef("The pruning point of the header tips is: %s", headerTipsPruningPoint) + + if isViolatingFinality { + log.Warnf("Finality Violation Detected! The suggest pruning point %s violates finality!", newPruningPointHash) + return nil + } protoUTXOSet := &utxoserialization.ProtoUTXOSet{} err = proto.Unmarshal(serializedUTXOSet, protoUTXOSet) @@ -54,24 +56,24 @@ func (csm *consensusStateManager) setPruningPointUTXOSet(serializedUTXOSet []byt } log.Tracef("Calculated multiset for given UTXO set: %s", utxoSetMultiSet.Hash()) - headerTipsPruningPointHeader, err := csm.blockHeaderStore.BlockHeader(csm.databaseContext, headerTipsPruningPoint) + newPruningPointHeader, err := csm.blockHeaderStore.BlockHeader(csm.databaseContext, newPruningPointHash) if err != nil { return err } - log.Tracef("The multiset in the header of the header tip pruning point: %s", - headerTipsPruningPointHeader.UTXOCommitment) + log.Tracef("The UTXO commitment of the pruning point: %s", + newPruningPointHeader.UTXOCommitment) - if headerTipsPruningPointHeader.UTXOCommitment != *utxoSetMultiSet.Hash() { + 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", headerTipsPruningPointHeader.UTXOCommitment, *utxoSetMultiSet.Hash()) + "point UTXO set is %s but got %s", newPruningPointHeader.UTXOCommitment, *utxoSetMultiSet.Hash()) } - log.Tracef("Header tip pruning point multiset validation passed") + log.Tracef("The new pruning point UTXO commitment validation passed") - log.Tracef("Staging the parent hashes for the header tips pruning point as the DAG tips") - csm.consensusStateStore.StageTips(headerTipsPruningPointHeader.ParentHashes) + log.Tracef("Staging the parent hashes for pruning point as the DAG tips") + csm.consensusStateStore.StageTips(newPruningPointHeader.ParentHashes) log.Tracef("Setting the parent hashes for the header tips pruning point as the virtual parents") - err = csm.dagTopologyManager.SetParents(model.VirtualBlockHash, headerTipsPruningPointHeader.ParentHashes) + err = csm.dagTopologyManager.SetParents(model.VirtualBlockHash, newPruningPointHeader.ParentHashes) if err != nil { return err } @@ -82,6 +84,13 @@ func (csm *consensusStateManager) setPruningPointUTXOSet(serializedUTXOSet []byt 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()) + if err != nil { + return err + } + err = csm.ghostdagManager.GHOSTDAG(model.VirtualBlockHash) if err != nil { return err @@ -93,8 +102,11 @@ func (csm *consensusStateManager) setPruningPointUTXOSet(serializedUTXOSet []byt return err } - log.Tracef("Staging the status of the header tips pruning point as %s", externalapi.StatusValid) - csm.blockStatusStore.Stage(headerTipsPruningPoint, externalapi.StatusValid) + log.Tracef("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) return nil } @@ -145,34 +157,3 @@ func (p protoUTXOSetIterator) Get() (outpoint *externalapi.DomainOutpoint, utxoE func protoUTXOSetToReadOnlyUTXOSetIterator(protoUTXOSet *utxoserialization.ProtoUTXOSet) model.ReadOnlyUTXOSetIterator { return &protoUTXOSetIterator{utxoSet: protoUTXOSet} } - -func (csm *consensusStateManager) HeaderTipsPruningPoint() (*externalapi.DomainHash, error) { - log.Tracef("HeaderTipsPruningPoint start") - defer log.Tracef("HeaderTipsPruningPoint end") - - headerTips, err := csm.headerTipsStore.Tips(csm.databaseContext) - if err != nil { - return nil, err - } - log.Tracef("The current header tips are: %s", headerTips) - - log.Tracef("Temporarily staging the parents of the virtual header to be the header tips: %s", headerTips) - csm.blockRelationStore.StageBlockRelation(virtualHeaderHash, &model.BlockRelations{ - Parents: headerTips, - }) - - defer csm.blockRelationStore.Discard() - - err = csm.ghostdagManager.GHOSTDAG(virtualHeaderHash) - if err != nil { - return nil, err - } - defer csm.ghostdagDataStore.Discard() - - pruningPoint, err := csm.dagTraversalManager.BlockAtDepth(virtualHeaderHash, csm.pruningDepth) - if err != nil { - return nil, err - } - log.Tracef("The block at depth %d from %s is: %s", csm.pruningDepth, virtualHeaderHash, pruningPoint) - return pruningPoint, nil -} diff --git a/domain/consensus/processes/consensusstatemanager/verify_and_build_utxo.go b/domain/consensus/processes/consensusstatemanager/verify_and_build_utxo.go index 8a8853b3b..f28ab7ba2 100644 --- a/domain/consensus/processes/consensusstatemanager/verify_and_build_utxo.go +++ b/domain/consensus/processes/consensusstatemanager/verify_and_build_utxo.go @@ -47,7 +47,7 @@ func (csm *consensusStateManager) verifyUTXO(block *externalapi.DomainBlock, blo log.Tracef("Coinbase transaction validation passed for block %s", blockHash) log.Tracef("Validating transactions against past UTXO for block %s", blockHash) - err = csm.validateBlockTransactionsAgainstPastUTXO(block, blockHash, pastUTXODiff) + err = csm.validateBlockTransactionsAgainstPastUTXO(block, pastUTXODiff) if err != nil { return err } @@ -57,8 +57,9 @@ func (csm *consensusStateManager) verifyUTXO(block *externalapi.DomainBlock, blo } func (csm *consensusStateManager) validateBlockTransactionsAgainstPastUTXO(block *externalapi.DomainBlock, - blockHash *externalapi.DomainHash, pastUTXODiff model.UTXODiff) error { + pastUTXODiff model.UTXODiff) error { + blockHash := consensushashing.BlockHash(block) log.Tracef("validateBlockTransactionsAgainstPastUTXO start for block %s", blockHash) defer log.Tracef("validateBlockTransactionsAgainstPastUTXO end for block %s", blockHash) diff --git a/domain/consensus/processes/dagtraversalmanager/dagtraversalmanager.go b/domain/consensus/processes/dagtraversalmanager/dagtraversalmanager.go index 5a0329fe3..762580ee4 100644 --- a/domain/consensus/processes/dagtraversalmanager/dagtraversalmanager.go +++ b/domain/consensus/processes/dagtraversalmanager/dagtraversalmanager.go @@ -2,9 +2,9 @@ package dagtraversalmanager import ( "fmt" - "github.com/kaspanet/kaspad/domain/consensus/model" "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" + "github.com/pkg/errors" ) // dagTraversalManager exposes methods for travering blocks @@ -100,6 +100,11 @@ func (dtm *dagTraversalManager) LowestChainBlockAboveOrEqualToBlueScore(highHash return nil, err } + if highBlockGHOSTDAGData.BlueScore() < blueScore { + return nil, errors.Errorf("the given blue score %d is higher than block %s blue score of %d", + blueScore, highHash, highBlockGHOSTDAGData.BlueScore()) + } + currentHash := highHash currentBlockGHOSTDAGData := highBlockGHOSTDAGData iterator := dtm.SelectedParentIterator(highHash) @@ -112,7 +117,7 @@ func (dtm *dagTraversalManager) LowestChainBlockAboveOrEqualToBlueScore(highHash if selectedParentBlockGHOSTDAGData.BlueScore() < blueScore { break } - currentHash = selectedParentBlockGHOSTDAGData.SelectedParent() + currentHash = currentBlockGHOSTDAGData.SelectedParent() currentBlockGHOSTDAGData = selectedParentBlockGHOSTDAGData } diff --git a/domain/consensus/processes/finalitymanager/finality_manager.go b/domain/consensus/processes/finalitymanager/finality_manager.go index 7fd8cf30e..b5fb1ba29 100644 --- a/domain/consensus/processes/finalitymanager/finality_manager.go +++ b/domain/consensus/processes/finalitymanager/finality_manager.go @@ -35,31 +35,6 @@ func New(databaseContext model.DBReader, } } -func (fm *finalityManager) IsViolatingFinality(blockHash *externalapi.DomainHash) (bool, error) { - if *blockHash == *fm.genesisHash { - log.Tracef("Block %s is the genesis block, "+ - "and does not violate finality by definition", blockHash) - return false, nil - } - log.Tracef("isViolatingFinality start for block %s", blockHash) - defer log.Tracef("isViolatingFinality end for block %s", blockHash) - - virtualFinalityPoint, err := fm.VirtualFinalityPoint() - if err != nil { - return false, err - } - log.Tracef("The virtual finality point is: %s", virtualFinalityPoint) - - isInSelectedParentChain, err := fm.dagTopologyManager.IsInSelectedParentChainOf(virtualFinalityPoint, blockHash) - if err != nil { - return false, err - } - log.Tracef("Is the virtual finality point %s "+ - "in the selected parent chain of %s: %t", virtualFinalityPoint, blockHash, isInSelectedParentChain) - - return !isInSelectedParentChain, nil -} - func (fm *finalityManager) VirtualFinalityPoint() (*externalapi.DomainHash, error) { log.Tracef("virtualFinalityPoint start") defer log.Tracef("virtualFinalityPoint end") diff --git a/domain/consensus/processes/headersselectedtipmanager/headertipsmanager.go b/domain/consensus/processes/headersselectedtipmanager/headertipsmanager.go new file mode 100644 index 000000000..4ff750563 --- /dev/null +++ b/domain/consensus/processes/headersselectedtipmanager/headertipsmanager.go @@ -0,0 +1,55 @@ +package headersselectedtipmanager + +import ( + "github.com/kaspanet/kaspad/domain/consensus/model" + "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" +) + +type headerTipsManager struct { + databaseContext model.DBReader + + dagTopologyManager model.DAGTopologyManager + ghostdagManager model.GHOSTDAGManager + headersSelectedTipStore model.HeaderSelectedTipStore +} + +// New instantiates a new HeadersSelectedTipManager +func New(databaseContext model.DBReader, + dagTopologyManager model.DAGTopologyManager, + ghostdagManager model.GHOSTDAGManager, + headersSelectedTipStore model.HeaderSelectedTipStore) model.HeadersSelectedTipManager { + + return &headerTipsManager{ + databaseContext: databaseContext, + dagTopologyManager: dagTopologyManager, + ghostdagManager: ghostdagManager, + headersSelectedTipStore: headersSelectedTipStore, + } +} + +func (h *headerTipsManager) AddHeaderTip(hash *externalapi.DomainHash) error { + hasSelectedTip, err := h.headersSelectedTipStore.Has(h.databaseContext) + if err != nil { + return err + } + + if !hasSelectedTip { + h.headersSelectedTipStore.Stage(hash) + } else { + headersSelectedTip, err := h.headersSelectedTipStore.HeadersSelectedTip(h.databaseContext) + if err != nil { + return err + } + + newHeadersSelectedTip, err := h.ghostdagManager.ChooseSelectedParent(headersSelectedTip, hash) + if err != nil { + return err + } + + if *newHeadersSelectedTip != *headersSelectedTip { + h.headersSelectedTipStore.Stage(newHeadersSelectedTip) + } + } + + return nil +} diff --git a/domain/consensus/processes/headertipsmanager/headertipsmanager.go b/domain/consensus/processes/headertipsmanager/headertipsmanager.go deleted file mode 100644 index 5ad81bdbf..000000000 --- a/domain/consensus/processes/headertipsmanager/headertipsmanager.go +++ /dev/null @@ -1,59 +0,0 @@ -package headertipsmanager - -import ( - "github.com/kaspanet/kaspad/domain/consensus/model" - "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" -) - -type headerTipsManager struct { - databaseContext model.DBReader - dagTopologyManager model.DAGTopologyManager - ghostdagManager model.GHOSTDAGManager - headerTipsStore model.HeaderTipsStore -} - -// New instantiates a new HeaderTipsManager -func New(databaseContext model.DBReader, - dagTopologyManager model.DAGTopologyManager, - ghostdagManager model.GHOSTDAGManager, - headerTipsStore model.HeaderTipsStore) model.HeaderTipsManager { - return &headerTipsManager{ - databaseContext: databaseContext, - dagTopologyManager: dagTopologyManager, - ghostdagManager: ghostdagManager, - headerTipsStore: headerTipsStore, - } -} - -func (h headerTipsManager) AddHeaderTip(hash *externalapi.DomainHash) error { - tips := []*externalapi.DomainHash{} - hasTips, err := h.headerTipsStore.HasTips(h.databaseContext) - if err != nil { - return err - } - - if hasTips { - var err error - tips, err = h.headerTipsStore.Tips(h.databaseContext) - if err != nil { - return err - } - } - - newTips := make([]*externalapi.DomainHash, 0, len(tips)+1) - for _, tip := range tips { - isAncestorOf, err := h.dagTopologyManager.IsAncestorOf(tip, hash) - if err != nil { - return err - } - - if !isAncestorOf { - newTips = append(newTips, tip) - } - } - - newTips = append(newTips, hash) - h.headerTipsStore.Stage(newTips) - - return nil -} diff --git a/domain/consensus/processes/headertipsmanager/selected_tip.go b/domain/consensus/processes/headertipsmanager/selected_tip.go deleted file mode 100644 index a2d28f3df..000000000 --- a/domain/consensus/processes/headertipsmanager/selected_tip.go +++ /dev/null @@ -1,17 +0,0 @@ -package headertipsmanager - -import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" - -func (h headerTipsManager) SelectedTip() (*externalapi.DomainHash, error) { - tips, err := h.headerTipsStore.Tips(h.databaseContext) - if err != nil { - return nil, err - } - - selectedTip, err := h.ghostdagManager.ChooseSelectedParent(tips...) - if err != nil { - return nil, err - } - - return selectedTip, nil -} diff --git a/domain/consensus/processes/pruningmanager/log.go b/domain/consensus/processes/pruningmanager/log.go new file mode 100644 index 000000000..68109a28e --- /dev/null +++ b/domain/consensus/processes/pruningmanager/log.go @@ -0,0 +1,5 @@ +package pruningmanager + +import "github.com/kaspanet/kaspad/infrastructure/logger" + +var log, _ = logger.Get(logger.SubsystemTags.PRNM) diff --git a/domain/consensus/processes/pruningmanager/pruningmanager.go b/domain/consensus/processes/pruningmanager/pruningmanager.go index 57ca1dd12..11a0e7431 100644 --- a/domain/consensus/processes/pruningmanager/pruningmanager.go +++ b/domain/consensus/processes/pruningmanager/pruningmanager.go @@ -11,13 +11,14 @@ import ( type pruningManager struct { databaseContext model.DBReader - dagTraversalManager model.DAGTraversalManager - dagTopologyManager model.DAGTopologyManager - consensusStateManager model.ConsensusStateManager - consensusStateStore model.ConsensusStateStore - ghostdagDataStore model.GHOSTDAGDataStore - pruningStore model.PruningStore - blockStatusStore model.BlockStatusStore + dagTraversalManager model.DAGTraversalManager + dagTopologyManager model.DAGTopologyManager + consensusStateManager model.ConsensusStateManager + consensusStateStore model.ConsensusStateStore + ghostdagDataStore model.GHOSTDAGDataStore + pruningStore model.PruningStore + blockStatusStore model.BlockStatusStore + headerSelectedTipStore model.HeaderSelectedTipStore multiSetStore model.MultisetStore acceptanceDataStore model.AcceptanceDataStore @@ -40,6 +41,7 @@ func New( ghostdagDataStore model.GHOSTDAGDataStore, pruningStore model.PruningStore, blockStatusStore model.BlockStatusStore, + headerSelectedTipStore model.HeaderSelectedTipStore, multiSetStore model.MultisetStore, acceptanceDataStore model.AcceptanceDataStore, @@ -52,27 +54,28 @@ func New( ) model.PruningManager { return &pruningManager{ - databaseContext: databaseContext, - dagTraversalManager: dagTraversalManager, - dagTopologyManager: dagTopologyManager, - consensusStateManager: consensusStateManager, - consensusStateStore: consensusStateStore, - ghostdagDataStore: ghostdagDataStore, - pruningStore: pruningStore, - blockStatusStore: blockStatusStore, - multiSetStore: multiSetStore, - acceptanceDataStore: acceptanceDataStore, - blocksStore: blocksStore, - utxoDiffStore: utxoDiffStore, - genesisHash: genesisHash, - pruningDepth: pruningDepth, - finalityInterval: finalityInterval, + databaseContext: databaseContext, + dagTraversalManager: dagTraversalManager, + dagTopologyManager: dagTopologyManager, + consensusStateManager: consensusStateManager, + consensusStateStore: consensusStateStore, + ghostdagDataStore: ghostdagDataStore, + pruningStore: pruningStore, + blockStatusStore: blockStatusStore, + multiSetStore: multiSetStore, + acceptanceDataStore: acceptanceDataStore, + blocksStore: blocksStore, + utxoDiffStore: utxoDiffStore, + headerSelectedTipStore: headerSelectedTipStore, + genesisHash: genesisHash, + pruningDepth: pruningDepth, + finalityInterval: finalityInterval, } } // FindNextPruningPoint finds the next pruning point from the // given blockHash -func (pm *pruningManager) FindNextPruningPoint() error { +func (pm *pruningManager) UpdatePruningPointByVirtual() error { hasPruningPoint, err := pm.pruningStore.HasPruningPoint(pm.databaseContext) if err != nil { return err @@ -95,40 +98,41 @@ func (pm *pruningManager) FindNextPruningPoint() error { return err } + virtualSelectedParent, err := pm.ghostdagDataStore.Get(pm.databaseContext, virtual.SelectedParent()) + if err != nil { + return err + } + currentPGhost, err := pm.ghostdagDataStore.Get(pm.databaseContext, currentP) if err != nil { return err } currentPBlueScore := currentPGhost.BlueScore() // Because the pruning point changes only once per finality, then there's no need to even check for that if a finality interval hasn't passed. - if virtual.BlueScore() <= currentPBlueScore+pm.finalityInterval { + if virtualSelectedParent.BlueScore() <= currentPBlueScore+pm.finalityInterval { return nil } // This means the pruning point is still genesis. - if virtual.BlueScore() <= pm.pruningDepth+pm.finalityInterval { + if virtualSelectedParent.BlueScore() <= pm.pruningDepth+pm.finalityInterval { return nil } // get Virtual(pruningDepth) - candidatePHash, err := pm.dagTraversalManager.BlockAtDepth(model.VirtualBlockHash, pm.pruningDepth) - if err != nil { - return err - } - candidatePGhost, err := pm.ghostdagDataStore.Get(pm.databaseContext, candidatePHash) + newPruningPoint, err := pm.calculatePruningPointFromBlock(model.VirtualBlockHash) if err != nil { return err } - // Actually check if the pruning point changed - if (currentPBlueScore / pm.finalityInterval) < (candidatePGhost.BlueScore() / pm.finalityInterval) { - err = pm.savePruningPoint(candidatePHash) + if *newPruningPoint != *currentP { + err = pm.savePruningPoint(newPruningPoint) if err != nil { return err } - return pm.deletePastBlocks(candidatePHash) + return pm.deletePastBlocks(newPruningPoint) } - return pm.deletePastBlocks(currentP) + + return nil } func (pm *pruningManager) deletePastBlocks(pruningPoint *externalapi.DomainHash) error { @@ -233,6 +237,30 @@ func (pm *pruningManager) deleteBlock(blockHash *externalapi.DomainHash) (alread return false, nil } +func (pm *pruningManager) CalculatePruningPointByHeaderSelectedTip() (*externalapi.DomainHash, error) { + headersSelectedTip, err := pm.headerSelectedTipStore.HeadersSelectedTip(pm.databaseContext) + if err != nil { + return nil, err + } + + return pm.calculatePruningPointFromBlock(headersSelectedTip) +} + +func (pm *pruningManager) calculatePruningPointFromBlock(blockHash *externalapi.DomainHash) (*externalapi.DomainHash, error) { + ghostdagData, err := pm.ghostdagDataStore.Get(pm.databaseContext, blockHash) + if err != nil { + return nil, err + } + + targetBlueScore := uint64(0) + if ghostdagData.BlueScore() > pm.pruningDepth { + // The target blue is calculated by calculating ghostdagData.BlueScore() - pm.pruningDepth and rounding + // down with the precision of finality interval. + targetBlueScore = ((ghostdagData.BlueScore() - pm.pruningDepth) / pm.finalityInterval) * pm.finalityInterval + } + return pm.dagTraversalManager.LowestChainBlockAboveOrEqualToBlueScore(blockHash, targetBlueScore) +} + func serializeUTXOSetIterator(iter model.ReadOnlyUTXOSetIterator) ([]byte, error) { serializedUtxo, err := utxoserialization.ReadOnlyUTXOSetToProtoUTXOSet(iter) if err != nil { diff --git a/domain/consensus/processes/reachabilitymanager/reachability_external_test.go b/domain/consensus/processes/reachabilitymanager/reachability_external_test.go index de8123ba9..62fe0796c 100644 --- a/domain/consensus/processes/reachabilitymanager/reachability_external_test.go +++ b/domain/consensus/processes/reachabilitymanager/reachability_external_test.go @@ -159,7 +159,7 @@ func TestReindexIntervalsEarlierThanReindexRoot(t *testing.T) { factory := consensus.NewFactory() tc, tearDown, err := factory.NewTestConsensus(params, "TestUpdateReindexRoot") if err != nil { - t.Fatalf("NewTestConsensus: %s", err) + t.Fatalf("NewTestConsensus: %+v", err) } defer tearDown() diff --git a/domain/consensus/processes/syncmanager/antipast.go b/domain/consensus/processes/syncmanager/antipast.go index c0e1908ec..14d2406be 100644 --- a/domain/consensus/processes/syncmanager/antipast.go +++ b/domain/consensus/processes/syncmanager/antipast.go @@ -99,26 +99,26 @@ func (sm *syncManager) antiPastHashesBetween(lowHash, highHash *externalapi.Doma } func (sm *syncManager) missingBlockBodyHashes(highHash *externalapi.DomainHash) ([]*externalapi.DomainHash, error) { - headerTipsPruningPoint, err := sm.consensusStateManager.HeaderTipsPruningPoint() + pruningPoint, err := sm.pruningStore.PruningPoint(sm.databaseContext) if err != nil { return nil, err } - selectedChildIterator, err := sm.dagTraversalManager.SelectedChildIterator(highHash, headerTipsPruningPoint) + selectedChildIterator, err := sm.dagTraversalManager.SelectedChildIterator(highHash, pruningPoint) if err != nil { return nil, err } - lowHash := headerTipsPruningPoint + lowHash := pruningPoint foundHeaderOnlyBlock := false for selectedChildIterator.Next() { selectedChild := selectedChildIterator.Get() - selectedChildStatus, err := sm.blockStatusStore.Get(sm.databaseContext, selectedChild) + hasBlock, err := sm.blockStore.HasBlock(sm.databaseContext, selectedChild) if err != nil { return nil, err } - if selectedChildStatus == externalapi.StatusHeaderOnly { + if !hasBlock { foundHeaderOnlyBlock = true break } @@ -167,24 +167,3 @@ func (sm *syncManager) isHeaderOnlyBlock(blockHash *externalapi.DomainHash) (boo return status == externalapi.StatusHeaderOnly, nil } - -func (sm *syncManager) isBlockInHeaderPruningPointFuture(blockHash *externalapi.DomainHash) (bool, error) { - if *blockHash == *sm.genesisBlockHash { - return false, nil - } - - exists, err := sm.blockStatusStore.Exists(sm.databaseContext, blockHash) - if err != nil { - return false, err - } - if !exists { - return false, nil - } - - headerTipsPruningPoint, err := sm.consensusStateManager.HeaderTipsPruningPoint() - if err != nil { - return false, err - } - - return sm.dagTopologyManager.IsAncestorOf(headerTipsPruningPoint, blockHash) -} diff --git a/domain/consensus/processes/syncmanager/syncinfo.go b/domain/consensus/processes/syncmanager/syncinfo.go index 2738f8e11..90a5009d8 100644 --- a/domain/consensus/processes/syncmanager/syncinfo.go +++ b/domain/consensus/processes/syncmanager/syncinfo.go @@ -4,84 +4,43 @@ import ( "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" ) -// areHeaderTipsSyncedMaxTimeDifference is the number of blocks from -// the header virtual selected parent (estimated by timestamps) for -// kaspad to be considered not synced -const areHeaderTipsSyncedMaxTimeDifference = 300 // 5 minutes - func (sm *syncManager) syncInfo() (*externalapi.SyncInfo, error) { - syncState, err := sm.resolveSyncState() + isAwaitingUTXOSet, ibdRootUTXOBlockHash, err := sm.isAwaitingUTXOSet() if err != nil { return nil, err } - var ibdRootUTXOBlockHash *externalapi.DomainHash - if syncState == externalapi.SyncStateAwaitingUTXOSet { - ibdRootUTXOBlockHash, err = sm.consensusStateManager.HeaderTipsPruningPoint() - if err != nil { - return nil, err - } - } - headerCount := sm.getHeaderCount() blockCount := sm.getBlockCount() return &externalapi.SyncInfo{ - State: syncState, + IsAwaitingUTXOSet: isAwaitingUTXOSet, IBDRootUTXOBlockHash: ibdRootUTXOBlockHash, HeaderCount: headerCount, BlockCount: blockCount, }, nil } -func (sm *syncManager) resolveSyncState() (externalapi.SyncState, error) { - hasTips, err := sm.headerTipsStore.HasTips(sm.databaseContext) +func (sm *syncManager) isAwaitingUTXOSet() (isAwaitingUTXOSet bool, ibdRootUTXOBlockHash *externalapi.DomainHash, + err error) { + + pruningPointByHeaders, err := sm.pruningManager.CalculatePruningPointByHeaderSelectedTip() if err != nil { - return 0, err - } - if !hasTips { - return externalapi.SyncStateAwaitingGenesis, nil + return false, nil, err } - headerVirtualSelectedParentHash, err := sm.headerVirtualSelectedParentHash() + pruningPoint, err := sm.pruningStore.PruningPoint(sm.databaseContext) if err != nil { - return 0, err - } - headerVirtualSelectedParentStatus, err := sm.blockStatusStore.Get(sm.databaseContext, headerVirtualSelectedParentHash) - if err != nil { - return 0, err - } - if headerVirtualSelectedParentStatus != externalapi.StatusHeaderOnly { - return externalapi.SyncStateSynced, nil + return false, nil, err } - // Once the header tips are synced, check the status of - // the pruning point from the point of view of the header - // tips. We check it against StatusValid (rather than - // StatusHeaderOnly) because once we do receive the - // UTXO set of said pruning point, the state is explicitly - // set to StatusValid. - headerTipsPruningPoint, err := sm.consensusStateManager.HeaderTipsPruningPoint() - if err != nil { - return 0, err - } - headerTipsPruningPointStatus, err := sm.blockStatusStore.Get(sm.databaseContext, headerTipsPruningPoint) - if err != nil { - return 0, err - } - if headerTipsPruningPointStatus != externalapi.StatusValid { - return externalapi.SyncStateAwaitingUTXOSet, nil + // If the pruning point by headers is different from the current point + // it means we need to request the new pruning point UTXO set. + if *pruningPoint != *pruningPointByHeaders { + return true, pruningPointByHeaders, nil } - return externalapi.SyncStateAwaitingBlockBodies, nil -} - -func (sm *syncManager) headerVirtualSelectedParentHash() (*externalapi.DomainHash, error) { - headerTips, err := sm.headerTipsStore.Tips(sm.databaseContext) - if err != nil { - return nil, err - } - return sm.ghostdagManager.ChooseSelectedParent(headerTips...) + return false, nil, nil } func (sm *syncManager) getHeaderCount() uint64 { diff --git a/domain/consensus/processes/syncmanager/syncmanager.go b/domain/consensus/processes/syncmanager/syncmanager.go index c3edba5d6..1cc7264ed 100644 --- a/domain/consensus/processes/syncmanager/syncmanager.go +++ b/domain/consensus/processes/syncmanager/syncmanager.go @@ -7,53 +7,50 @@ import ( ) type syncManager struct { - databaseContext model.DBReader - genesisBlockHash *externalapi.DomainHash - targetTimePerBlock int64 + databaseContext model.DBReader + genesisBlockHash *externalapi.DomainHash - dagTraversalManager model.DAGTraversalManager - dagTopologyManager model.DAGTopologyManager - ghostdagManager model.GHOSTDAGManager - consensusStateManager model.ConsensusStateManager + dagTraversalManager model.DAGTraversalManager + dagTopologyManager model.DAGTopologyManager + ghostdagManager model.GHOSTDAGManager + pruningManager model.PruningManager ghostdagDataStore model.GHOSTDAGDataStore blockStatusStore model.BlockStatusStore blockHeaderStore model.BlockHeaderStore - headerTipsStore model.HeaderTipsStore blockStore model.BlockStore + pruningStore model.PruningStore } // New instantiates a new SyncManager func New( databaseContext model.DBReader, genesisBlockHash *externalapi.DomainHash, - targetTimePerBlock int64, dagTraversalManager model.DAGTraversalManager, dagTopologyManager model.DAGTopologyManager, ghostdagManager model.GHOSTDAGManager, - consensusStateManager model.ConsensusStateManager, + pruningManager model.PruningManager, ghostdagDataStore model.GHOSTDAGDataStore, blockStatusStore model.BlockStatusStore, blockHeaderStore model.BlockHeaderStore, - headerTipsStore model.HeaderTipsStore, - blockStore model.BlockStore) model.SyncManager { + blockStore model.BlockStore, + pruningStore model.PruningStore) model.SyncManager { return &syncManager{ - databaseContext: databaseContext, - genesisBlockHash: genesisBlockHash, - targetTimePerBlock: targetTimePerBlock, + databaseContext: databaseContext, + genesisBlockHash: genesisBlockHash, - dagTraversalManager: dagTraversalManager, - dagTopologyManager: dagTopologyManager, - ghostdagManager: ghostdagManager, - consensusStateManager: consensusStateManager, + dagTraversalManager: dagTraversalManager, + dagTopologyManager: dagTopologyManager, + ghostdagManager: ghostdagManager, + pruningManager: pruningManager, ghostdagDataStore: ghostdagDataStore, blockStatusStore: blockStatusStore, blockHeaderStore: blockHeaderStore, - headerTipsStore: headerTipsStore, blockStore: blockStore, + pruningStore: pruningStore, } } @@ -71,13 +68,6 @@ func (sm *syncManager) GetMissingBlockBodyHashes(highHash *externalapi.DomainHas return sm.missingBlockBodyHashes(highHash) } -func (sm *syncManager) IsBlockInHeaderPruningPointFuture(blockHash *externalapi.DomainHash) (bool, error) { - onEnd := logger.LogAndMeasureExecutionTime(log, "IsBlockInHeaderPruningPointFuture") - defer onEnd() - - return sm.isBlockInHeaderPruningPointFuture(blockHash) -} - func (sm *syncManager) CreateBlockLocator(lowHash, highHash *externalapi.DomainHash, limit uint32) (externalapi.BlockLocator, error) { onEnd := logger.LogAndMeasureExecutionTime(log, "CreateBlockLocator") defer onEnd() diff --git a/domain/consensus/ruleerrors/rule_error.go b/domain/consensus/ruleerrors/rule_error.go index 051efccaf..6d6c0fe97 100644 --- a/domain/consensus/ruleerrors/rule_error.go +++ b/domain/consensus/ruleerrors/rule_error.go @@ -237,6 +237,8 @@ var ( //ErrBlockIsTooMuchInTheFuture indicates that the block timestamp is too much in the future. ErrBlockIsTooMuchInTheFuture = newRuleError("ErrBlockIsTooMuchInTheFuture") + + ErrUnexpectedPruningPoint = newRuleError("ErrUnexpectedPruningPoint") ) // RuleError identifies a rule violation. It is used to indicate that diff --git a/domain/consensus/test_consensus_getters.go b/domain/consensus/test_consensus_getters.go index 539bdcb0b..58712718d 100644 --- a/domain/consensus/test_consensus_getters.go +++ b/domain/consensus/test_consensus_getters.go @@ -37,8 +37,8 @@ func (tc *testConsensus) GHOSTDAGDataStore() model.GHOSTDAGDataStore { return tc.ghostdagDataStore } -func (tc *testConsensus) HeaderTipsStore() model.HeaderTipsStore { - return tc.headerTipsStore +func (tc *testConsensus) HeaderTipsStore() model.HeaderSelectedTipStore { + return tc.headersSelectedTipStore } func (tc *testConsensus) MultisetStore() model.MultisetStore { @@ -93,7 +93,7 @@ func (tc *testConsensus) GHOSTDAGManager() model.GHOSTDAGManager { return tc.ghostdagManager } -func (tc *testConsensus) HeaderTipsManager() model.HeaderTipsManager { +func (tc *testConsensus) HeaderTipsManager() model.HeadersSelectedTipManager { return tc.headerTipsManager } diff --git a/infrastructure/config/network.go b/infrastructure/config/network.go index 4ec22a74e..a2b49033e 100644 --- a/infrastructure/config/network.go +++ b/infrastructure/config/network.go @@ -83,6 +83,11 @@ func (networkFlags *NetworkFlags) ResolveNetwork(parser *flags.Parser) error { return errors.Errorf("Mainnet has not launched yet, use --testnet to run in testnet mode") } + err := networkFlags.overrideDAGParams() + if err != nil { + return err + } + return nil } diff --git a/infrastructure/logger/logger.go b/infrastructure/logger/logger.go index 8e2d952b3..0776fb79d 100644 --- a/infrastructure/logger/logger.go +++ b/infrastructure/logger/logger.go @@ -54,6 +54,8 @@ var ( snvrLog = BackendLog.Logger("SNVR") wsvcLog = BackendLog.Logger("WSVC") reacLog = BackendLog.Logger("REAC") + prnmLog = BackendLog.Logger("PRNM") + blvlLog = BackendLog.Logger("BLVL") ) // SubsystemTags is an enum of all sub system tags @@ -85,7 +87,9 @@ var SubsystemTags = struct { DNSS, SNVR, WSVC, - REAC string + REAC, + PRNM, + BLVL string }{ ADXR: "ADXR", AMGR: "AMGR", @@ -115,6 +119,8 @@ var SubsystemTags = struct { SNVR: "SNVR", WSVC: "WSVC", REAC: "REAC", + PRNM: "PRNM", + BLVL: "BLVL", } // subsystemLoggers maps each subsystem identifier to its associated logger. @@ -147,6 +153,8 @@ var subsystemLoggers = map[string]*Logger{ SubsystemTags.SNVR: snvrLog, SubsystemTags.WSVC: wsvcLog, SubsystemTags.REAC: reacLog, + SubsystemTags.PRNM: prnmLog, + SubsystemTags.BLVL: blvlLog, } // InitLog attaches log file and error log file to the backend log.