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
This commit is contained in:
Ori Newman 2020-12-14 17:53:08 +02:00 committed by GitHub
parent 6926a7ab81
commit 48e1a2c396
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 769 additions and 800 deletions

View File

@ -212,7 +212,6 @@ func (flow *handleRelayInvsFlow) processBlock(block *externalapi.DomainBlock) ([
return missingParentsError.MissingParentHashes, nil return missingParentsError.MissingParentHashes, nil
} }
log.Warnf("Rejected block %s from %s: %s", blockHash, flow.peer, err) 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, protocolerrors.Wrapf(true, err, "got invalid block %s from relay", blockHash)
} }
return nil, nil return nil, nil

View File

@ -35,7 +35,7 @@ func (flow *handleRelayInvsFlow) runIBDIfNotRunning(highHash *externalapi.Domain
if err != nil { if err != nil {
return err return err
} }
if syncInfo.State == externalapi.SyncStateAwaitingUTXOSet { if syncInfo.IsAwaitingUTXOSet {
found, err := flow.fetchMissingUTXOSet(syncInfo.IBDRootUTXOBlockHash) found, err := flow.fetchMissingUTXOSet(syncInfo.IBDRootUTXOBlockHash)
if err != nil { if err != nil {
return err return err
@ -190,8 +190,8 @@ func (flow *handleRelayInvsFlow) processHeader(msgBlockHeader *appmessage.MsgBlo
return nil return nil
} }
func (flow *handleRelayInvsFlow) fetchMissingUTXOSet(ibdRootHash *externalapi.DomainHash) (bool, error) { func (flow *handleRelayInvsFlow) fetchMissingUTXOSet(ibdRootHash *externalapi.DomainHash) (succeed bool, err error) {
err := flow.outgoingRoute.Enqueue(appmessage.NewMsgRequestIBDRootUTXOSetAndBlock(ibdRootHash)) err = flow.outgoingRoute.Enqueue(appmessage.NewMsgRequestIBDRootUTXOSetAndBlock(ibdRootHash))
if err != nil { if err != nil {
return false, err return false, err
} }
@ -205,17 +205,23 @@ func (flow *handleRelayInvsFlow) fetchMissingUTXOSet(ibdRootHash *externalapi.Do
return false, nil return false, nil
} }
err = flow.Domain().Consensus().ValidateAndInsertBlock(block) err = flow.Domain().Consensus().ValidateAndInsertPruningPoint(block, utxoSet)
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)
if err != nil { if err != nil {
return false, protocolerrors.ConvertToBanningProtocolErrorIfRuleError(err, "error with IBD root UTXO set") 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 return true, nil
} }

View File

@ -25,25 +25,25 @@ type consensus struct {
dagTraversalManager model.DAGTraversalManager dagTraversalManager model.DAGTraversalManager
difficultyManager model.DifficultyManager difficultyManager model.DifficultyManager
ghostdagManager model.GHOSTDAGManager ghostdagManager model.GHOSTDAGManager
headerTipsManager model.HeaderTipsManager headerTipsManager model.HeadersSelectedTipManager
mergeDepthManager model.MergeDepthManager mergeDepthManager model.MergeDepthManager
pruningManager model.PruningManager pruningManager model.PruningManager
reachabilityManager model.ReachabilityManager reachabilityManager model.ReachabilityManager
finalityManager model.FinalityManager finalityManager model.FinalityManager
acceptanceDataStore model.AcceptanceDataStore acceptanceDataStore model.AcceptanceDataStore
blockStore model.BlockStore blockStore model.BlockStore
blockHeaderStore model.BlockHeaderStore blockHeaderStore model.BlockHeaderStore
pruningStore model.PruningStore pruningStore model.PruningStore
ghostdagDataStore model.GHOSTDAGDataStore ghostdagDataStore model.GHOSTDAGDataStore
blockRelationStore model.BlockRelationStore blockRelationStore model.BlockRelationStore
blockStatusStore model.BlockStatusStore blockStatusStore model.BlockStatusStore
consensusStateStore model.ConsensusStateStore consensusStateStore model.ConsensusStateStore
headerTipsStore model.HeaderTipsStore headersSelectedTipStore model.HeaderSelectedTipStore
multisetStore model.MultisetStore multisetStore model.MultisetStore
reachabilityDataStore model.ReachabilityDataStore reachabilityDataStore model.ReachabilityDataStore
utxoDiffStore model.UTXODiffStore utxoDiffStore model.UTXODiffStore
finalityStore model.FinalityStore finalityStore model.FinalityStore
} }
// BuildBlock builds a block over the current state, with the transactions // 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() blockInfo.BlueScore = ghostdagData.BlueScore()
isBlockInHeaderPruningPointFuture, err := s.syncManager.IsBlockInHeaderPruningPointFuture(blockHash)
if err != nil {
return nil, err
}
blockInfo.IsBlockInHeaderPruningPointFuture = isBlockInHeaderPruningPointFuture
return blockInfo, nil return blockInfo, nil
} }
@ -183,11 +177,11 @@ func (s *consensus) GetPruningPointUTXOSet(expectedPruningPointHash *externalapi
return serializedUTXOSet, nil return serializedUTXOSet, nil
} }
func (s *consensus) SetPruningPointUTXOSet(serializedUTXOSet []byte) error { func (s *consensus) ValidateAndInsertPruningPoint(newPruningPoint *externalapi.DomainBlock, serializedUTXOSet []byte) error {
s.lock.Lock() s.lock.Lock()
defer s.lock.Unlock() defer s.lock.Unlock()
return s.consensusStateManager.SetPruningPointUTXOSet(serializedUTXOSet) return s.blockProcessor.ValidateAndInsertPruningPoint(newPruningPoint, serializedUTXOSet)
} }
func (s *consensus) GetVirtualSelectedParent() (*externalapi.DomainBlock, error) { 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()) 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) { func (s *consensus) Tips() ([]*externalapi.DomainHash, error) {
return s.consensusStateStore.Tips(s.databaseContext) return s.consensusStateStore.Tips(s.databaseContext)
} }
@ -246,3 +219,24 @@ func (s *consensus) GetVirtualInfo() (*externalapi.VirtualInfo, error) {
PastMedianTime: pastMedianTime, PastMedianTime: pastMedianTime,
}, nil }, 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()
}

View File

@ -42,9 +42,6 @@ func TestConsensus_GetBlockInfo(t *testing.T) {
if info.BlockStatus != externalapi.StatusInvalid { if info.BlockStatus != externalapi.StatusInvalid {
t.Fatalf("Expected block status: %s, instead got: %s", externalapi.StatusInvalid, info.BlockStatus) 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{} emptyCoinbase := externalapi.DomainCoinbaseData{}
validBlock, err := consensus.BuildBlock(&emptyCoinbase, nil) validBlock, err := consensus.BuildBlock(&emptyCoinbase, nil)
@ -68,9 +65,6 @@ func TestConsensus_GetBlockInfo(t *testing.T) {
if info.BlockStatus != externalapi.StatusValid { if info.BlockStatus != externalapi.StatusValid {
t.Fatalf("Expected block status: %s, instead got: %s", externalapi.StatusValid, info.BlockStatus) 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)
}
}) })
} }

View File

@ -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)
}

View File

@ -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)
}

View File

