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

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{
@ -312,19 +313,19 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat
reachabilityManager: reachabilityManager,
finalityManager: finalityManager,
acceptanceDataStore: acceptanceDataStore,
blockStore: blockStore,
blockHeaderStore: blockHeaderStore,
pruningStore: pruningStore,
ghostdagDataStore: ghostdagDataStore,
blockStatusStore: blockStatusStore,
blockRelationStore: blockRelationStore,
consensusStateStore: consensusStateStore,
headerTipsStore: headerTipsStore,
multisetStore: multisetStore,
reachabilityDataStore: reachabilityDataStore,
utxoDiffStore: utxoDiffStore,
finalityStore: finalityStore,
acceptanceDataStore: acceptanceDataStore,
blockStore: blockStore,
blockHeaderStore: blockHeaderStore,
pruningStore: pruningStore,
ghostdagDataStore: ghostdagDataStore,
blockStatusStore: blockStatusStore,
blockRelationStore: blockRelationStore,
consensusStateStore: consensusStateStore,
headersSelectedTipStore: headersSelectedTipStore,
multisetStore: multisetStore,
reachabilityDataStore: reachabilityDataStore,
utxoDiffStore: utxoDiffStore,
finalityStore: finalityStore,
}
genesisInfo, err := c.GetBlockInfo(genesisHash)

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

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

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

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

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

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

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

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

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.