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
}
log.Warnf("Rejected block %s from %s: %s", blockHash, flow.peer, err)
return nil, protocolerrors.Wrapf(true, err, "got invalid block %s from relay", blockHash)
}
return nil, nil

View File

@ -35,7 +35,7 @@ func (flow *handleRelayInvsFlow) runIBDIfNotRunning(highHash *externalapi.Domain
if err != nil {
return err
}
if syncInfo.State == externalapi.SyncStateAwaitingUTXOSet {
if syncInfo.IsAwaitingUTXOSet {
found, err := flow.fetchMissingUTXOSet(syncInfo.IBDRootUTXOBlockHash)
if err != nil {
return err
@ -190,8 +190,8 @@ func (flow *handleRelayInvsFlow) processHeader(msgBlockHeader *appmessage.MsgBlo
return nil
}
func (flow *handleRelayInvsFlow) fetchMissingUTXOSet(ibdRootHash *externalapi.DomainHash) (bool, error) {
err := flow.outgoingRoute.Enqueue(appmessage.NewMsgRequestIBDRootUTXOSetAndBlock(ibdRootHash))
func (flow *handleRelayInvsFlow) fetchMissingUTXOSet(ibdRootHash *externalapi.DomainHash) (succeed bool, err error) {
err = flow.outgoingRoute.Enqueue(appmessage.NewMsgRequestIBDRootUTXOSetAndBlock(ibdRootHash))
if err != nil {
return false, err
}
@ -205,17 +205,23 @@ func (flow *handleRelayInvsFlow) fetchMissingUTXOSet(ibdRootHash *externalapi.Do
return false, nil
}
err = flow.Domain().Consensus().ValidateAndInsertBlock(block)
if err != nil {
blockHash := consensushashing.BlockHash(block)
return false, protocolerrors.ConvertToBanningProtocolErrorIfRuleError(err, "got invalid block %s during IBD", blockHash)
}
err = flow.Domain().Consensus().SetPruningPointUTXOSet(utxoSet)
err = flow.Domain().Consensus().ValidateAndInsertPruningPoint(block, utxoSet)
if err != nil {
return false, protocolerrors.ConvertToBanningProtocolErrorIfRuleError(err, "error with IBD root UTXO set")
}
syncInfo, err := flow.Domain().Consensus().GetSyncInfo()
if err != nil {
return false, err
}
// TODO: Find a better way to deal with finality conflicts.
if syncInfo.IsAwaitingUTXOSet {
log.Warnf("Still awaiting for UTXO set. This can happen only because the given pruning point violates " +
"finality. If this keeps happening delete the data directory and restart your node.")
return false, nil
}
return true, nil
}

View File

@ -25,7 +25,7 @@ type consensus struct {
dagTraversalManager model.DAGTraversalManager
difficultyManager model.DifficultyManager
ghostdagManager model.GHOSTDAGManager
headerTipsManager model.HeaderTipsManager
headerTipsManager model.HeadersSelectedTipManager
mergeDepthManager model.MergeDepthManager
pruningManager model.PruningManager
reachabilityManager model.ReachabilityManager
@ -39,7 +39,7 @@ type consensus struct {
blockRelationStore model.BlockRelationStore
blockStatusStore model.BlockStatusStore
consensusStateStore model.ConsensusStateStore
headerTipsStore model.HeaderTipsStore
headersSelectedTipStore model.HeaderSelectedTipStore
multisetStore model.MultisetStore
reachabilityDataStore model.ReachabilityDataStore
utxoDiffStore model.UTXODiffStore
@ -138,12 +138,6 @@ func (s *consensus) GetBlockInfo(blockHash *externalapi.DomainHash) (*externalap
blockInfo.BlueScore = ghostdagData.BlueScore()
isBlockInHeaderPruningPointFuture, err := s.syncManager.IsBlockInHeaderPruningPointFuture(blockHash)
if err != nil {
return nil, err
}
blockInfo.IsBlockInHeaderPruningPointFuture = isBlockInHeaderPruningPointFuture
return blockInfo, nil
}
@ -183,11 +177,11 @@ func (s *consensus) GetPruningPointUTXOSet(expectedPruningPointHash *externalapi
return serializedUTXOSet, nil
}
func (s *consensus) SetPruningPointUTXOSet(serializedUTXOSet []byte) error {
func (s *consensus) ValidateAndInsertPruningPoint(newPruningPoint *externalapi.DomainBlock, serializedUTXOSet []byte) error {
s.lock.Lock()
defer s.lock.Unlock()
return s.consensusStateManager.SetPruningPointUTXOSet(serializedUTXOSet)
return s.blockProcessor.ValidateAndInsertPruningPoint(newPruningPoint, serializedUTXOSet)
}
func (s *consensus) GetVirtualSelectedParent() (*externalapi.DomainBlock, error) {
@ -201,27 +195,6 @@ func (s *consensus) GetVirtualSelectedParent() (*externalapi.DomainBlock, error)
return s.blockStore.Block(s.databaseContext, virtualGHOSTDAGData.SelectedParent())
}
func (s *consensus) CreateBlockLocator(lowHash, highHash *externalapi.DomainHash, limit uint32) (externalapi.BlockLocator, error) {
s.lock.Lock()
defer s.lock.Unlock()
return s.syncManager.CreateBlockLocator(lowHash, highHash, limit)
}
func (s *consensus) FindNextBlockLocatorBoundaries(blockLocator externalapi.BlockLocator) (lowHash, highHash *externalapi.DomainHash, err error) {
s.lock.Lock()
defer s.lock.Unlock()
return s.syncManager.FindNextBlockLocatorBoundaries(blockLocator)
}
func (s *consensus) GetSyncInfo() (*externalapi.SyncInfo, error) {
s.lock.Lock()
defer s.lock.Unlock()
return s.syncManager.GetSyncInfo()
}
func (s *consensus) Tips() ([]*externalapi.DomainHash, error) {
return s.consensusStateStore.Tips(s.databaseContext)
}
@ -246,3 +219,24 @@ func (s *consensus) GetVirtualInfo() (*externalapi.VirtualInfo, error) {
PastMedianTime: pastMedianTime,
}, nil
}
func (s *consensus) CreateBlockLocator(lowHash, highHash *externalapi.DomainHash, limit uint32) (externalapi.BlockLocator, error) {
s.lock.Lock()
defer s.lock.Unlock()
return s.syncManager.CreateBlockLocator(lowHash, highHash, limit)
}
func (s *consensus) FindNextBlockLocatorBoundaries(blockLocator externalapi.BlockLocator) (lowHash, highHash *externalapi.DomainHash, err error) {
s.lock.Lock()
defer s.lock.Unlock()
return s.syncManager.FindNextBlockLocatorBoundaries(blockLocator)
}
func (s *consensus) GetSyncInfo() (*externalapi.SyncInfo, error) {
s.lock.Lock()
defer s.lock.Unlock()
return s.syncManager.GetSyncInfo()
}

View File

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

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

View File

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

View File

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

View File

@ -1,37 +1,8 @@
package externalapi
import "fmt"
// Each of the following represent one of the possible sync
// states of the consensus
const (
SyncStateSynced SyncState = iota
SyncStateAwaitingGenesis
SyncStateAwaitingUTXOSet
SyncStateAwaitingBlockBodies
)
// SyncState represents the current sync state of the consensus
type SyncState uint8
func (s SyncState) String() string {
switch s {
case SyncStateSynced:
return "SyncStateSynced"
case SyncStateAwaitingGenesis:
return "SyncStateAwaitingGenesis"
case SyncStateAwaitingUTXOSet:
return "SyncStateAwaitingUTXOSet"
case SyncStateAwaitingBlockBodies:
return "SyncStateAwaitingBlockBodies"
}
return fmt.Sprintf("<unknown state (%d)>", s)
}
// SyncInfo holds info about the current sync state of the consensus
type SyncInfo struct {
State SyncState
IsAwaitingUTXOSet bool
IBDRootUTXOBlockHash *DomainHash
HeaderCount uint64
BlockCount uint64

View File

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

View File

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

View File

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

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
type FinalityManager interface {
IsViolatingFinality(blockHash *externalapi.DomainHash) (bool, error)
VirtualFinalityPoint() (*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"
// HeaderTipsManager manages the state of the header tips
type HeaderTipsManager interface {
// HeadersSelectedTipManager manages the state of the headers selected tip
type HeadersSelectedTipManager interface {
AddHeaderTip(hash *externalapi.DomainHash) error
SelectedTip() (*externalapi.DomainHash, error)
}

View File

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

View File

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

View File

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

View File

@ -21,7 +21,7 @@ type blockProcessor struct {
ghostdagManager model.GHOSTDAGManager
pastMedianTimeManager model.PastMedianTimeManager
coinbaseManager model.CoinbaseManager
headerTipsManager model.HeaderTipsManager
headerTipsManager model.HeadersSelectedTipManager
syncManager model.SyncManager
acceptanceDataStore model.AcceptanceDataStore
@ -35,7 +35,7 @@ type blockProcessor struct {
reachabilityDataStore model.ReachabilityDataStore
utxoDiffStore model.UTXODiffStore
blockHeaderStore model.BlockHeaderStore
headerTipsStore model.HeaderTipsStore
headersSelectedTipStore model.HeaderSelectedTipStore
finalityStore model.FinalityStore
stores []model.Store
@ -54,7 +54,7 @@ func New(
pastMedianTimeManager model.PastMedianTimeManager,
ghostdagManager model.GHOSTDAGManager,
coinbaseManager model.CoinbaseManager,
headerTipsManager model.HeaderTipsManager,
headerTipsManager model.HeadersSelectedTipManager,
syncManager model.SyncManager,
acceptanceDataStore model.AcceptanceDataStore,
@ -68,7 +68,7 @@ func New(
reachabilityDataStore model.ReachabilityDataStore,
utxoDiffStore model.UTXODiffStore,
blockHeaderStore model.BlockHeaderStore,
headerTipsStore model.HeaderTipsStore,
headersSelectedTipStore model.HeaderSelectedTipStore,
finalityStore model.FinalityStore,
) model.BlockProcessor {
@ -98,7 +98,7 @@ func New(
reachabilityDataStore: reachabilityDataStore,
utxoDiffStore: utxoDiffStore,
blockHeaderStore: blockHeaderStore,
headerTipsStore: headerTipsStore,
headersSelectedTipStore: headersSelectedTipStore,
finalityStore: finalityStore,
stores: []model.Store{
@ -114,7 +114,7 @@ func New(
reachabilityDataStore,
utxoDiffStore,
blockHeaderStore,
headerTipsStore,
headersSelectedTipStore,
finalityStore,
},
}
@ -128,3 +128,10 @@ func (bp *blockProcessor) ValidateAndInsertBlock(block *externalapi.DomainBlock)
return bp.validateAndInsertBlock(block)
}
func (bp *blockProcessor) ValidateAndInsertPruningPoint(newPruningPoint *externalapi.DomainBlock, serializedUTXOSet []byte) error {
onEnd := logger.LogAndMeasureExecutionTime(log, "ValidateAndInsertPruningPoint")
defer onEnd()
return bp.validateAndInsertPruningPoint(newPruningPoint, serializedUTXOSet)
}

View File

@ -11,36 +11,16 @@ import (
"github.com/pkg/errors"
)
type insertMode uint8
const (
insertModeGenesis insertMode = iota
insertModeHeader
insertModeBlockBody
insertModeBlock
)
func (bp *blockProcessor) validateAndInsertBlock(block *externalapi.DomainBlock) error {
blockHash := consensushashing.HeaderHash(block.Header)
log.Debugf("Validating block %s", blockHash)
insertMode, err := bp.validateAgainstSyncStateAndResolveInsertMode(block)
if err != nil {
return err
}
err = bp.checkBlockStatus(blockHash, insertMode)
if err != nil {
return err
}
err = bp.validateBlock(block, insertMode)
err := bp.validateBlock(block)
if err != nil {
bp.discardAllChanges()
return err
}
if insertMode == insertModeHeader {
isHeaderOnlyBlock := isHeaderOnlyBlock(block)
if isHeaderOnlyBlock {
bp.blockStatusStore.Stage(blockHash, externalapi.StatusHeaderOnly)
} else {
bp.blockStatusStore.Stage(blockHash, externalapi.StatusUTXOPendingVerification)
@ -54,43 +34,38 @@ func (bp *blockProcessor) validateAndInsertBlock(block *externalapi.DomainBlock)
}
var oldHeadersSelectedTip *externalapi.DomainHash
if insertMode != insertModeGenesis {
isGenesis := *blockHash != *bp.genesisHash
if isGenesis {
var err error
oldHeadersSelectedTip, err = bp.headerTipsManager.SelectedTip()
oldHeadersSelectedTip, err = bp.headersSelectedTipStore.HeadersSelectedTip(bp.databaseContext)
if err != nil {
return err
}
}
if insertMode == insertModeHeader {
err = bp.headerTipsManager.AddHeaderTip(blockHash)
if err != nil {
return err
}
} else if insertMode == insertModeBlock || insertMode == insertModeGenesis {
if !isHeaderOnlyBlock {
// Attempt to add the block to the virtual
err = bp.consensusStateManager.AddBlockToVirtual(blockHash)
err = bp.consensusStateManager.AddBlock(blockHash)
if err != nil {
return err
}
tips, err := bp.consensusStateStore.Tips(bp.databaseContext)
if err != nil {
return err
}
bp.headerTipsStore.Stage(tips)
}
if insertMode != insertModeGenesis {
if isGenesis {
err := bp.updateReachabilityReindexRoot(oldHeadersSelectedTip)
if err != nil {
return err
}
}
if insertMode == insertModeBlock {
if !isHeaderOnlyBlock {
// Trigger pruning, which will check if the pruning point changed and delete the data if it did.
err = bp.pruningManager.FindNextPruningPoint()
err = bp.pruningManager.UpdatePruningPointByVirtual()
if err != nil {
return err
}
@ -115,8 +90,8 @@ func (bp *blockProcessor) validateAndInsertBlock(block *externalapi.DomainBlock)
logClosureErr = err
return fmt.Sprintf("Failed to get sync info: %s", err)
}
return fmt.Sprintf("New virtual's blue score: %d. Sync state: %s. Block count: %d. Header count: %d",
virtualGhostDAGData.BlueScore(), syncInfo.State, syncInfo.BlockCount, syncInfo.HeaderCount)
return fmt.Sprintf("New virtual's blue score: %d. Is awaiting UTXO set: %t. Block count: %d. Header count: %d",
virtualGhostDAGData.BlueScore(), syncInfo.IsAwaitingUTXOSet, syncInfo.BlockCount, syncInfo.HeaderCount)
}))
if logClosureErr != nil {
return logClosureErr
@ -125,64 +100,12 @@ func (bp *blockProcessor) validateAndInsertBlock(block *externalapi.DomainBlock)
return nil
}
func (bp *blockProcessor) validateAgainstSyncStateAndResolveInsertMode(block *externalapi.DomainBlock) (insertMode, error) {
syncInfo, err := bp.syncManager.GetSyncInfo()
if err != nil {
return 0, err
}
syncState := syncInfo.State
isHeaderOnlyBlock := isHeaderOnlyBlock(block)
blockHash := consensushashing.HeaderHash(block.Header)
if syncState == externalapi.SyncStateAwaitingGenesis {
if isHeaderOnlyBlock {
return 0, errors.Errorf("Got a header-only block while awaiting genesis")
}
if *blockHash != *bp.genesisHash {
return 0, errors.Errorf("Received a non-genesis block while awaiting genesis")
}
return insertModeGenesis, nil
}
if isHeaderOnlyBlock {
return insertModeHeader, nil
}
if syncState == externalapi.SyncStateAwaitingUTXOSet {
headerTipsPruningPoint, err := bp.consensusStateManager.HeaderTipsPruningPoint()
if err != nil {
return 0, err
}
if *blockHash != *headerTipsPruningPoint {
return 0, errors.Errorf("cannot insert blocks other than the header pruning point " +
"while awaiting the UTXO set")
}
return insertModeBlock, nil
}
if syncState == externalapi.SyncStateAwaitingBlockBodies {
headerTips, err := bp.headerTipsStore.Tips(bp.databaseContext)
if err != nil {
return 0, err
}
selectedHeaderTip, err := bp.ghostdagManager.ChooseSelectedParent(headerTips...)
if err != nil {
return 0, err
}
if *selectedHeaderTip != *blockHash {
return insertModeBlockBody, nil
}
}
return insertModeBlock, nil
}
func isHeaderOnlyBlock(block *externalapi.DomainBlock) bool {
return len(block.Transactions) == 0
}
func (bp *blockProcessor) updateReachabilityReindexRoot(oldHeadersSelectedTip *externalapi.DomainHash) error {
headersSelectedTip, err := bp.headerTipsManager.SelectedTip()
headersSelectedTip, err := bp.headersSelectedTipStore.HeadersSelectedTip(bp.databaseContext)
if err != nil {
return err
}
@ -194,7 +117,9 @@ func (bp *blockProcessor) updateReachabilityReindexRoot(oldHeadersSelectedTip *e
return bp.reachabilityManager.UpdateReindexRoot(headersSelectedTip)
}
func (bp *blockProcessor) checkBlockStatus(hash *externalapi.DomainHash, mode insertMode) error {
func (bp *blockProcessor) checkBlockStatus(block *externalapi.DomainBlock) error {
hash := consensushashing.BlockHash(block)
isHeaderOnlyBlock := isHeaderOnlyBlock(block)
exists, err := bp.blockStatusStore.Exists(bp.databaseContext, hash)
if err != nil {
return err
@ -212,12 +137,12 @@ func (bp *blockProcessor) checkBlockStatus(hash *externalapi.DomainHash, mode in
return errors.Wrapf(ruleerrors.ErrKnownInvalid, "block %s is a known invalid block", hash)
}
isBlockBodyAfterBlockHeader := mode != insertModeHeader && status == externalapi.StatusHeaderOnly
isBlockBodyAfterBlockHeader := !isHeaderOnlyBlock && status == externalapi.StatusHeaderOnly
if !isBlockBodyAfterBlockHeader {
return errors.Wrapf(ruleerrors.ErrDuplicateBlock, "block %s already exists", hash)
}
isDuplicateHeader := mode == insertModeHeader && status == externalapi.StatusHeaderOnly
isDuplicateHeader := isHeaderOnlyBlock && status == externalapi.StatusHeaderOnly
if isDuplicateHeader {
return errors.Wrapf(ruleerrors.ErrDuplicateBlock, "block %s already exists", hash)
}
@ -225,57 +150,16 @@ func (bp *blockProcessor) checkBlockStatus(hash *externalapi.DomainHash, mode in
return nil
}
func (bp *blockProcessor) validateBlock(block *externalapi.DomainBlock, mode insertMode) error {
blockHash := consensushashing.HeaderHash(block.Header)
hasHeader, err := bp.hasHeader(blockHash)
if err != nil {
return err
}
if !hasHeader {
bp.blockHeaderStore.Stage(blockHash, block.Header)
}
// If any validation until (included) proof-of-work fails, simply
// return an error without writing anything in the database.
// This is to prevent spamming attacks.
err = bp.validatePreProofOfWork(block)
if err != nil {
return err
}
err = bp.validatePruningPointViolationAndProofOfWorkAndDifficulty(block, mode)
if err != nil {
return err
}
// If in-context validations fail, discard all changes and store the
// block with StatusInvalid.
err = bp.validatePostProofOfWork(block, mode)
if err != nil {
if errors.As(err, &ruleerrors.RuleError{}) {
bp.discardAllChanges()
hash := consensushashing.BlockHash(block)
bp.blockStatusStore.Stage(hash, externalapi.StatusInvalid)
commitErr := bp.commitAllChanges()
if commitErr != nil {
return commitErr
}
}
return err
}
return nil
}
func (bp *blockProcessor) validatePreProofOfWork(block *externalapi.DomainBlock) error {
blockHash := consensushashing.BlockHash(block)
hasHeader, err := bp.hasHeader(blockHash)
hasValidatedOnlyHeader, err := bp.hasValidatedOnlyHeader(blockHash)
if err != nil {
return err
}
if hasHeader {
if hasValidatedOnlyHeader {
log.Debugf("Block %s header was already validated, so skip the rest of validatePreProofOfWork", blockHash)
return nil
}
@ -286,41 +170,43 @@ func (bp *blockProcessor) validatePreProofOfWork(block *externalapi.DomainBlock)
return nil
}
func (bp *blockProcessor) validatePruningPointViolationAndProofOfWorkAndDifficulty(block *externalapi.DomainBlock, mode insertMode) error {
blockHash := consensushashing.HeaderHash(block.Header)
if mode != insertModeHeader {
// We stage the block here since we need it for parent validation
bp.blockStore.Stage(blockHash, block)
}
return bp.blockValidator.ValidatePruningPointViolationAndProofOfWorkAndDifficulty(blockHash)
}
func (bp *blockProcessor) validatePostProofOfWork(block *externalapi.DomainBlock, mode insertMode) error {
func (bp *blockProcessor) validatePostProofOfWork(block *externalapi.DomainBlock) error {
blockHash := consensushashing.BlockHash(block)
if mode != insertModeHeader {
isHeaderOnlyBlock := isHeaderOnlyBlock(block)
if !isHeaderOnlyBlock {
bp.blockStore.Stage(blockHash, block)
err := bp.blockValidator.ValidateBodyInIsolation(blockHash)
if err != nil {
return err
}
}
hasHeader, err := bp.hasHeader(blockHash)
hasValidatedHeader, err := bp.hasValidatedOnlyHeader(blockHash)
if err != nil {
return err
}
if !hasHeader {
if !hasValidatedHeader {
err = bp.blockValidator.ValidateHeaderInContext(blockHash)
if err != nil {
return err
}
}
if !isHeaderOnlyBlock {
err = bp.blockValidator.ValidateBodyInContext(blockHash)
if err != nil {
return err
}
} else {
log.Tracef("Skipping ValidateBodyInContext for block %s because it's header only", blockHash)
}
return nil
}
func (bp *blockProcessor) hasHeader(blockHash *externalapi.DomainHash) (bool, error) {
func (bp *blockProcessor) hasValidatedOnlyHeader(blockHash *externalapi.DomainHash) (bool, error) {
exists, err := bp.blockStatusStore.Exists(bp.databaseContext, blockHash)
if err != nil {
return false, err

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

View File

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

View File

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

View File

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

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

View File

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

View File

@ -2,23 +2,65 @@ package consensusstatemanager
import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
func (csm *consensusStateManager) checkFinalityViolation(
blockHash *externalapi.DomainHash) error {
func (csm *consensusStateManager) isViolatingFinality(blockHash *externalapi.DomainHash) (isViolatingFinality bool,
shouldSendNotification bool, err error) {
log.Tracef("checkFinalityViolation start for block %s", blockHash)
defer log.Tracef("checkFinalityViolation end for block %s", blockHash)
log.Tracef("isViolatingFinality start for block %s", blockHash)
defer log.Tracef("isViolatingFinality end for block %s", blockHash)
isViolatingFinality, err := csm.finalityManager.IsViolatingFinality(blockHash)
if err != nil {
return err
if *blockHash == *csm.genesisHash {
log.Tracef("Block %s is the genesis block, "+
"and does not violate finality by definition", blockHash)
return false, false, nil
}
if isViolatingFinality {
csm.blockStatusStore.Stage(blockHash, externalapi.StatusUTXOPendingVerification)
log.Warnf("Finality Violation Detected! Block %s violates finality!", blockHash)
return nil
var finalityPoint *externalapi.DomainHash
virtualFinalityPoint, err := csm.finalityManager.VirtualFinalityPoint()
if err != nil {
return false, false, err
}
log.Tracef("The virtual finality point is: %s", virtualFinalityPoint)
// There can be a situation where the virtual points close to the pruning point (or even in the past
// of the pruning point before calling validateAndInsertBlock for the pruning point block) and the
// finality point from the virtual point-of-view is in the past of the pruning point.
// In such situation we override the finality point to be the pruning point to avoid situations where
// the virtual selected parent chain don't include the pruning point.
pruningPoint, err := csm.pruningStore.PruningPoint(csm.databaseContext)
if err != nil {
return false, false, err
}
log.Tracef("The pruning point is: %s", pruningPoint)
isFinalityPointInPastOfPruningPoint, err := csm.dagTopologyManager.IsAncestorOf(virtualFinalityPoint, pruningPoint)
if err != nil {
return false, false, err
}
if !isFinalityPointInPastOfPruningPoint {
finalityPoint = virtualFinalityPoint
} else {
log.Tracef("The virtual finality point is %s in the past of the pruning point, so finality is validated "+
"using the pruning point", virtualFinalityPoint)
finalityPoint = pruningPoint
}
isInSelectedParentChainOfFinalityPoint, err := csm.dagTopologyManager.IsInSelectedParentChainOf(finalityPoint,
blockHash)
if err != nil {
return false, false, err
}
if !isInSelectedParentChainOfFinalityPoint {
if !isFinalityPointInPastOfPruningPoint {
return true, true, nil
}
// On IBD it's pretty normal to get blocks in the anticone of the pruning
// point, so we don't notify on cases when the pruning point is in the future
// of the finality point.
return true, false, nil
}
log.Tracef("Block %s does not violate finality", blockHash)
return nil
return false, false, nil
}

View File

@ -25,7 +25,7 @@ type consensusStateManager struct {
mergeDepthManager model.MergeDepthManager
finalityManager model.FinalityManager
headerTipsStore model.HeaderTipsStore
headersSelectedTipStore model.HeaderSelectedTipStore
blockStatusStore model.BlockStatusStore
ghostdagDataStore model.GHOSTDAGDataStore
consensusStateStore model.ConsensusStateStore
@ -35,6 +35,7 @@ type consensusStateManager struct {
blockRelationStore model.BlockRelationStore
acceptanceDataStore model.AcceptanceDataStore
blockHeaderStore model.BlockHeaderStore
pruningStore model.PruningStore
stores []model.Store
}
@ -68,7 +69,8 @@ func New(
blockRelationStore model.BlockRelationStore,
acceptanceDataStore model.AcceptanceDataStore,
blockHeaderStore model.BlockHeaderStore,
headerTipsStore model.HeaderTipsStore) (model.ConsensusStateManager, error) {
headersSelectedTipStore model.HeaderSelectedTipStore,
pruningStore model.PruningStore) (model.ConsensusStateManager, error) {
csm := &consensusStateManager{
pruningDepth: pruningDepth,
@ -98,7 +100,8 @@ func New(
blockRelationStore: blockRelationStore,
acceptanceDataStore: acceptanceDataStore,
blockHeaderStore: blockHeaderStore,
headerTipsStore: headerTipsStore,
headersSelectedTipStore: headersSelectedTipStore,
pruningStore: pruningStore,
stores: []model.Store{
consensusStateStore,
@ -111,7 +114,8 @@ func New(
consensusStateStore,
utxoDiffStore,
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/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
"github.com/kaspanet/kaspad/domain/consensus/utils/serialization"
"github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
"github.com/kaspanet/kaspad/domain/consensus/utils/utxoserialization"
@ -12,18 +13,11 @@ import (
"github.com/pkg/errors"
)
var virtualHeaderHash = &externalapi.DomainHash{
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe,
}
func (csm *consensusStateManager) SetPruningPointUTXOSet(serializedUTXOSet []byte) error {
onEnd := logger.LogAndMeasureExecutionTime(log, "SetPruningPointUTXOSet")
func (csm *consensusStateManager) UpdatePruningPoint(newPruningPoint *externalapi.DomainBlock, serializedUTXOSet []byte) error {
onEnd := logger.LogAndMeasureExecutionTime(log, "UpdatePruningPoint")
defer onEnd()
err := csm.setPruningPointUTXOSet(serializedUTXOSet)
err := csm.updatePruningPoint(newPruningPoint, serializedUTXOSet)
if err != nil {
csm.discardSetPruningPointUTXOSetChanges()
return err
@ -32,15 +26,23 @@ func (csm *consensusStateManager) SetPruningPointUTXOSet(serializedUTXOSet []byt
return csm.commitSetPruningPointUTXOSetAll()
}
func (csm *consensusStateManager) setPruningPointUTXOSet(serializedUTXOSet []byte) error {
log.Tracef("setPruningPointUTXOSet start")
defer log.Tracef("setPruningPointUTXOSet end")
func (csm *consensusStateManager) updatePruningPoint(newPruningPoint *externalapi.DomainBlock, serializedUTXOSet []byte) error {
log.Tracef("updatePruningPoint start")
defer log.Tracef("updatePruningPoint end")
headerTipsPruningPoint, err := csm.HeaderTipsPruningPoint()
newPruningPointHash := consensushashing.BlockHash(newPruningPoint)
// We ignore the shouldSendNotification return value because we always want to send finality conflict notification
// in case the new pruning point violates finality
isViolatingFinality, _, err := csm.isViolatingFinality(newPruningPointHash)
if err != nil {
return err
}
log.Tracef("The pruning point of the header tips is: %s", headerTipsPruningPoint)
if isViolatingFinality {
log.Warnf("Finality Violation Detected! The suggest pruning point %s violates finality!", newPruningPointHash)
return nil
}
protoUTXOSet := &utxoserialization.ProtoUTXOSet{}
err = proto.Unmarshal(serializedUTXOSet, protoUTXOSet)
@ -54,24 +56,24 @@ func (csm *consensusStateManager) setPruningPointUTXOSet(serializedUTXOSet []byt
}
log.Tracef("Calculated multiset for given UTXO set: %s", utxoSetMultiSet.Hash())
headerTipsPruningPointHeader, err := csm.blockHeaderStore.BlockHeader(csm.databaseContext, headerTipsPruningPoint)
newPruningPointHeader, err := csm.blockHeaderStore.BlockHeader(csm.databaseContext, newPruningPointHash)
if err != nil {
return err
}
log.Tracef("The multiset in the header of the header tip pruning point: %s",
headerTipsPruningPointHeader.UTXOCommitment)
log.Tracef("The UTXO commitment of the pruning point: %s",
newPruningPointHeader.UTXOCommitment)
if headerTipsPruningPointHeader.UTXOCommitment != *utxoSetMultiSet.Hash() {
if newPruningPointHeader.UTXOCommitment != *utxoSetMultiSet.Hash() {
return errors.Wrapf(ruleerrors.ErrBadPruningPointUTXOSet, "the expected multiset hash of the pruning "+
"point UTXO set is %s but got %s", headerTipsPruningPointHeader.UTXOCommitment, *utxoSetMultiSet.Hash())
"point UTXO set is %s but got %s", newPruningPointHeader.UTXOCommitment, *utxoSetMultiSet.Hash())
}
log.Tracef("Header tip pruning point multiset validation passed")
log.Tracef("The new pruning point UTXO commitment validation passed")
log.Tracef("Staging the parent hashes for the header tips pruning point as the DAG tips")
csm.consensusStateStore.StageTips(headerTipsPruningPointHeader.ParentHashes)
log.Tracef("Staging the parent hashes for pruning point as the DAG tips")
csm.consensusStateStore.StageTips(newPruningPointHeader.ParentHashes)
log.Tracef("Setting the parent hashes for the header tips pruning point as the virtual parents")
err = csm.dagTopologyManager.SetParents(model.VirtualBlockHash, headerTipsPruningPointHeader.ParentHashes)
err = csm.dagTopologyManager.SetParents(model.VirtualBlockHash, newPruningPointHeader.ParentHashes)
if err != nil {
return err
}
@ -82,6 +84,13 @@ func (csm *consensusStateManager) setPruningPointUTXOSet(serializedUTXOSet []byt
return err
}
// Before we manually mark the new pruning point as valid, we validate that all of its transactions are valid
// against the provided UTXO set.
err = csm.validateBlockTransactionsAgainstPastUTXO(newPruningPoint, utxo.NewUTXODiff())
if err != nil {
return err
}
err = csm.ghostdagManager.GHOSTDAG(model.VirtualBlockHash)
if err != nil {
return err
@ -93,8 +102,11 @@ func (csm *consensusStateManager) setPruningPointUTXOSet(serializedUTXOSet []byt
return err
}
log.Tracef("Staging the status of the header tips pruning point as %s", externalapi.StatusValid)
csm.blockStatusStore.Stage(headerTipsPruningPoint, externalapi.StatusValid)
log.Tracef("Staging the new pruning point and its UTXO set")
csm.pruningStore.Stage(newPruningPointHash, serializedUTXOSet)
log.Tracef("Staging the new pruning point as %s", externalapi.StatusValid)
csm.blockStatusStore.Stage(newPruningPointHash, externalapi.StatusValid)
return nil
}
@ -145,34 +157,3 @@ func (p protoUTXOSetIterator) Get() (outpoint *externalapi.DomainOutpoint, utxoE
func protoUTXOSetToReadOnlyUTXOSetIterator(protoUTXOSet *utxoserialization.ProtoUTXOSet) model.ReadOnlyUTXOSetIterator {
return &protoUTXOSetIterator{utxoSet: protoUTXOSet}
}
func (csm *consensusStateManager) HeaderTipsPruningPoint() (*externalapi.DomainHash, error) {
log.Tracef("HeaderTipsPruningPoint start")
defer log.Tracef("HeaderTipsPruningPoint end")
headerTips, err := csm.headerTipsStore.Tips(csm.databaseContext)
if err != nil {
return nil, err
}
log.Tracef("The current header tips are: %s", headerTips)
log.Tracef("Temporarily staging the parents of the virtual header to be the header tips: %s", headerTips)
csm.blockRelationStore.StageBlockRelation(virtualHeaderHash, &model.BlockRelations{
Parents: headerTips,
})
defer csm.blockRelationStore.Discard()
err = csm.ghostdagManager.GHOSTDAG(virtualHeaderHash)
if err != nil {
return nil, err
}
defer csm.ghostdagDataStore.Discard()
pruningPoint, err := csm.dagTraversalManager.BlockAtDepth(virtualHeaderHash, csm.pruningDepth)
if err != nil {
return nil, err
}
log.Tracef("The block at depth %d from %s is: %s", csm.pruningDepth, virtualHeaderHash, pruningPoint)
return pruningPoint, nil
}

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("Validating transactions against past UTXO for block %s", blockHash)
err = csm.validateBlockTransactionsAgainstPastUTXO(block, blockHash, pastUTXODiff)
err = csm.validateBlockTransactionsAgainstPastUTXO(block, pastUTXODiff)
if err != nil {
return err
}
@ -57,8 +57,9 @@ func (csm *consensusStateManager) verifyUTXO(block *externalapi.DomainBlock, blo
}
func (csm *consensusStateManager) validateBlockTransactionsAgainstPastUTXO(block *externalapi.DomainBlock,
blockHash *externalapi.DomainHash, pastUTXODiff model.UTXODiff) error {
pastUTXODiff model.UTXODiff) error {
blockHash := consensushashing.BlockHash(block)
log.Tracef("validateBlockTransactionsAgainstPastUTXO start for block %s", blockHash)
defer log.Tracef("validateBlockTransactionsAgainstPastUTXO end for block %s", blockHash)

View File

@ -2,9 +2,9 @@ package dagtraversalmanager
import (
"fmt"
"github.com/kaspanet/kaspad/domain/consensus/model"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/pkg/errors"
)
// dagTraversalManager exposes methods for travering blocks
@ -100,6 +100,11 @@ func (dtm *dagTraversalManager) LowestChainBlockAboveOrEqualToBlueScore(highHash
return nil, err
}
if highBlockGHOSTDAGData.BlueScore() < blueScore {
return nil, errors.Errorf("the given blue score %d is higher than block %s blue score of %d",
blueScore, highHash, highBlockGHOSTDAGData.BlueScore())
}
currentHash := highHash
currentBlockGHOSTDAGData := highBlockGHOSTDAGData
iterator := dtm.SelectedParentIterator(highHash)
@ -112,7 +117,7 @@ func (dtm *dagTraversalManager) LowestChainBlockAboveOrEqualToBlueScore(highHash
if selectedParentBlockGHOSTDAGData.BlueScore() < blueScore {
break
}
currentHash = selectedParentBlockGHOSTDAGData.SelectedParent()
currentHash = currentBlockGHOSTDAGData.SelectedParent()
currentBlockGHOSTDAGData = selectedParentBlockGHOSTDAGData
}

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) {
log.Tracef("virtualFinalityPoint start")
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

@ -18,6 +18,7 @@ type pruningManager struct {
ghostdagDataStore model.GHOSTDAGDataStore
pruningStore model.PruningStore
blockStatusStore model.BlockStatusStore
headerSelectedTipStore model.HeaderSelectedTipStore
multiSetStore model.MultisetStore
acceptanceDataStore model.AcceptanceDataStore
@ -40,6 +41,7 @@ func New(
ghostdagDataStore model.GHOSTDAGDataStore,
pruningStore model.PruningStore,
blockStatusStore model.BlockStatusStore,
headerSelectedTipStore model.HeaderSelectedTipStore,
multiSetStore model.MultisetStore,
acceptanceDataStore model.AcceptanceDataStore,
@ -64,6 +66,7 @@ func New(
acceptanceDataStore: acceptanceDataStore,
blocksStore: blocksStore,
utxoDiffStore: utxoDiffStore,
headerSelectedTipStore: headerSelectedTipStore,
genesisHash: genesisHash,
pruningDepth: pruningDepth,
finalityInterval: finalityInterval,
@ -72,7 +75,7 @@ func New(
// FindNextPruningPoint finds the next pruning point from the
// given blockHash
func (pm *pruningManager) FindNextPruningPoint() error {
func (pm *pruningManager) UpdatePruningPointByVirtual() error {
hasPruningPoint, err := pm.pruningStore.HasPruningPoint(pm.databaseContext)
if err != nil {
return err
@ -95,40 +98,41 @@ func (pm *pruningManager) FindNextPruningPoint() error {
return err
}
virtualSelectedParent, err := pm.ghostdagDataStore.Get(pm.databaseContext, virtual.SelectedParent())
if err != nil {
return err
}
currentPGhost, err := pm.ghostdagDataStore.Get(pm.databaseContext, currentP)
if err != nil {
return err
}
currentPBlueScore := currentPGhost.BlueScore()
// Because the pruning point changes only once per finality, then there's no need to even check for that if a finality interval hasn't passed.
if virtual.BlueScore() <= currentPBlueScore+pm.finalityInterval {
if virtualSelectedParent.BlueScore() <= currentPBlueScore+pm.finalityInterval {
return nil
}
// This means the pruning point is still genesis.
if virtual.BlueScore() <= pm.pruningDepth+pm.finalityInterval {
if virtualSelectedParent.BlueScore() <= pm.pruningDepth+pm.finalityInterval {
return nil
}
// get Virtual(pruningDepth)
candidatePHash, err := pm.dagTraversalManager.BlockAtDepth(model.VirtualBlockHash, pm.pruningDepth)
if err != nil {
return err
}
candidatePGhost, err := pm.ghostdagDataStore.Get(pm.databaseContext, candidatePHash)
newPruningPoint, err := pm.calculatePruningPointFromBlock(model.VirtualBlockHash)
if err != nil {
return err
}
// Actually check if the pruning point changed
if (currentPBlueScore / pm.finalityInterval) < (candidatePGhost.BlueScore() / pm.finalityInterval) {
err = pm.savePruningPoint(candidatePHash)
if *newPruningPoint != *currentP {
err = pm.savePruningPoint(newPruningPoint)
if err != nil {
return err
}
return pm.deletePastBlocks(candidatePHash)
return pm.deletePastBlocks(newPruningPoint)
}
return pm.deletePastBlocks(currentP)
return nil
}
func (pm *pruningManager) deletePastBlocks(pruningPoint *externalapi.DomainHash) error {
@ -233,6 +237,30 @@ func (pm *pruningManager) deleteBlock(blockHash *externalapi.DomainHash) (alread
return false, nil
}
func (pm *pruningManager) CalculatePruningPointByHeaderSelectedTip() (*externalapi.DomainHash, error) {
headersSelectedTip, err := pm.headerSelectedTipStore.HeadersSelectedTip(pm.databaseContext)
if err != nil {
return nil, err
}
return pm.calculatePruningPointFromBlock(headersSelectedTip)
}
func (pm *pruningManager) calculatePruningPointFromBlock(blockHash *externalapi.DomainHash) (*externalapi.DomainHash, error) {
ghostdagData, err := pm.ghostdagDataStore.Get(pm.databaseContext, blockHash)
if err != nil {
return nil, err
}
targetBlueScore := uint64(0)
if ghostdagData.BlueScore() > pm.pruningDepth {
// The target blue is calculated by calculating ghostdagData.BlueScore() - pm.pruningDepth and rounding
// down with the precision of finality interval.
targetBlueScore = ((ghostdagData.BlueScore() - pm.pruningDepth) / pm.finalityInterval) * pm.finalityInterval
}
return pm.dagTraversalManager.LowestChainBlockAboveOrEqualToBlueScore(blockHash, targetBlueScore)
}
func serializeUTXOSetIterator(iter model.ReadOnlyUTXOSetIterator) ([]byte, error) {
serializedUtxo, err := utxoserialization.ReadOnlyUTXOSetToProtoUTXOSet(iter)
if err != nil {

View File

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

View File

@ -99,26 +99,26 @@ func (sm *syncManager) antiPastHashesBetween(lowHash, highHash *externalapi.Doma
}
func (sm *syncManager) missingBlockBodyHashes(highHash *externalapi.DomainHash) ([]*externalapi.DomainHash, error) {
headerTipsPruningPoint, err := sm.consensusStateManager.HeaderTipsPruningPoint()
pruningPoint, err := sm.pruningStore.PruningPoint(sm.databaseContext)
if err != nil {
return nil, err
}
selectedChildIterator, err := sm.dagTraversalManager.SelectedChildIterator(highHash, headerTipsPruningPoint)
selectedChildIterator, err := sm.dagTraversalManager.SelectedChildIterator(highHash, pruningPoint)
if err != nil {
return nil, err
}
lowHash := headerTipsPruningPoint
lowHash := pruningPoint
foundHeaderOnlyBlock := false
for selectedChildIterator.Next() {
selectedChild := selectedChildIterator.Get()
selectedChildStatus, err := sm.blockStatusStore.Get(sm.databaseContext, selectedChild)
hasBlock, err := sm.blockStore.HasBlock(sm.databaseContext, selectedChild)
if err != nil {
return nil, err
}
if selectedChildStatus == externalapi.StatusHeaderOnly {
if !hasBlock {
foundHeaderOnlyBlock = true
break
}
@ -167,24 +167,3 @@ func (sm *syncManager) isHeaderOnlyBlock(blockHash *externalapi.DomainHash) (boo
return status == externalapi.StatusHeaderOnly, nil
}
func (sm *syncManager) isBlockInHeaderPruningPointFuture(blockHash *externalapi.DomainHash) (bool, error) {
if *blockHash == *sm.genesisBlockHash {
return false, nil
}
exists, err := sm.blockStatusStore.Exists(sm.databaseContext, blockHash)
if err != nil {
return false, err
}
if !exists {
return false, nil
}
headerTipsPruningPoint, err := sm.consensusStateManager.HeaderTipsPruningPoint()
if err != nil {
return false, err
}
return sm.dagTopologyManager.IsAncestorOf(headerTipsPruningPoint, blockHash)
}

View File

@ -4,84 +4,43 @@ import (
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
)
// areHeaderTipsSyncedMaxTimeDifference is the number of blocks from
// the header virtual selected parent (estimated by timestamps) for
// kaspad to be considered not synced
const areHeaderTipsSyncedMaxTimeDifference = 300 // 5 minutes
func (sm *syncManager) syncInfo() (*externalapi.SyncInfo, error) {
syncState, err := sm.resolveSyncState()
isAwaitingUTXOSet, ibdRootUTXOBlockHash, err := sm.isAwaitingUTXOSet()
if err != nil {
return nil, err
}
var ibdRootUTXOBlockHash *externalapi.DomainHash
if syncState == externalapi.SyncStateAwaitingUTXOSet {
ibdRootUTXOBlockHash, err = sm.consensusStateManager.HeaderTipsPruningPoint()
if err != nil {
return nil, err
}
}
headerCount := sm.getHeaderCount()
blockCount := sm.getBlockCount()
return &externalapi.SyncInfo{
State: syncState,
IsAwaitingUTXOSet: isAwaitingUTXOSet,
IBDRootUTXOBlockHash: ibdRootUTXOBlockHash,
HeaderCount: headerCount,
BlockCount: blockCount,
}, nil
}
func (sm *syncManager) resolveSyncState() (externalapi.SyncState, error) {
hasTips, err := sm.headerTipsStore.HasTips(sm.databaseContext)
func (sm *syncManager) isAwaitingUTXOSet() (isAwaitingUTXOSet bool, ibdRootUTXOBlockHash *externalapi.DomainHash,
err error) {
pruningPointByHeaders, err := sm.pruningManager.CalculatePruningPointByHeaderSelectedTip()
if err != nil {
return 0, err
}
if !hasTips {
return externalapi.SyncStateAwaitingGenesis, nil
return false, nil, err
}
headerVirtualSelectedParentHash, err := sm.headerVirtualSelectedParentHash()
pruningPoint, err := sm.pruningStore.PruningPoint(sm.databaseContext)
if err != nil {
return 0, err
}
headerVirtualSelectedParentStatus, err := sm.blockStatusStore.Get(sm.databaseContext, headerVirtualSelectedParentHash)
if err != nil {
return 0, err
}
if headerVirtualSelectedParentStatus != externalapi.StatusHeaderOnly {
return externalapi.SyncStateSynced, nil
return false, nil, err
}
// Once the header tips are synced, check the status of
// the pruning point from the point of view of the header
// tips. We check it against StatusValid (rather than
// StatusHeaderOnly) because once we do receive the
// UTXO set of said pruning point, the state is explicitly
// set to StatusValid.
headerTipsPruningPoint, err := sm.consensusStateManager.HeaderTipsPruningPoint()
if err != nil {
return 0, err
}
headerTipsPruningPointStatus, err := sm.blockStatusStore.Get(sm.databaseContext, headerTipsPruningPoint)
if err != nil {
return 0, err
}
if headerTipsPruningPointStatus != externalapi.StatusValid {
return externalapi.SyncStateAwaitingUTXOSet, nil
// If the pruning point by headers is different from the current point
// it means we need to request the new pruning point UTXO set.
if *pruningPoint != *pruningPointByHeaders {
return true, pruningPointByHeaders, nil
}
return externalapi.SyncStateAwaitingBlockBodies, nil
}
func (sm *syncManager) headerVirtualSelectedParentHash() (*externalapi.DomainHash, error) {
headerTips, err := sm.headerTipsStore.Tips(sm.databaseContext)
if err != nil {
return nil, err
}
return sm.ghostdagManager.ChooseSelectedParent(headerTips...)
return false, nil, nil
}
func (sm *syncManager) getHeaderCount() uint64 {

View File

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

View File

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

View File

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

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")
}
err := networkFlags.overrideDAGParams()
if err != nil {
return err
}
return nil
}

View File

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