@ -17,7 +17,7 @@ import (
"github.com/kaspanet/kaspad/domain/consensus/datastructures/consensusstatestore" "github.com/kaspanet/kaspad/domain/consensus/datastructures/consensusstatestore"
"github.com/kaspanet/kaspad/domain/consensus/datastructures/finalitystore" "github.com/kaspanet/kaspad/domain/consensus/datastructures/finalitystore"
"github.com/kaspanet/kaspad/domain/consensus/datastructures/ghostdagdatastore" "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/multisetstore"
"github.com/kaspanet/kaspad/domain/consensus/datastructures/pruningstore" "github.com/kaspanet/kaspad/domain/consensus/datastructures/pruningstore"
"github.com/kaspanet/kaspad/domain/consensus/datastructures/reachabilitydatastore" "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/dagtopologymanager"
"github.com/kaspanet/kaspad/domain/consensus/processes/difficultymanager" "github.com/kaspanet/kaspad/domain/consensus/processes/difficultymanager"
"github.com/kaspanet/kaspad/domain/consensus/processes/ghostdagmanager" "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/mergedepthmanager"
"github.com/kaspanet/kaspad/domain/consensus/processes/pastmediantimemanager" "github.com/kaspanet/kaspad/domain/consensus/processes/pastmediantimemanager"
"github.com/kaspanet/kaspad/domain/consensus/processes/pruningmanager" "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) utxoDiffStore := utxodiffstore.New(200)
consensusStateStore := consensusstatestore.New() consensusStateStore := consensusstatestore.New()
ghostdagDataStore := ghostdagdatastore.New(10_000) ghostdagDataStore := ghostdagdatastore.New(10_000)
headerTipsStore := headertipsstore.New() headersSelectedTipStore := headersselectedtipstore.New()
finalityStore := finalitystore.New(200) finalityStore := finalitystore.New(200)
// Processes // Processes
@ -139,7 +139,7 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat
dagParams.CoinbasePayloadScriptPublicKeyMaxLength, dagParams.CoinbasePayloadScriptPublicKeyMaxLength,
ghostdagDataStore, ghostdagDataStore,
acceptanceDataStore) acceptanceDataStore)
headerTipsManager := headertipsmanager.New(dbManager, dagTopologyManager, ghostdagManager, headerTipsStore) headerTipsManager := headersselectedtipmanager.New(dbManager, dagTopologyManager, ghostdagManager, headersSelectedTipStore)
genesisHash := dagParams.GenesisHash genesisHash := dagParams.GenesisHash
finalityManager := finalitymanager.New( finalityManager := finalitymanager.New(
dbManager, dbManager,
@ -211,7 +211,8 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat
blockRelationStore, blockRelationStore,
acceptanceDataStore, acceptanceDataStore,
blockHeaderStore, blockHeaderStore,
headerTipsStore) headersSelectedTipStore,
pruningStore)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -225,6 +226,7 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat
ghostdagDataStore, ghostdagDataStore,
pruningStore, pruningStore,
blockStatusStore, blockStatusStore,
headersSelectedTipStore,
multisetStore, multisetStore,
acceptanceDataStore, acceptanceDataStore,
blockStore, blockStore,
@ -236,17 +238,16 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat
syncManager := syncmanager.New( syncManager := syncmanager.New(
dbManager, dbManager,
genesisHash, genesisHash,
dagParams.TargetTimePerBlock.Milliseconds(),
dagTraversalManager, dagTraversalManager,
dagTopologyManager, dagTopologyManager,
ghostdagManager, ghostdagManager,
consensusStateManager, pruningManager,
ghostdagDataStore, ghostdagDataStore,
blockStatusStore, blockStatusStore,
blockHeaderStore, blockHeaderStore,
headerTipsStore, blockStore,
blockStore) pruningStore)
blockBuilder := blockbuilder.New( blockBuilder := blockbuilder.New(
dbManager, dbManager,
@ -287,7 +288,7 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat
reachabilityDataStore, reachabilityDataStore,
utxoDiffStore, utxoDiffStore,
blockHeaderStore, blockHeaderStore,
headerTipsStore, headersSelectedTipStore,
finalityStore) finalityStore)
c := &consensus{ c := &consensus{
@ -312,19 +313,19 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat
reachabilityManager: reachabilityManager, reachabilityManager: reachabilityManager,
finalityManager: finalityManager, finalityManager: finalityManager,
acceptanceDataStore: acceptanceDataStore, acceptanceDataStore: acceptanceDataStore,
blockStore: blockStore, blockStore: blockStore,
blockHeaderStore: blockHeaderStore, blockHeaderStore: blockHeaderStore,
pruningStore: pruningStore, pruningStore: pruningStore,
ghostdagDataStore: ghostdagDataStore, ghostdagDataStore: ghostdagDataStore,
blockStatusStore: blockStatusStore, blockStatusStore: blockStatusStore,
blockRelationStore: blockRelationStore, blockRelationStore: blockRelationStore,
consensusStateStore: consensusStateStore, consensusStateStore: consensusStateStore,
headerTipsStore: headerTipsStore, headersSelectedTipStore: headersSelectedTipStore,
multisetStore: multisetStore, multisetStore: multisetStore,
reachabilityDataStore: reachabilityDataStore, reachabilityDataStore: reachabilityDataStore,
utxoDiffStore: utxoDiffStore, utxoDiffStore: utxoDiffStore,
finalityStore: finalityStore, finalityStore: finalityStore,
} }
genesisInfo, err := c.GetBlockInfo(genesisHash) genesisInfo, err := c.GetBlockInfo(genesisHash)

View File

@ -5,6 +5,4 @@ type BlockInfo struct {
Exists bool Exists bool
BlockStatus BlockStatus BlockStatus BlockStatus
BlueScore uint64 BlueScore uint64
IsBlockInHeaderPruningPointFuture bool
} }

View File

@ -13,7 +13,7 @@ type Consensus interface {
GetHashesBetween(lowHash, highHash *DomainHash) ([]*DomainHash, error) GetHashesBetween(lowHash, highHash *DomainHash) ([]*DomainHash, error)
GetMissingBlockBodyHashes(highHash *DomainHash) ([]*DomainHash, error) GetMissingBlockBodyHashes(highHash *DomainHash) ([]*DomainHash, error)
GetPruningPointUTXOSet(expectedPruningPointHash *DomainHash) ([]byte, error) GetPruningPointUTXOSet(expectedPruningPointHash *DomainHash) ([]byte, error)
SetPruningPointUTXOSet(serializedUTXOSet []byte) error ValidateAndInsertPruningPoint(newPruningPoint *DomainBlock, serializedUTXOSet []byte) error
GetVirtualSelectedParent() (*DomainBlock, error) GetVirtualSelectedParent() (*DomainBlock, error)
CreateBlockLocator(lowHash, highHash *DomainHash, limit uint32) (BlockLocator, error) CreateBlockLocator(lowHash, highHash *DomainHash, limit uint32) (BlockLocator, error)
FindNextBlockLocatorBoundaries(blockLocator BlockLocator) (lowHash, highHash *DomainHash, err error) FindNextBlockLocatorBoundaries(blockLocator BlockLocator) (lowHash, highHash *DomainHash, err error)

View File

@ -1,37 +1,8 @@
package externalapi 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("<unknown state (%d)>", s)
}
// SyncInfo holds info about the current sync state of the consensus // SyncInfo holds info about the current sync state of the consensus
type SyncInfo struct { type SyncInfo struct {
State SyncState IsAwaitingUTXOSet bool
IBDRootUTXOBlockHash *DomainHash IBDRootUTXOBlockHash *DomainHash
HeaderCount uint64 HeaderCount uint64
BlockCount uint64 BlockCount uint64

View File

@ -2,11 +2,11 @@ package model
import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
// HeaderTipsStore represents a store of the header tips // HeaderSelectedTipStore represents a store of the headers selected tip
type HeaderTipsStore interface { type HeaderSelectedTipStore interface {
Store Store
Stage(tips []*externalapi.DomainHash) Stage(selectedTip *externalapi.DomainHash)
IsStaged() bool IsStaged() bool
Tips(dbContext DBReader) ([]*externalapi.DomainHash, error) HeadersSelectedTip(dbContext DBReader) (*externalapi.DomainHash, error)
HasTips(dbContext DBReader) (bool, error) Has(dbContext DBReader) (bool, error)
} }

View File

@ -5,4 +5,5 @@ import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
// BlockProcessor is responsible for processing incoming blocks // BlockProcessor is responsible for processing incoming blocks
type BlockProcessor interface { type BlockProcessor interface {
ValidateAndInsertBlock(block *externalapi.DomainBlock) error ValidateAndInsertBlock(block *externalapi.DomainBlock) error
ValidateAndInsertPruningPoint(newPruningPoint *externalapi.DomainBlock, serializedUTXOSet []byte) error
} }

View File

@ -4,10 +4,9 @@ import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
// ConsensusStateManager manages the node's consensus state // ConsensusStateManager manages the node's consensus state
type ConsensusStateManager interface { type ConsensusStateManager interface {
AddBlockToVirtual(blockHash *externalapi.DomainHash) error AddBlock(blockHash *externalapi.DomainHash) error
PopulateTransactionWithUTXOEntries(transaction *externalapi.DomainTransaction) error PopulateTransactionWithUTXOEntries(transaction *externalapi.DomainTransaction) error
SetPruningPointUTXOSet(serializedUTXOSet []byte) error UpdatePruningPoint(newPruningPoint *externalapi.DomainBlock, serializedUTXOSet []byte) error
RestorePastUTXOSetIterator(blockHash *externalapi.DomainHash) (ReadOnlyUTXOSetIterator, error) RestorePastUTXOSetIterator(blockHash *externalapi.DomainHash) (ReadOnlyUTXOSetIterator, error)
HeaderTipsPruningPoint() (*externalapi.DomainHash, error)
CalculatePastUTXOAndAcceptanceData(blockHash *externalapi.DomainHash) (UTXODiff, AcceptanceData, Multiset, error) CalculatePastUTXOAndAcceptanceData(blockHash *externalapi.DomainHash) (UTXODiff, AcceptanceData, Multiset, error)
} }

View File

@ -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 // FinalityManager provides method to validate that a block does not violate finality
type FinalityManager interface { type FinalityManager interface {
IsViolatingFinality(blockHash *externalapi.DomainHash) (bool, error)
VirtualFinalityPoint() (*externalapi.DomainHash, error) VirtualFinalityPoint() (*externalapi.DomainHash, error)
FinalityPoint(blockHash *externalapi.DomainHash) (*externalapi.DomainHash, error) FinalityPoint(blockHash *externalapi.DomainHash) (*externalapi.DomainHash, error)
} }

View File

@ -2,8 +2,7 @@ package model
import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
// HeaderTipsManager manages the state of the header tips // HeadersSelectedTipManager manages the state of the headers selected tip
type HeaderTipsManager interface { type HeadersSelectedTipManager interface {
AddHeaderTip(hash *externalapi.DomainHash) error AddHeaderTip(hash *externalapi.DomainHash) error
SelectedTip() (*externalapi.DomainHash, error)
} }

View File

@ -1,6 +1,9 @@
package model package model
import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
// PruningManager resolves and manages the current pruning point // PruningManager resolves and manages the current pruning point
type PruningManager interface { type PruningManager interface {
FindNextPruningPoint() error UpdatePruningPointByVirtual() error
CalculatePruningPointByHeaderSelectedTip() (*externalapi.DomainHash, error)
} }

View File

@ -8,6 +8,5 @@ type SyncManager interface {
GetMissingBlockBodyHashes(highHash *externalapi.DomainHash) ([]*externalapi.DomainHash, error) GetMissingBlockBodyHashes(highHash *externalapi.DomainHash) ([]*externalapi.DomainHash, error)
CreateBlockLocator(lowHash, highHash *externalapi.DomainHash, limit uint32) (externalapi.BlockLocator, error) CreateBlockLocator(lowHash, highHash *externalapi.DomainHash, limit uint32) (externalapi.BlockLocator, error)
FindNextBlockLocatorBoundaries(blockLocator externalapi.BlockLocator) (lowHash, highHash *externalapi.DomainHash, err error) FindNextBlockLocatorBoundaries(blockLocator externalapi.BlockLocator) (lowHash, highHash *externalapi.DomainHash, err error)
IsBlockInHeaderPruningPointFuture(blockHash *externalapi.DomainHash) (bool, error)
GetSyncInfo() (*externalapi.SyncInfo, error) GetSyncInfo() (*externalapi.SyncInfo, error)
} }

View File

@ -30,7 +30,7 @@ type TestConsensus interface {
BlockStore() model.BlockStore BlockStore() model.BlockStore
ConsensusStateStore() model.ConsensusStateStore ConsensusStateStore() model.ConsensusStateStore
GHOSTDAGDataStore() model.GHOSTDAGDataStore GHOSTDAGDataStore() model.GHOSTDAGDataStore
HeaderTipsStore() model.HeaderTipsStore HeaderTipsStore() model.HeaderSelectedTipStore
MultisetStore() model.MultisetStore MultisetStore() model.MultisetStore
PruningStore() model.PruningStore PruningStore() model.PruningStore
ReachabilityDataStore() model.ReachabilityDataStore ReachabilityDataStore() model.ReachabilityDataStore
@ -46,7 +46,7 @@ type TestConsensus interface {
DAGTraversalManager() model.DAGTraversalManager DAGTraversalManager() model.DAGTraversalManager
DifficultyManager() model.DifficultyManager DifficultyManager() model.DifficultyManager
GHOSTDAGManager() model.GHOSTDAGManager GHOSTDAGManager() model.GHOSTDAGManager
HeaderTipsManager() model.HeaderTipsManager HeaderTipsManager() model.HeadersSelectedTipManager
MergeDepthManager() model.MergeDepthManager MergeDepthManager() model.MergeDepthManager
PastMedianTimeManager() model.PastMedianTimeManager PastMedianTimeManager() model.PastMedianTimeManager
PruningManager() model.PruningManager PruningManager() model.PruningManager

View File

@ -21,22 +21,22 @@ type blockProcessor struct {
ghostdagManager model.GHOSTDAGManager ghostdagManager model.GHOSTDAGManager
pastMedianTimeManager model.PastMedianTimeManager pastMedianTimeManager model.PastMedianTimeManager
coinbaseManager model.CoinbaseManager coinbaseManager model.CoinbaseManager
headerTipsManager model.HeaderTipsManager headerTipsManager model.HeadersSelectedTipManager
syncManager model.SyncManager syncManager model.SyncManager
acceptanceDataStore model.AcceptanceDataStore acceptanceDataStore model.AcceptanceDataStore
blockStore model.BlockStore blockStore model.BlockStore
blockStatusStore model.BlockStatusStore blockStatusStore model.BlockStatusStore
blockRelationStore model.BlockRelationStore blockRelationStore model.BlockRelationStore
multisetStore model.MultisetStore multisetStore model.MultisetStore
ghostdagDataStore model.GHOSTDAGDataStore ghostdagDataStore model.GHOSTDAGDataStore
consensusStateStore model.ConsensusStateStore consensusStateStore model.ConsensusStateStore
pruningStore model.PruningStore pruningStore model.PruningStore
reachabilityDataStore model.ReachabilityDataStore reachabilityDataStore model.ReachabilityDataStore
utxoDiffStore model.UTXODiffStore utxoDiffStore model.UTXODiffStore
blockHeaderStore model.BlockHeaderStore blockHeaderStore model.BlockHeaderStore
headerTipsStore model.HeaderTipsStore headersSelectedTipStore model.HeaderSelectedTipStore
finalityStore model.FinalityStore finalityStore model.FinalityStore
stores []model.Store stores []model.Store
} }
@ -54,7 +54,7 @@ func New(
pastMedianTimeManager model.PastMedianTimeManager, pastMedianTimeManager model.PastMedianTimeManager,
ghostdagManager model.GHOSTDAGManager, ghostdagManager model.GHOSTDAGManager,
coinbaseManager model.CoinbaseManager, coinbaseManager model.CoinbaseManager,
headerTipsManager model.HeaderTipsManager, headerTipsManager model.HeadersSelectedTipManager,
syncManager model.SyncManager, syncManager model.SyncManager,
acceptanceDataStore model.AcceptanceDataStore, acceptanceDataStore model.AcceptanceDataStore,
@ -68,7 +68,7 @@ func New(
reachabilityDataStore model.ReachabilityDataStore, reachabilityDataStore model.ReachabilityDataStore,
utxoDiffStore model.UTXODiffStore, utxoDiffStore model.UTXODiffStore,
blockHeaderStore model.BlockHeaderStore, blockHeaderStore model.BlockHeaderStore,
headerTipsStore model.HeaderTipsStore, headersSelectedTipStore model.HeaderSelectedTipStore,
finalityStore model.FinalityStore, finalityStore model.FinalityStore,
) model.BlockProcessor { ) model.BlockProcessor {
@ -86,20 +86,20 @@ func New(
headerTipsManager: headerTipsManager, headerTipsManager: headerTipsManager,
syncManager: syncManager, syncManager: syncManager,
consensusStateManager: consensusStateManager, consensusStateManager: consensusStateManager,
acceptanceDataStore: acceptanceDataStore, acceptanceDataStore: acceptanceDataStore,
blockStore: blockStore, blockStore: blockStore,
blockStatusStore: blockStatusStore, blockStatusStore: blockStatusStore,
blockRelationStore: blockRelationStore, blockRelationStore: blockRelationStore,
multisetStore: multisetStore, multisetStore: multisetStore,
ghostdagDataStore: ghostdagDataStore, ghostdagDataStore: ghostdagDataStore,
consensusStateStore: consensusStateStore, consensusStateStore: consensusStateStore,
pruningStore: pruningStore, pruningStore: pruningStore,
reachabilityDataStore: reachabilityDataStore, reachabilityDataStore: reachabilityDataStore,
utxoDiffStore: utxoDiffStore, utxoDiffStore: utxoDiffStore,
blockHeaderStore: blockHeaderStore, blockHeaderStore: blockHeaderStore,
headerTipsStore: headerTipsStore, headersSelectedTipStore: headersSelectedTipStore,
finalityStore: finalityStore, finalityStore: finalityStore,
stores: []model.Store{ stores: []model.Store{
consensusStateStore, consensusStateStore,
@ -114,7 +114,7 @@ func New(
reachabilityDataStore, reachabilityDataStore,
utxoDiffStore, utxoDiffStore,
blockHeaderStore, blockHeaderStore,
headerTipsStore, headersSelectedTipStore,
finalityStore, finalityStore,
}, },
} }
@ -128,3 +128,10 @@ func (bp *blockProcessor) ValidateAndInsertBlock(block *externalapi.DomainBlock)
return bp.validateAndInsertBlock(block) 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)
}

View File

@ -11,36 +11,16 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
) )
type insertMode uint8
const (
insertModeGenesis insertMode = iota
insertModeHeader
insertModeBlockBody
insertModeBlock
)
func (bp *blockProcessor) validateAndInsertBlock(block *externalapi.DomainBlock) error { func (bp *blockProcessor) validateAndInsertBlock(block *externalapi.DomainBlock) error {
blockHash := consensushashing.HeaderHash(block.Header) blockHash := consensushashing.HeaderHash(block.Header)
log.Debugf("Validating block %s", blockHash) err := bp.validateBlock(block)
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)
if err != nil { if err != nil {
bp.discardAllChanges() bp.discardAllChanges()
return err return err
} }
if insertMode == insertModeHeader { isHeaderOnlyBlock := isHeaderOnlyBlock(block)
if isHeaderOnlyBlock {
bp.blockStatusStore.Stage(blockHash, externalapi.StatusHeaderOnly) bp.blockStatusStore.Stage(blockHash, externalapi.StatusHeaderOnly)
} else { } else {
bp.blockStatusStore.Stage(blockHash, externalapi.StatusUTXOPendingVerification) bp.blockStatusStore.Stage(blockHash, externalapi.StatusUTXOPendingVerification)
@ -54,43 +34,38 @@ func (bp *blockProcessor) validateAndInsertBlock(block *externalapi.DomainBlock)
} }
var oldHeadersSelectedTip *externalapi.DomainHash var oldHeadersSelectedTip *externalapi.DomainHash
if insertMode != insertModeGenesis { isGenesis := *blockHash != *bp.genesisHash
if isGenesis {
var err error var err error
oldHeadersSelectedTip, err = bp.headerTipsManager.SelectedTip() oldHeadersSelectedTip, err = bp.headersSelectedTipStore.HeadersSelectedTip(bp.databaseContext)
if err != nil { if err != nil {
return err return err
} }
} }
if insertMode == insertModeHeader { err = bp.headerTipsManager.AddHeaderTip(blockHash)
err = bp.headerTipsManager.AddHeaderTip(blockHash) if err != nil {
if err != nil { return err
return err }
}
} else if insertMode == insertModeBlock || insertMode == insertModeGenesis { if !isHeaderOnlyBlock {
// Attempt to add the block to the virtual // Attempt to add the block to the virtual
err = bp.consensusStateManager.AddBlockToVirtual(blockHash) err = bp.consensusStateManager.AddBlock(blockHash)
if err != nil { if err != nil {
return err 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) err := bp.updateReachabilityReindexRoot(oldHeadersSelectedTip)
if err != nil { if err != nil {
return err return err
} }
} }
if insertMode == insertModeBlock { if !isHeaderOnlyBlock {
// Trigger pruning, which will check if the pruning point changed and delete the data if it did. // 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 { if err != nil {
return err return err
} }
@ -115,8 +90,8 @@ func (bp *blockProcessor) validateAndInsertBlock(block *externalapi.DomainBlock)
logClosureErr = err logClosureErr = err
return fmt.Sprintf("Failed to get sync info: %s", 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", return fmt.Sprintf("New virtual's blue score: %d. Is awaiting UTXO set: %t. Block count: %d. Header count: %d",
virtualGhostDAGData.BlueScore(), syncInfo.State, syncInfo.BlockCount, syncInfo.HeaderCount) virtualGhostDAGData.BlueScore(), syncInfo.IsAwaitingUTXOSet, syncInfo.BlockCount, syncInfo.HeaderCount)
})) }))
if logClosureErr != nil { if logClosureErr != nil {
return logClosureErr return logClosureErr
@ -125,64 +100,12 @@ func (bp *blockProcessor) validateAndInsertBlock(block *externalapi.DomainBlock)
return nil 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 { func isHeaderOnlyBlock(block *externalapi.DomainBlock) bool {
return len(block.Transactions) == 0 return len(block.Transactions) == 0
} }
func (bp *blockProcessor) updateReachabilityReindexRoot(oldHeadersSelectedTip *externalapi.DomainHash) error { func (bp *blockProcessor) updateReachabilityReindexRoot(oldHeadersSelectedTip *externalapi.DomainHash) error {
headersSelectedTip, err := bp.headerTipsManager.SelectedTip() headersSelectedTip, err := bp.headersSelectedTipStore.HeadersSelectedTip(bp.databaseContext)
if err != nil { if err != nil {
return err return err
} }
@ -194,7 +117,9 @@ func (bp *blockProcessor) updateReachabilityReindexRoot(oldHeadersSelectedTip *e
return bp.reachabilityManager.UpdateReindexRoot(headersSelectedTip) 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) exists, err := bp.blockStatusStore.Exists(bp.databaseContext, hash)
if err != nil { if err != nil {
return err 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) 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 { if !isBlockBodyAfterBlockHeader {
return errors.Wrapf(ruleerrors.ErrDuplicateBlock, "block %s already exists", hash) return errors.Wrapf(ruleerrors.ErrDuplicateBlock, "block %s already exists", hash)
} }
isDuplicateHeader := mode == insertModeHeader && status == externalapi.StatusHeaderOnly isDuplicateHeader := isHeaderOnlyBlock && status == externalapi.StatusHeaderOnly
if isDuplicateHeader { if isDuplicateHeader {
return errors.Wrapf(ruleerrors.ErrDuplicateBlock, "block %s already exists", hash) 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 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 { func (bp *blockProcessor) validatePreProofOfWork(block *externalapi.DomainBlock) error {
blockHash := consensushashing.BlockHash(block) blockHash := consensushashing.BlockHash(block)
hasHeader, err := bp.hasHeader(blockHash) hasValidatedOnlyHeader, err := bp.hasValidatedOnlyHeader(blockHash)
if err != nil { if err != nil {
return err return err
} }
if hasHeader { if hasValidatedOnlyHeader {
log.Debugf("Block %s header was already validated, so skip the rest of validatePreProofOfWork", blockHash)
return nil return nil
} }
@ -286,41 +170,43 @@ func (bp *blockProcessor) validatePreProofOfWork(block *externalapi.DomainBlock)
return nil return nil
} }
func (bp *blockProcessor) validatePruningPointViolationAndProofOfWorkAndDifficulty(block *externalapi.DomainBlock, mode insertMode) error { func (bp *blockProcessor) validatePostProofOfWork(block *externalapi.DomainBlock) 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 {
blockHash := consensushashing.BlockHash(block) blockHash := consensushashing.BlockHash(block)
if mode != insertModeHeader { isHeaderOnlyBlock := isHeaderOnlyBlock(block)
if !isHeaderOnlyBlock {
bp.blockStore.Stage(blockHash, block)
err := bp.blockValidator.ValidateBodyInIsolation(blockHash) err := bp.blockValidator.ValidateBodyInIsolation(blockHash)
if err != nil { if err != nil {
return err return err
} }
} }
hasHeader, err := bp.hasHeader(blockHash) hasValidatedHeader, err := bp.hasValidatedOnlyHeader(blockHash)
if err != nil { if err != nil {
return err return err
} }
if !hasHeader { if !hasValidatedHeader {
err = bp.blockValidator.ValidateHeaderInContext(blockHash) err = bp.blockValidator.ValidateHeaderInContext(blockHash)
if err != nil { if err != nil {
return err 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 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) exists, err := bp.blockStatusStore.Exists(bp.databaseContext, blockHash)
if err != nil { if err != nil {
return false, err return false, err

View File

@ -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)
}

View File

@ -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
}

View File

@ -1,6 +1,7 @@
package blockvalidator package blockvalidator
import ( import (
"github.com/kaspanet/kaspad/infrastructure/logger"
"math" "math"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi" "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
@ -13,6 +14,9 @@ import (
// ValidateBodyInContext validates block bodies in the context of the current // ValidateBodyInContext validates block bodies in the context of the current
// consensus state // consensus state
func (v *blockValidator) ValidateBodyInContext(blockHash *externalapi.DomainHash) error { func (v *blockValidator) ValidateBodyInContext(blockHash *externalapi.DomainHash) error {
onEnd := logger.LogAndMeasureExecutionTime(log, "ValidateBodyInContext")
defer onEnd()
return v.checkBlockTransactionsFinalized(blockHash) return v.checkBlockTransactionsFinalized(blockHash)
} }

View File

@ -8,12 +8,16 @@ import (
"github.com/kaspanet/kaspad/domain/consensus/utils/merkle" "github.com/kaspanet/kaspad/domain/consensus/utils/merkle"
"github.com/kaspanet/kaspad/domain/consensus/utils/subnetworks" "github.com/kaspanet/kaspad/domain/consensus/utils/subnetworks"
"github.com/kaspanet/kaspad/domain/consensus/utils/transactionhelper" "github.com/kaspanet/kaspad/domain/consensus/utils/transactionhelper"
"github.com/kaspanet/kaspad/infrastructure/logger"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
// ValidateBodyInIsolation validates block bodies in isolation from the current // ValidateBodyInIsolation validates block bodies in isolation from the current
// consensus state // consensus state
func (v *blockValidator) ValidateBodyInIsolation(blockHash *externalapi.DomainHash) error { func (v *blockValidator) ValidateBodyInIsolation(blockHash *externalapi.DomainHash) error {
onEnd := logger.LogAndMeasureExecutionTime(log, "ValidateBodyInContext")
defer onEnd()
block, err := v.blockStore.Block(v.databaseContext, blockHash) block, err := v.blockStore.Block(v.databaseContext, blockHash)
if err != nil { if err != nil {
return err return err

View File

@ -4,23 +4,27 @@ import (
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi" "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors" "github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing" "github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
"github.com/kaspanet/kaspad/infrastructure/logger"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
// ValidateHeaderInContext validates block headers in the context of the current // ValidateHeaderInContext validates block headers in the context of the current
// consensus state // consensus state
func (v *blockValidator) ValidateHeaderInContext(blockHash *externalapi.DomainHash) error { func (v *blockValidator) ValidateHeaderInContext(blockHash *externalapi.DomainHash) error {
onEnd := logger.LogAndMeasureExecutionTime(log, "ValidateHeaderInContext")
defer onEnd()
header, err := v.blockHeaderStore.BlockHeader(v.databaseContext, blockHash) header, err := v.blockHeaderStore.BlockHeader(v.databaseContext, blockHash)
if err != nil { if err != nil {
return err return err
} }
isHeadersOnlyBlock, err := v.isHeadersOnlyBlock(blockHash) hasValidatedHeader, err := v.hasValidatedHeader(blockHash)
if err != nil { if err != nil {
return err return err
} }
if !isHeadersOnlyBlock { if !hasValidatedHeader {
err = v.ghostdagManager.GHOSTDAG(blockHash) err = v.ghostdagManager.GHOSTDAG(blockHash)
if err != nil { if err != nil {
return err return err
@ -60,7 +64,7 @@ func (v *blockValidator) ValidateHeaderInContext(blockHash *externalapi.DomainHa
return nil 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) exists, err := v.blockStatusStore.Exists(v.databaseContext, blockHash)
if err != nil { if err != nil {
return false, err return false, err

View File

@ -4,6 +4,7 @@ import (
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi" "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors" "github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing" "github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
"github.com/kaspanet/kaspad/infrastructure/logger"
"github.com/kaspanet/kaspad/util/mstime" "github.com/kaspanet/kaspad/util/mstime"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -11,6 +12,9 @@ import (
// ValidateHeaderInIsolation validates block headers in isolation from the current // ValidateHeaderInIsolation validates block headers in isolation from the current
// consensus state // consensus state
func (v *blockValidator) ValidateHeaderInIsolation(blockHash *externalapi.DomainHash) error { func (v *blockValidator) ValidateHeaderInIsolation(blockHash *externalapi.DomainHash) error {
onEnd := logger.LogAndMeasureExecutionTime(log, "ValidateHeaderInIsolation")
defer onEnd()
header, err := v.blockHeaderStore.BlockHeader(v.databaseContext, blockHash) header, err := v.blockHeaderStore.BlockHeader(v.databaseContext, blockHash)
if err != nil { if err != nil {
return err return err

View File

@ -0,0 +1,7 @@
package blockvalidator
import (
"github.com/kaspanet/kaspad/infrastructure/logger"
)
var log, _ = logger.Get(logger.SubsystemTags.BLVL)

View File

@ -5,11 +5,15 @@ import (
"github.com/kaspanet/kaspad/domain/consensus/model/pow" "github.com/kaspanet/kaspad/domain/consensus/model/pow"
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors" "github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing" "github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
"github.com/kaspanet/kaspad/infrastructure/logger"
"github.com/kaspanet/kaspad/util" "github.com/kaspanet/kaspad/util"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
func (v *blockValidator) ValidatePruningPointViolationAndProofOfWorkAndDifficulty(blockHash *externalapi.DomainHash) error { func (v *blockValidator) ValidatePruningPointViolationAndProofOfWorkAndDifficulty(blockHash *externalapi.DomainHash) error {
onEnd := logger.LogAndMeasureExecutionTime(log, "ValidatePruningPointViolationAndProofOfWorkAndDifficulty")
defer onEnd()
header, err := v.blockHeaderStore.BlockHeader(v.databaseContext, blockHash) header, err := v.blockHeaderStore.BlockHeader(v.databaseContext, blockHash)
if err != nil { if err != nil {
return err return err
@ -102,7 +106,7 @@ func (v *blockValidator) checkProofOfWork(header *externalapi.DomainBlockHeader)
func (v *blockValidator) checkParentsExist(blockHash *externalapi.DomainHash, header *externalapi.DomainBlockHeader) error { func (v *blockValidator) checkParentsExist(blockHash *externalapi.DomainHash, header *externalapi.DomainBlockHeader) error {
missingParentHashes := []*externalapi.DomainHash{} missingParentHashes := []*externalapi.DomainHash{}
isFullBlock, err := v.blockStore.HasBlock(v.databaseContext, blockHash) hasBlockBody, err := v.blockStore.HasBlock(v.databaseContext, blockHash)
if err != nil { if err != nil {
return err return err
} }
@ -117,12 +121,31 @@ func (v *blockValidator) checkParentsExist(blockHash *externalapi.DomainHash, he
continue continue
} }
if isFullBlock { parentStatus, err := v.blockStatusStore.Get(v.databaseContext, parent)
parentStatus, err := v.blockStatusStore.Get(v.databaseContext, parent) if err != nil {
if err != nil { return err
return err }
}
if parentStatus == externalapi.StatusInvalid {
return errors.Wrapf(ruleerrors.ErrInvalidAncestorBlock, "parent %s is invalid", parent)
}
if hasBlockBody {
if parentStatus == externalapi.StatusHeaderOnly { 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) missingParentHashes = append(missingParentHashes, parent)
} }
} }

View File

@ -8,41 +8,41 @@ import (
// AddBlockToVirtual submits the given block to be added to the // AddBlockToVirtual submits the given block to be added to the
// current virtual. This process may result in a new virtual block // current virtual. This process may result in a new virtual block
// getting created // getting created
func (csm *consensusStateManager) AddBlockToVirtual(blockHash *externalapi.DomainHash) error { func (csm *consensusStateManager) AddBlock(blockHash *externalapi.DomainHash) error {
log.Tracef("AddBlockToVirtual start for block %s", blockHash) log.Tracef("AddBlock start for block %s", blockHash)
defer log.Tracef("AddBlockToVirtual end 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) 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 { if err != nil {
return err return err
} }
if isNextVirtualSelectedParent { if isCandidateToBeNextVirtualSelectedParent {
log.Tracef("Block %s is the new virtual. Resolving its block status", blockHash) // It's important to check for finality violation before resolving the block status, because the status of
blockStatus, err := csm.resolveBlockStatus(blockHash) // 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 { if err != nil {
return err return err
} }
if blockStatus == externalapi.StatusValid { if shouldNotify {
log.Tracef("Block %s is tentatively valid. Resolving whether it violates finality", blockHash) //TODO: Send finality conflict notification
err = csm.checkFinalityViolation(blockHash) log.Warnf("Finality Violation Detected! Block %s violates finality!", 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
}
} }
log.Debugf("Block %s is the next virtual selected parent. "+ if !isViolatingFinality {
"Its resolved status is `%s`", blockHash, blockStatus) 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 { } else {
log.Debugf("Block %s is not the next virtual selected parent, "+ log.Debugf("Block %s is not the next virtual selected parent, "+
"therefore its status remains `%s`", blockHash, externalapi.StatusUTXOPendingVerification) "therefore its status remains `%s`", blockHash, externalapi.StatusUTXOPendingVerification)
@ -64,9 +64,9 @@ func (csm *consensusStateManager) AddBlockToVirtual(blockHash *externalapi.Domai
return nil return nil
} }
func (csm *consensusStateManager) isNextVirtualSelectedParent(blockHash *externalapi.DomainHash) (bool, error) { func (csm *consensusStateManager) isCandidateToBeNextVirtualSelectedParent(blockHash *externalapi.DomainHash) (bool, error) {
log.Tracef("isNextVirtualSelectedParent start for block %s", blockHash) log.Tracef("isCandidateToBeNextVirtualSelectedParent start for block %s", blockHash)
defer log.Tracef("isNextVirtualSelectedParent end for block %s", blockHash) defer log.Tracef("isCandidateToBeNextVirtualSelectedParent end for block %s", blockHash)
if *blockHash == *csm.genesisHash { if *blockHash == *csm.genesisHash {
log.Tracef("Block %s is the genesis block, therefore it is "+ log.Tracef("Block %s is the genesis block, therefore it is "+

View File

@ -2,23 +2,65 @@ package consensusstatemanager
import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
func (csm *consensusStateManager) checkFinalityViolation( func (csm *consensusStateManager) isViolatingFinality(blockHash *externalapi.DomainHash) (isViolatingFinality bool,
blockHash *externalapi.DomainHash) error { shouldSendNotification bool, err error) {
log.Tracef("checkFinalityViolation start for block %s", blockHash) log.Tracef("isViolatingFinality start for block %s", blockHash)
defer log.Tracef("checkFinalityViolation end for block %s", blockHash) defer log.Tracef("isViolatingFinality end for block %s", blockHash)
isViolatingFinality, err := csm.finalityManager.IsViolatingFinality(blockHash) if *blockHash == *csm.genesisHash {
if err != nil { log.Tracef("Block %s is the genesis block, "+
return err "and does not violate finality by definition", blockHash)
return false, false, nil
} }
if isViolatingFinality { var finalityPoint *externalapi.DomainHash
csm.blockStatusStore.Stage(blockHash, externalapi.StatusUTXOPendingVerification) virtualFinalityPoint, err := csm.finalityManager.VirtualFinalityPoint()
log.Warnf("Finality Violation Detected! Block %s violates finality!", blockHash) if err != nil {
return 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) log.Tracef("Block %s does not violate finality", blockHash)
return nil return false, false, nil
} }

View File

@ -25,16 +25,17 @@ type consensusStateManager struct {
mergeDepthManager model.MergeDepthManager mergeDepthManager model.MergeDepthManager
finalityManager model.FinalityManager finalityManager model.FinalityManager
headerTipsStore model.HeaderTipsStore headersSelectedTipStore model.HeaderSelectedTipStore
blockStatusStore model.BlockStatusStore blockStatusStore model.BlockStatusStore
ghostdagDataStore model.GHOSTDAGDataStore ghostdagDataStore model.GHOSTDAGDataStore
consensusStateStore model.ConsensusStateStore consensusStateStore model.ConsensusStateStore
multisetStore model.MultisetStore multisetStore model.MultisetStore
blockStore model.BlockStore blockStore model.BlockStore
utxoDiffStore model.UTXODiffStore utxoDiffStore model.UTXODiffStore
blockRelationStore model.BlockRelationStore blockRelationStore model.BlockRelationStore
acceptanceDataStore model.AcceptanceDataStore acceptanceDataStore model.AcceptanceDataStore
blockHeaderStore model.BlockHeaderStore blockHeaderStore model.BlockHeaderStore
pruningStore model.PruningStore
stores []model.Store stores []model.Store
} }
@ -68,7 +69,8 @@ func New(
blockRelationStore model.BlockRelationStore, blockRelationStore model.BlockRelationStore,
acceptanceDataStore model.AcceptanceDataStore, acceptanceDataStore model.AcceptanceDataStore,
blockHeaderStore model.BlockHeaderStore, blockHeaderStore model.BlockHeaderStore,
headerTipsStore model.HeaderTipsStore) (model.ConsensusStateManager, error) { headersSelectedTipStore model.HeaderSelectedTipStore,
pruningStore model.PruningStore) (model.ConsensusStateManager, error) {
csm := &consensusStateManager{ csm := &consensusStateManager{
pruningDepth: pruningDepth, pruningDepth: pruningDepth,
@ -89,16 +91,17 @@ func New(
mergeDepthManager: mergeDepthManager, mergeDepthManager: mergeDepthManager,
finalityManager: finalityManager, finalityManager: finalityManager,
multisetStore: multisetStore, multisetStore: multisetStore,
blockStore: blockStore, blockStore: blockStore,
blockStatusStore: blockStatusStore, blockStatusStore: blockStatusStore,
ghostdagDataStore: ghostdagDataStore, ghostdagDataStore: ghostdagDataStore,
consensusStateStore: consensusStateStore, consensusStateStore: consensusStateStore,
utxoDiffStore: utxoDiffStore, utxoDiffStore: utxoDiffStore,
blockRelationStore: blockRelationStore, blockRelationStore: blockRelationStore,
acceptanceDataStore: acceptanceDataStore, acceptanceDataStore: acceptanceDataStore,
blockHeaderStore: blockHeaderStore, blockHeaderStore: blockHeaderStore,
headerTipsStore: headerTipsStore, headersSelectedTipStore: headersSelectedTipStore,
pruningStore: pruningStore,
stores: []model.Store{ stores: []model.Store{
consensusStateStore, consensusStateStore,
@ -111,7 +114,8 @@ func New(
consensusStateStore, consensusStateStore,
utxoDiffStore, utxoDiffStore,
blockHeaderStore, blockHeaderStore,
headerTipsStore, headersSelectedTipStore,
pruningStore,
}, },
} }

View File

@ -5,6 +5,7 @@ import (
"github.com/kaspanet/kaspad/domain/consensus/model" "github.com/kaspanet/kaspad/domain/consensus/model"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi" "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors" "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/serialization"
"github.com/kaspanet/kaspad/domain/consensus/utils/utxo" "github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
"github.com/kaspanet/kaspad/domain/consensus/utils/utxoserialization" "github.com/kaspanet/kaspad/domain/consensus/utils/utxoserialization"
@ -12,18 +13,11 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
) )
var virtualHeaderHash = &externalapi.DomainHash{ func (csm *consensusStateManager) UpdatePruningPoint(newPruningPoint *externalapi.DomainBlock, serializedUTXOSet []byte) error {
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, onEnd := logger.LogAndMeasureExecutionTime(log, "UpdatePruningPoint")
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")
defer onEnd() defer onEnd()
err := csm.setPruningPointUTXOSet(serializedUTXOSet) err := csm.updatePruningPoint(newPruningPoint, serializedUTXOSet)
if err != nil { if err != nil {
csm.discardSetPruningPointUTXOSetChanges() csm.discardSetPruningPointUTXOSetChanges()
return err return err
@ -32,15 +26,23 @@ func (csm *consensusStateManager) SetPruningPointUTXOSet(serializedUTXOSet []byt
return csm.commitSetPruningPointUTXOSetAll() return csm.commitSetPruningPointUTXOSetAll()
} }
func (csm *consensusStateManager) setPruningPointUTXOSet(serializedUTXOSet []byte) error { func (csm *consensusStateManager) updatePruningPoint(newPruningPoint *externalapi.DomainBlock, serializedUTXOSet []byte) error {
log.Tracef("setPruningPointUTXOSet start") log.Tracef("updatePruningPoint start")
defer log.Tracef("setPruningPointUTXOSet end") 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 { if err != nil {
return err 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{} protoUTXOSet := &utxoserialization.ProtoUTXOSet{}
err = proto.Unmarshal(serializedUTXOSet, 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()) 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 { if err != nil {
return err return err
} }
log.Tracef("The multiset in the header of the header tip pruning point: %s", log.Tracef("The UTXO commitment of the pruning point: %s",
headerTipsPruningPointHeader.UTXOCommitment) newPruningPointHeader.UTXOCommitment)
if headerTipsPruningPointHeader.UTXOCommitment != *utxoSetMultiSet.Hash() { if newPruningPointHeader.UTXOCommitment != *utxoSetMultiSet.Hash() {
return errors.Wrapf(ruleerrors.ErrBadPruningPointUTXOSet, "the expected multiset hash of the pruning "+ 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") log.Tracef("Staging the parent hashes for pruning point as the DAG tips")
csm.consensusStateStore.StageTips(headerTipsPruningPointHeader.ParentHashes) csm.consensusStateStore.StageTips(newPruningPointHeader.ParentHashes)
log.Tracef("Setting the parent hashes for the header tips pruning point as the virtual parents") 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 { if err != nil {
return err return err
} }
@ -82,6 +84,13 @@ func (csm *consensusStateManager) setPruningPointUTXOSet(serializedUTXOSet []byt
return err 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) err = csm.ghostdagManager.GHOSTDAG(model.VirtualBlockHash)
if err != nil { if err != nil {
return err return err
@ -93,8 +102,11 @@ func (csm *consensusStateManager) setPruningPointUTXOSet(serializedUTXOSet []byt
return err return err
} }
log.Tracef("Staging the status of the header tips pruning point as %s", externalapi.StatusValid) log.Tracef("Staging the new pruning point and its UTXO set")
csm.blockStatusStore.Stage(headerTipsPruningPoint, externalapi.StatusValid) csm.pruningStore.Stage(newPruningPointHash, serializedUTXOSet)
log.Tracef("Staging the new pruning point as %s", externalapi.StatusValid)
csm.blockStatusStore.Stage(newPruningPointHash, externalapi.StatusValid)
return nil return nil
} }
@ -145,34 +157,3 @@ func (p protoUTXOSetIterator) Get() (outpoint *externalapi.DomainOutpoint, utxoE
func protoUTXOSetToReadOnlyUTXOSetIterator(protoUTXOSet *utxoserialization.ProtoUTXOSet) model.ReadOnlyUTXOSetIterator { func protoUTXOSetToReadOnlyUTXOSetIterator(protoUTXOSet *utxoserialization.ProtoUTXOSet) model.ReadOnlyUTXOSetIterator {
return &protoUTXOSetIterator{utxoSet: protoUTXOSet} 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
}

View File

@ -47,7 +47,7 @@ func (csm *consensusStateManager) verifyUTXO(block *externalapi.DomainBlock, blo
log.Tracef("Coinbase transaction validation passed for block %s", blockHash) log.Tracef("Coinbase transaction validation passed for block %s", blockHash)
log.Tracef("Validating transactions against past UTXO 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 { if err != nil {
return err return err
} }
@ -57,8 +57,9 @@ func (csm *consensusStateManager) verifyUTXO(block *externalapi.DomainBlock, blo
} }
func (csm *consensusStateManager) validateBlockTransactionsAgainstPastUTXO(block *externalapi.DomainBlock, 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) log.Tracef("validateBlockTransactionsAgainstPastUTXO start for block %s", blockHash)
defer log.Tracef("validateBlockTransactionsAgainstPastUTXO end for block %s", blockHash) defer log.Tracef("validateBlockTransactionsAgainstPastUTXO end for block %s", blockHash)

View File

@ -2,9 +2,9 @@ package dagtraversalmanager
import ( import (
"fmt" "fmt"
"github.com/kaspanet/kaspad/domain/consensus/model" "github.com/kaspanet/kaspad/domain/consensus/model"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi" "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/pkg/errors"
) )
// dagTraversalManager exposes methods for travering blocks // dagTraversalManager exposes methods for travering blocks
@ -100,6 +100,11 @@ func (dtm *dagTraversalManager) LowestChainBlockAboveOrEqualToBlueScore(highHash
return nil, err 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 currentHash := highHash
currentBlockGHOSTDAGData := highBlockGHOSTDAGData currentBlockGHOSTDAGData := highBlockGHOSTDAGData
iterator := dtm.SelectedParentIterator(highHash) iterator := dtm.SelectedParentIterator(highHash)
@ -112,7 +117,7 @@ func (dtm *dagTraversalManager) LowestChainBlockAboveOrEqualToBlueScore(highHash
if selectedParentBlockGHOSTDAGData.BlueScore() < blueScore { if selectedParentBlockGHOSTDAGData.BlueScore() < blueScore {
break break
} }
currentHash = selectedParentBlockGHOSTDAGData.SelectedParent() currentHash = currentBlockGHOSTDAGData.SelectedParent()
currentBlockGHOSTDAGData = selectedParentBlockGHOSTDAGData currentBlockGHOSTDAGData = selectedParentBlockGHOSTDAGData
} }

View File

@ -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) { func (fm *finalityManager) VirtualFinalityPoint() (*externalapi.DomainHash, error) {
log.Tracef("virtualFinalityPoint start") log.Tracef("virtualFinalityPoint start")
defer log.Tracef("virtualFinalityPoint end") defer log.Tracef("virtualFinalityPoint end")

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -0,0 +1,5 @@
package pruningmanager
import "github.com/kaspanet/kaspad/infrastructure/logger"
var log, _ = logger.Get(logger.SubsystemTags.PRNM)

View File

@ -11,13 +11,14 @@ import (
type pruningManager struct { type pruningManager struct {
databaseContext model.DBReader databaseContext model.DBReader
dagTraversalManager model.DAGTraversalManager dagTraversalManager model.DAGTraversalManager
dagTopologyManager model.DAGTopologyManager dagTopologyManager model.DAGTopologyManager
consensusStateManager model.ConsensusStateManager consensusStateManager model.ConsensusStateManager
consensusStateStore model.ConsensusStateStore consensusStateStore model.ConsensusStateStore
ghostdagDataStore model.GHOSTDAGDataStore ghostdagDataStore model.GHOSTDAGDataStore
pruningStore model.PruningStore pruningStore model.PruningStore
blockStatusStore model.BlockStatusStore blockStatusStore model.BlockStatusStore
headerSelectedTipStore model.HeaderSelectedTipStore
multiSetStore model.MultisetStore multiSetStore model.MultisetStore
acceptanceDataStore model.AcceptanceDataStore acceptanceDataStore model.AcceptanceDataStore
@ -40,6 +41,7 @@ func New(
ghostdagDataStore model.GHOSTDAGDataStore, ghostdagDataStore model.GHOSTDAGDataStore,
pruningStore model.PruningStore, pruningStore model.PruningStore,
blockStatusStore model.BlockStatusStore, blockStatusStore model.BlockStatusStore,
headerSelectedTipStore model.HeaderSelectedTipStore,
multiSetStore model.MultisetStore, multiSetStore model.MultisetStore,
acceptanceDataStore model.AcceptanceDataStore, acceptanceDataStore model.AcceptanceDataStore,
@ -52,27 +54,28 @@ func New(
) model.PruningManager { ) model.PruningManager {
return &pruningManager{ return &pruningManager{
databaseContext: databaseContext, databaseContext: databaseContext,
dagTraversalManager: dagTraversalManager, dagTraversalManager: dagTraversalManager,
dagTopologyManager: dagTopologyManager, dagTopologyManager: dagTopologyManager,
consensusStateManager: consensusStateManager, consensusStateManager: consensusStateManager,
consensusStateStore: consensusStateStore, consensusStateStore: consensusStateStore,
ghostdagDataStore: ghostdagDataStore, ghostdagDataStore: ghostdagDataStore,
pruningStore: pruningStore, pruningStore: pruningStore,
blockStatusStore: blockStatusStore, blockStatusStore: blockStatusStore,
multiSetStore: multiSetStore, multiSetStore: multiSetStore,
acceptanceDataStore: acceptanceDataStore, acceptanceDataStore: acceptanceDataStore,
blocksStore: blocksStore, blocksStore: blocksStore,
utxoDiffStore: utxoDiffStore, utxoDiffStore: utxoDiffStore,
genesisHash: genesisHash, headerSelectedTipStore: headerSelectedTipStore,
pruningDepth: pruningDepth, genesisHash: genesisHash,
finalityInterval: finalityInterval, pruningDepth: pruningDepth,
finalityInterval: finalityInterval,
} }
} }
// FindNextPruningPoint finds the next pruning point from the // FindNextPruningPoint finds the next pruning point from the
// given blockHash // given blockHash
func (pm *pruningManager) FindNextPruningPoint() error { func (pm *pruningManager) UpdatePruningPointByVirtual() error {
hasPruningPoint, err := pm.pruningStore.HasPruningPoint(pm.databaseContext) hasPruningPoint, err := pm.pruningStore.HasPruningPoint(pm.databaseContext)
if err != nil { if err != nil {
return err return err
@ -95,40 +98,41 @@ func (pm *pruningManager) FindNextPruningPoint() error {
return err return err
} }
virtualSelectedParent, err := pm.ghostdagDataStore.Get(pm.databaseContext, virtual.SelectedParent())
if err != nil {
return err
}
currentPGhost, err := pm.ghostdagDataStore.Get(pm.databaseContext, currentP) currentPGhost, err := pm.ghostdagDataStore.Get(pm.databaseContext, currentP)
if err != nil { if err != nil {
return err return err
} }
currentPBlueScore := currentPGhost.BlueScore() 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. // 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 return nil
} }
// This means the pruning point is still genesis. // This means the pruning point is still genesis.
if virtual.BlueScore() <= pm.pruningDepth+pm.finalityInterval { if virtualSelectedParent.BlueScore() <= pm.pruningDepth+pm.finalityInterval {
return nil return nil
} }
// get Virtual(pruningDepth) // get Virtual(pruningDepth)
candidatePHash, err := pm.dagTraversalManager.BlockAtDepth(model.VirtualBlockHash, pm.pruningDepth) newPruningPoint, err := pm.calculatePruningPointFromBlock(model.VirtualBlockHash)
if err != nil {
return err
}
candidatePGhost, err := pm.ghostdagDataStore.Get(pm.databaseContext, candidatePHash)
if err != nil { if err != nil {
return err return err
} }
// Actually check if the pruning point changed if *newPruningPoint != *currentP {
if (currentPBlueScore / pm.finalityInterval) < (candidatePGhost.BlueScore() / pm.finalityInterval) { err = pm.savePruningPoint(newPruningPoint)
err = pm.savePruningPoint(candidatePHash)
if err != nil { if err != nil {
return err return err
} }
return pm.deletePastBlocks(candidatePHash) return pm.deletePastBlocks(newPruningPoint)
} }
return pm.deletePastBlocks(currentP)
return nil
} }
func (pm *pruningManager) deletePastBlocks(pruningPoint *externalapi.DomainHash) error { func (pm *pruningManager) deletePastBlocks(pruningPoint *externalapi.DomainHash) error {
@ -233,6 +237,30 @@ func (pm *pruningManager) deleteBlock(blockHash *externalapi.DomainHash) (alread
return false, nil 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) { func serializeUTXOSetIterator(iter model.ReadOnlyUTXOSetIterator) ([]byte, error) {
serializedUtxo, err := utxoserialization.ReadOnlyUTXOSetToProtoUTXOSet(iter) serializedUtxo, err := utxoserialization.ReadOnlyUTXOSetToProtoUTXOSet(iter)
if err != nil { if err != nil {

View File

@ -159,7 +159,7 @@ func TestReindexIntervalsEarlierThanReindexRoot(t *testing.T) {
factory := consensus.NewFactory() factory := consensus.NewFactory()
tc, tearDown, err := factory.NewTestConsensus(params, "TestUpdateReindexRoot") tc, tearDown, err := factory.NewTestConsensus(params, "TestUpdateReindexRoot")
if err != nil { if err != nil {
t.Fatalf("NewTestConsensus: %s", err) t.Fatalf("NewTestConsensus: %+v", err)
} }
defer tearDown() defer tearDown()

View File

@ -99,26 +99,26 @@ func (sm *syncManager) antiPastHashesBetween(lowHash, highHash *externalapi.Doma
} }
func (sm *syncManager) missingBlockBodyHashes(highHash *externalapi.DomainHash) ([]*externalapi.DomainHash, error) { 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 { if err != nil {
return nil, err return nil, err
} }
selectedChildIterator, err := sm.dagTraversalManager.SelectedChildIterator(highHash, headerTipsPruningPoint) selectedChildIterator, err := sm.dagTraversalManager.SelectedChildIterator(highHash, pruningPoint)
if err != nil { if err != nil {
return nil, err return nil, err
} }
lowHash := headerTipsPruningPoint lowHash := pruningPoint
foundHeaderOnlyBlock := false foundHeaderOnlyBlock := false
for selectedChildIterator.Next() { for selectedChildIterator.Next() {
selectedChild := selectedChildIterator.Get() selectedChild := selectedChildIterator.Get()
selectedChildStatus, err := sm.blockStatusStore.Get(sm.databaseContext, selectedChild) hasBlock, err := sm.blockStore.HasBlock(sm.databaseContext, selectedChild)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if selectedChildStatus == externalapi.StatusHeaderOnly { if !hasBlock {
foundHeaderOnlyBlock = true foundHeaderOnlyBlock = true
break break
} }
@ -167,24 +167,3 @@ func (sm *syncManager) isHeaderOnlyBlock(blockHash *externalapi.DomainHash) (boo
return status == externalapi.StatusHeaderOnly, nil 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)
}

View File

@ -4,84 +4,43 @@ import (
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi" "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) { func (sm *syncManager) syncInfo() (*externalapi.SyncInfo, error) {
syncState, err := sm.resolveSyncState() isAwaitingUTXOSet, ibdRootUTXOBlockHash, err := sm.isAwaitingUTXOSet()
if err != nil { if err != nil {
return nil, err 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() headerCount := sm.getHeaderCount()
blockCount := sm.getBlockCount() blockCount := sm.getBlockCount()
return &externalapi.SyncInfo{ return &externalapi.SyncInfo{
State: syncState, IsAwaitingUTXOSet: isAwaitingUTXOSet,
IBDRootUTXOBlockHash: ibdRootUTXOBlockHash, IBDRootUTXOBlockHash: ibdRootUTXOBlockHash,
HeaderCount: headerCount, HeaderCount: headerCount,
BlockCount: blockCount, BlockCount: blockCount,
}, nil }, nil
} }
func (sm *syncManager) resolveSyncState() (externalapi.SyncState, error) { func (sm *syncManager) isAwaitingUTXOSet() (isAwaitingUTXOSet bool, ibdRootUTXOBlockHash *externalapi.DomainHash,
hasTips, err := sm.headerTipsStore.HasTips(sm.databaseContext) err error) {
pruningPointByHeaders, err := sm.pruningManager.CalculatePruningPointByHeaderSelectedTip()
if err != nil { if err != nil {
return 0, err return false, nil, err
}
if !hasTips {
return externalapi.SyncStateAwaitingGenesis, nil
} }
headerVirtualSelectedParentHash, err := sm.headerVirtualSelectedParentHash() pruningPoint, err := sm.pruningStore.PruningPoint(sm.databaseContext)
if err != nil { if err != nil {
return 0, err return false, nil, err
}
headerVirtualSelectedParentStatus, err := sm.blockStatusStore.Get(sm.databaseContext, headerVirtualSelectedParentHash)
if err != nil {
return 0, err
}
if headerVirtualSelectedParentStatus != externalapi.StatusHeaderOnly {
return externalapi.SyncStateSynced, nil
} }
// Once the header tips are synced, check the status of // If the pruning point by headers is different from the current point
// the pruning point from the point of view of the header // it means we need to request the new pruning point UTXO set.
// tips. We check it against StatusValid (rather than if *pruningPoint != *pruningPointByHeaders {
// StatusHeaderOnly) because once we do receive the return true, pruningPointByHeaders, nil
// 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
} }
return externalapi.SyncStateAwaitingBlockBodies, nil return false, nil, 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...)
} }
func (sm *syncManager) getHeaderCount() uint64 { func (sm *syncManager) getHeaderCount() uint64 {

View File

@ -7,53 +7,50 @@ import (
) )
type syncManager struct { type syncManager struct {
databaseContext model.DBReader databaseContext model.DBReader
genesisBlockHash *externalapi.DomainHash genesisBlockHash *externalapi.DomainHash
targetTimePerBlock int64
dagTraversalManager model.DAGTraversalManager dagTraversalManager model.DAGTraversalManager
dagTopologyManager model.DAGTopologyManager dagTopologyManager model.DAGTopologyManager
ghostdagManager model.GHOSTDAGManager ghostdagManager model.GHOSTDAGManager
consensusStateManager model.ConsensusStateManager pruningManager model.PruningManager
ghostdagDataStore model.GHOSTDAGDataStore ghostdagDataStore model.GHOSTDAGDataStore
blockStatusStore model.BlockStatusStore blockStatusStore model.BlockStatusStore
blockHeaderStore model.BlockHeaderStore blockHeaderStore model.BlockHeaderStore
headerTipsStore model.HeaderTipsStore
blockStore model.BlockStore blockStore model.BlockStore
pruningStore model.PruningStore
} }
// New instantiates a new SyncManager // New instantiates a new SyncManager
func New( func New(
databaseContext model.DBReader, databaseContext model.DBReader,
genesisBlockHash *externalapi.DomainHash, genesisBlockHash *externalapi.DomainHash,
targetTimePerBlock int64,
dagTraversalManager model.DAGTraversalManager, dagTraversalManager model.DAGTraversalManager,
dagTopologyManager model.DAGTopologyManager, dagTopologyManager model.DAGTopologyManager,
ghostdagManager model.GHOSTDAGManager, ghostdagManager model.GHOSTDAGManager,
consensusStateManager model.ConsensusStateManager, pruningManager model.PruningManager,
ghostdagDataStore model.GHOSTDAGDataStore, ghostdagDataStore model.GHOSTDAGDataStore,
blockStatusStore model.BlockStatusStore, blockStatusStore model.BlockStatusStore,
blockHeaderStore model.BlockHeaderStore, blockHeaderStore model.BlockHeaderStore,
headerTipsStore model.HeaderTipsStore, blockStore model.BlockStore,
blockStore model.BlockStore) model.SyncManager { pruningStore model.PruningStore) model.SyncManager {
return &syncManager{ return &syncManager{
databaseContext: databaseContext, databaseContext: databaseContext,
genesisBlockHash: genesisBlockHash, genesisBlockHash: genesisBlockHash,
targetTimePerBlock: targetTimePerBlock,
dagTraversalManager: dagTraversalManager, dagTraversalManager: dagTraversalManager,
dagTopologyManager: dagTopologyManager, dagTopologyManager: dagTopologyManager,
ghostdagManager: ghostdagManager, ghostdagManager: ghostdagManager,
consensusStateManager: consensusStateManager, pruningManager: pruningManager,
ghostdagDataStore: ghostdagDataStore, ghostdagDataStore: ghostdagDataStore,
blockStatusStore: blockStatusStore, blockStatusStore: blockStatusStore,
blockHeaderStore: blockHeaderStore, blockHeaderStore: blockHeaderStore,
headerTipsStore: headerTipsStore,
blockStore: blockStore, blockStore: blockStore,
pruningStore: pruningStore,
} }
} }
@ -71,13 +68,6 @@ func (sm *syncManager) GetMissingBlockBodyHashes(highHash *externalapi.DomainHas
return sm.missingBlockBodyHashes(highHash) 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) { func (sm *syncManager) CreateBlockLocator(lowHash, highHash *externalapi.DomainHash, limit uint32) (externalapi.BlockLocator, error) {
onEnd := logger.LogAndMeasureExecutionTime(log, "CreateBlockLocator") onEnd := logger.LogAndMeasureExecutionTime(log, "CreateBlockLocator")
defer onEnd() defer onEnd()

View File

@ -237,6 +237,8 @@ var (
//ErrBlockIsTooMuchInTheFuture indicates that the block timestamp is too much in the future. //ErrBlockIsTooMuchInTheFuture indicates that the block timestamp is too much in the future.
ErrBlockIsTooMuchInTheFuture = newRuleError("ErrBlockIsTooMuchInTheFuture") ErrBlockIsTooMuchInTheFuture = newRuleError("ErrBlockIsTooMuchInTheFuture")
ErrUnexpectedPruningPoint = newRuleError("ErrUnexpectedPruningPoint")
) )
// RuleError identifies a rule violation. It is used to indicate that // RuleError identifies a rule violation. It is used to indicate that

View File

@ -37,8 +37,8 @@ func (tc *testConsensus) GHOSTDAGDataStore() model.GHOSTDAGDataStore {
return tc.ghostdagDataStore return tc.ghostdagDataStore
} }
func (tc *testConsensus) HeaderTipsStore() model.HeaderTipsStore { func (tc *testConsensus) HeaderTipsStore() model.HeaderSelectedTipStore {
return tc.headerTipsStore return tc.headersSelectedTipStore
} }
func (tc *testConsensus) MultisetStore() model.MultisetStore { func (tc *testConsensus) MultisetStore() model.MultisetStore {
@ -93,7 +93,7 @@ func (tc *testConsensus) GHOSTDAGManager() model.GHOSTDAGManager {
return tc.ghostdagManager return tc.ghostdagManager
} }
func (tc *testConsensus) HeaderTipsManager() model.HeaderTipsManager { func (tc *testConsensus) HeaderTipsManager() model.HeadersSelectedTipManager {
return tc.headerTipsManager return tc.headerTipsManager
} }

View File

@ -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") 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 return nil
} }

View File

@ -54,6 +54,8 @@ var (
snvrLog = BackendLog.Logger("SNVR") snvrLog = BackendLog.Logger("SNVR")
wsvcLog = BackendLog.Logger("WSVC") wsvcLog = BackendLog.Logger("WSVC")
reacLog = BackendLog.Logger("REAC") reacLog = BackendLog.Logger("REAC")
prnmLog = BackendLog.Logger("PRNM")
blvlLog = BackendLog.Logger("BLVL")
) )
// SubsystemTags is an enum of all sub system tags // SubsystemTags is an enum of all sub system tags
@ -85,7 +87,9 @@ var SubsystemTags = struct {
DNSS, DNSS,
SNVR, SNVR,
WSVC, WSVC,
REAC string REAC,
PRNM,
BLVL string
}{ }{
ADXR: "ADXR", ADXR: "ADXR",
AMGR: "AMGR", AMGR: "AMGR",
@ -115,6 +119,8 @@ var SubsystemTags = struct {
SNVR: "SNVR", SNVR: "SNVR",
WSVC: "WSVC", WSVC: "WSVC",
REAC: "REAC", REAC: "REAC",
PRNM: "PRNM",
BLVL: "BLVL",
} }
// subsystemLoggers maps each subsystem identifier to its associated logger. // subsystemLoggers maps each subsystem identifier to its associated logger.
@ -147,6 +153,8 @@ var subsystemLoggers = map[string]*Logger{
SubsystemTags.SNVR: snvrLog, SubsystemTags.SNVR: snvrLog,
SubsystemTags.WSVC: wsvcLog, SubsystemTags.WSVC: wsvcLog,
SubsystemTags.REAC: reacLog, SubsystemTags.REAC: reacLog,
SubsystemTags.PRNM: prnmLog,
SubsystemTags.BLVL: blvlLog,
} }
// InitLog attaches log file and error log file to the backend log. // InitLog attaches log file and error log file to the backend log.