[NOD-1590] Implement optimized finalityPoint calculation mechanism (#1190)

* [NOD-1590] Moved all finality logic to FinalityManager

* [NOD-1590] Add finality store

* [NOD-1590] Implement optimized finalityPoint calculation mechanism

* [NOD-1590] Add comments

* [NOD-1590] Add finalityStore to consensus object, and TestConsensus

* [NOD-1590] Added logs to finalityPoint calculation
This commit is contained in:
Svarog 2020-12-08 10:26:39 +02:00 committed by GitHub
parent 37bf261da1
commit b7ca3f4461
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 418 additions and 134 deletions

View File

@ -29,6 +29,7 @@ type consensus struct {
mergeDepthManager model.MergeDepthManager
pruningManager model.PruningManager
reachabilityManager model.ReachabilityManager
finalityManager model.FinalityManager
acceptanceDataStore model.AcceptanceDataStore
blockStore model.BlockStore
@ -42,6 +43,7 @@ type consensus struct {
multisetStore model.MultisetStore
reachabilityDataStore model.ReachabilityDataStore
utxoDiffStore model.UTXODiffStore
finalityStore model.FinalityStore
}
// BuildBlock builds a block over the current state, with the transactions

View File

@ -0,0 +1,78 @@
package finalitystore
import (
"github.com/kaspanet/kaspad/domain/consensus/model"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/dbkeys"
"github.com/kaspanet/kaspad/domain/consensus/utils/hashes"
"github.com/kaspanet/kaspad/domain/consensus/utils/lrucache"
)
var bucket = dbkeys.MakeBucket([]byte("finality-points"))
type finalityStore struct {
staging map[externalapi.DomainHash]*externalapi.DomainHash
toDelete map[externalapi.DomainHash]struct{}
cache *lrucache.LRUCache
}
// New instantiates a new FinalityStore
func New(cacheSize int) model.FinalityStore {
return &finalityStore{
staging: make(map[externalapi.DomainHash]*externalapi.DomainHash),
toDelete: make(map[externalapi.DomainHash]struct{}),
cache: lrucache.New(cacheSize),
}
}
func (fs *finalityStore) StageFinalityPoint(blockHash *externalapi.DomainHash, finalityPointHash *externalapi.DomainHash) {
fs.staging[*blockHash] = finalityPointHash
}
func (fs *finalityStore) FinalityPoint(
dbContext model.DBReader, blockHash *externalapi.DomainHash) (*externalapi.DomainHash, error) {
if finalityPointHash, ok := fs.staging[*blockHash]; ok {
return finalityPointHash, nil
}
if finalityPointHash, ok := fs.cache.Get(blockHash); ok {
return finalityPointHash.(*externalapi.DomainHash), nil
}
finalityPointHashBytes, err := dbContext.Get(fs.hashAsKey(blockHash))
if err != nil {
return nil, err
}
finalityPointHash, err := hashes.FromBytes(finalityPointHashBytes)
if err != nil {
return nil, err
}
fs.cache.Add(blockHash, finalityPointHash)
return finalityPointHash, nil
}
func (fs *finalityStore) Discard() {
fs.staging = make(map[externalapi.DomainHash]*externalapi.DomainHash)
}
func (fs *finalityStore) Commit(dbTx model.DBTransaction) error {
for hash, finalityPointHash := range fs.staging {
err := dbTx.Put(fs.hashAsKey(&hash), finalityPointHash[:])
if err != nil {
return err
}
fs.cache.Add(&hash, finalityPointHash)
}
fs.Discard()
return nil
}
func (fs *finalityStore) IsStaged() bool {
return len(fs.staging) == 0
}
func (fs *finalityStore) hashAsKey(hash *externalapi.DomainHash) model.DBKey {
return bucket.Key(hash[:])
}

View File

@ -1,12 +1,12 @@
package consensus
import (
"github.com/kaspanet/kaspad/domain/consensus/processes/dagtraversalmanager"
"io/ioutil"
"os"
"sync"
"github.com/kaspanet/kaspad/infrastructure/db/database/ldb"
"github.com/kaspanet/kaspad/domain/consensus/processes/dagtraversalmanager"
"github.com/kaspanet/kaspad/domain/consensus/processes/finalitymanager"
consensusdatabase "github.com/kaspanet/kaspad/domain/consensus/database"
"github.com/kaspanet/kaspad/domain/consensus/datastructures/acceptancedatastore"
@ -15,6 +15,7 @@ import (
"github.com/kaspanet/kaspad/domain/consensus/datastructures/blockstatusstore"
"github.com/kaspanet/kaspad/domain/consensus/datastructures/blockstore"
"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/multisetstore"
@ -40,6 +41,7 @@ import (
"github.com/kaspanet/kaspad/domain/consensus/processes/transactionvalidator"
"github.com/kaspanet/kaspad/domain/dagconfig"
infrastructuredatabase "github.com/kaspanet/kaspad/infrastructure/db/database"
"github.com/kaspanet/kaspad/infrastructure/db/database/ldb"
)
// Factory instantiates new Consensuses
@ -80,6 +82,7 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat
consensusStateStore := consensusstatestore.New()
ghostdagDataStore := ghostdagdatastore.New(10_000)
headerTipsStore := headertipsstore.New()
finalityStore := finalitystore.New(200)
// Processes
reachabilityManager := reachabilitymanager.New(
@ -89,7 +92,8 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat
dagTopologyManager := dagtopologymanager.New(
dbManager,
reachabilityManager,
blockRelationStore)
blockRelationStore,
ghostdagDataStore)
ghostdagManager := ghostdagmanager.New(
dbManager,
dagTopologyManager,
@ -137,11 +141,18 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat
acceptanceDataStore)
headerTipsManager := headertipsmanager.New(dbManager, dagTopologyManager, ghostdagManager, headerTipsStore)
genesisHash := dagParams.GenesisHash
finalityManager := finalitymanager.New(
dbManager,
dagTopologyManager,
finalityStore,
ghostdagDataStore,
genesisHash,
dagParams.FinalityDepth())
mergeDepthManager := mergedepthmanager.New(
dagParams.FinalityDepth(),
dbManager,
dagTopologyManager,
dagTraversalManager,
finalityManager,
ghostdagDataStore)
blockValidator := blockvalidator.New(
dagParams.PowMax,
@ -163,16 +174,17 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat
dagTraversalManager,
coinbaseManager,
mergeDepthManager,
pruningStore,
reachabilityManager,
pruningStore,
blockStore,
ghostdagDataStore,
blockHeaderStore,
blockStatusStore,
reachabilityDataStore,
)
consensusStateManager, err := consensusstatemanager.New(
dbManager,
dagParams.FinalityDepth(),
dagParams.PruningDepth(),
dagParams.MaxMassAcceptedByBlock,
dagParams.MaxBlockParents,
@ -188,6 +200,7 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat
reachabilityManager,
coinbaseManager,
mergeDepthManager,
finalityManager,
blockStatusStore,
ghostdagDataStore,
@ -274,7 +287,8 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat
reachabilityDataStore,
utxoDiffStore,
blockHeaderStore,
headerTipsStore)
headerTipsStore,
finalityStore)
c := &consensus{
lock: &sync.Mutex{},
@ -296,6 +310,7 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat
mergeDepthManager: mergeDepthManager,
pruningManager: pruningManager,
reachabilityManager: reachabilityManager,
finalityManager: finalityManager,
acceptanceDataStore: acceptanceDataStore,
blockStore: blockStore,
@ -309,6 +324,7 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat
multisetStore: multisetStore,
reachabilityDataStore: reachabilityDataStore,
utxoDiffStore: utxoDiffStore,
finalityStore: finalityStore,
}
genesisInfo, err := c.GetBlockInfo(genesisHash)

View File

@ -47,13 +47,13 @@ func TestFinality(t *testing.T) {
for i := uint64(0); i < finalityInterval-1; i++ {
mainChainTip, err = buildAndInsertBlock([]*externalapi.DomainHash{mainChainTipHash})
if err != nil {
t.Fatalf("TestFinality: Failed to process Block #%d: %v", i, err)
t.Fatalf("TestFinality: Failed to process Block #%d: %+v", i, err)
}
mainChainTipHash = consensushashing.BlockHash(mainChainTip)
blockInfo, err := consensus.GetBlockInfo(mainChainTipHash)
if err != nil {
t.Fatalf("TestFinality: Block #%d failed to get info: %v", i, err)
t.Fatalf("TestFinality: Block #%d failed to get info: %+v", i, err)
}
if blockInfo.BlockStatus != externalapi.StatusValid {
t.Fatalf("Block #%d in main chain expected to have status '%s', but got '%s'",
@ -120,7 +120,7 @@ func TestFinality(t *testing.T) {
mainChainTipHash = consensushashing.BlockHash(mainChainTip)
}
virtualFinality, err := consensus.ConsensusStateManager().VirtualFinalityPoint()
virtualFinality, err := consensus.FinalityManager().VirtualFinalityPoint()
if err != nil {
t.Fatalf("TestFinality: Failed getting the virtual's finality point: %v", err)
}

View File

@ -0,0 +1,13 @@
package model
import (
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
)
// FinalityStore represents a store for finality data
type FinalityStore interface {
Store
IsStaged() bool
StageFinalityPoint(blockHash *externalapi.DomainHash, finalityPointHash *externalapi.DomainHash)
FinalityPoint(dbContext DBReader, blockHash *externalapi.DomainHash) (*externalapi.DomainHash, error)
}

View File

@ -13,6 +13,7 @@ type DAGTopologyManager interface {
IsDescendantOf(blockHashA *externalapi.DomainHash, blockHashB *externalapi.DomainHash) (bool, error)
IsAncestorOfAny(blockHash *externalapi.DomainHash, potentialDescendants []*externalapi.DomainHash) (bool, error)
IsInSelectedParentChainOf(blockHashA *externalapi.DomainHash, blockHashB *externalapi.DomainHash) (bool, error)
ChildInSelectedParentChainOf(context, highHash *externalapi.DomainHash) (*externalapi.DomainHash, error)
SetParents(blockHash *externalapi.DomainHash, parentHashes []*externalapi.DomainHash) error
}

View File

@ -0,0 +1,10 @@
package model
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

@ -9,4 +9,5 @@ type ReachabilityManager interface {
IsReachabilityTreeAncestorOf(blockHashA *externalapi.DomainHash, blockHashB *externalapi.DomainHash) (bool, error)
IsDAGAncestorOf(blockHashA *externalapi.DomainHash, blockHashB *externalapi.DomainHash) (bool, error)
UpdateReindexRoot(selectedTip *externalapi.DomainHash) error
FindAncestorOfThisAmongChildrenOfOther(this, other *externalapi.DomainHash) (*externalapi.DomainHash, error)
}

View File

@ -41,6 +41,7 @@ type TestConsensus interface {
BlockValidator() model.BlockValidator
CoinbaseManager() model.CoinbaseManager
ConsensusStateManager() TestConsensusStateManager
FinalityManager() model.FinalityManager
DAGTopologyManager() model.DAGTopologyManager
DAGTraversalManager() model.DAGTraversalManager
DifficultyManager() model.DifficultyManager

View File

@ -11,5 +11,4 @@ type TestConsensusStateManager interface {
AddUTXOToMultiset(multiset model.Multiset, entry externalapi.UTXOEntry,
outpoint *externalapi.DomainOutpoint) error
ResolveBlockStatus(blockHash *externalapi.DomainHash) (externalapi.BlockStatus, error)
VirtualFinalityPoint() (*externalapi.DomainHash, error)
}

View File

@ -36,6 +36,7 @@ type blockProcessor struct {
utxoDiffStore model.UTXODiffStore
blockHeaderStore model.BlockHeaderStore
headerTipsStore model.HeaderTipsStore
finalityStore model.FinalityStore
stores []model.Store
}
@ -67,7 +68,9 @@ func New(
reachabilityDataStore model.ReachabilityDataStore,
utxoDiffStore model.UTXODiffStore,
blockHeaderStore model.BlockHeaderStore,
headerTipsStore model.HeaderTipsStore) model.BlockProcessor {
headerTipsStore model.HeaderTipsStore,
finalityStore model.FinalityStore,
) model.BlockProcessor {
return &blockProcessor{
genesisHash: genesisHash,
@ -96,6 +99,7 @@ func New(
utxoDiffStore: utxoDiffStore,
blockHeaderStore: blockHeaderStore,
headerTipsStore: headerTipsStore,
finalityStore: finalityStore,
stores: []model.Store{
consensusStateStore,
@ -111,6 +115,7 @@ func New(
utxoDiffStore,
blockHeaderStore,
headerTipsStore,
finalityStore,
},
}
}

View File

@ -40,18 +40,6 @@ func (bp *blockProcessor) validateAndInsertBlock(block *externalapi.DomainBlock)
return err
}
hasHeader, err := bp.hasHeader(blockHash)
if err != nil {
return err
}
if !hasHeader {
err = bp.reachabilityManager.AddBlock(blockHash)
if err != nil {
return err
}
}
if insertMode == insertModeHeader {
bp.blockStatusStore.Stage(blockHash, externalapi.StatusHeaderOnly)
} else {

View File

@ -37,6 +37,21 @@ func (v *blockValidator) ValidateHeaderInContext(blockHash *externalapi.DomainHa
return err
}
// If needed - calculate reachability data right before calling CheckBoundedMergeDepth,
// since it's used to find a block's finality point.
// This might not be required if this block's header has previously been received during
// headers-first synchronization.
hasReachabilityData, err := v.reachabilityStore.HasReachabilityData(v.databaseContext, blockHash)
if err != nil {
return err
}
if !hasReachabilityData {
err = v.reachabilityManager.AddBlock(blockHash)
if err != nil {
return err
}
}
err = v.mergeDepthManager.CheckBoundedMergeDepth(blockHash)
if err != nil {
return err

View File

@ -33,11 +33,13 @@ type blockValidator struct {
coinbaseManager model.CoinbaseManager
mergeDepthManager model.MergeDepthManager
pruningStore model.PruningStore
reachabilityManager model.ReachabilityManager
blockStore model.BlockStore
ghostdagDataStore model.GHOSTDAGDataStore
blockHeaderStore model.BlockHeaderStore
blockStatusStore model.BlockStatusStore
reachabilityStore model.ReachabilityDataStore
}
// New instantiates a new BlockValidator
@ -61,22 +63,27 @@ func New(powMax *big.Int,
dagTraversalManager model.DAGTraversalManager,
coinbaseManager model.CoinbaseManager,
mergeDepthManager model.MergeDepthManager,
reachabilityManager model.ReachabilityManager,
pruningStore model.PruningStore,
blockStore model.BlockStore,
ghostdagDataStore model.GHOSTDAGDataStore,
blockHeaderStore model.BlockHeaderStore,
blockStatusStore model.BlockStatusStore) model.BlockValidator {
blockStatusStore model.BlockStatusStore,
reachabilityStore model.ReachabilityDataStore,
) model.BlockValidator {
return &blockValidator{
powMax: powMax,
skipPoW: skipPoW,
genesisHash: genesisHash,
enableNonNativeSubnetworks: enableNonNativeSubnetworks,
powMaxBits: util.BigToCompact(powMax),
maxBlockSize: maxBlockSize,
mergeSetSizeLimit: mergeSetSizeLimit,
maxBlockParents: maxBlockParents,
powMax: powMax,
skipPoW: skipPoW,
genesisHash: genesisHash,
enableNonNativeSubnetworks: enableNonNativeSubnetworks,
powMaxBits: util.BigToCompact(powMax),
maxBlockSize: maxBlockSize,
mergeSetSizeLimit: mergeSetSizeLimit,
maxBlockParents: maxBlockParents,
timestampDeviationTolerance: timestampDeviationTolerance,
targetTimePerBlock: targetTimePerBlock,
databaseContext: databaseContext,
@ -88,11 +95,13 @@ func New(powMax *big.Int,
dagTraversalManager: dagTraversalManager,
coinbaseManager: coinbaseManager,
mergeDepthManager: mergeDepthManager,
pruningStore: pruningStore,
reachabilityManager: reachabilityManager,
pruningStore: pruningStore,
blockStore: blockStore,
ghostdagDataStore: ghostdagDataStore,
blockHeaderStore: blockHeaderStore,
blockStatusStore: blockStatusStore,
reachabilityStore: reachabilityStore,
}
}

View File

@ -0,0 +1,24 @@
package consensusstatemanager
import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
func (csm *consensusStateManager) checkFinalityViolation(
blockHash *externalapi.DomainHash) error {
log.Tracef("checkFinalityViolation start for block %s", blockHash)
defer log.Tracef("checkFinalityViolation end for block %s", blockHash)
isViolatingFinality, err := csm.finalityManager.IsViolatingFinality(blockHash)
if err != nil {
return err
}
if isViolatingFinality {
csm.blockStatusStore.Stage(blockHash, externalapi.StatusUTXOPendingVerification)
log.Warnf("Finality Violation Detected! Block %s violates finality!", blockHash)
return nil
}
log.Tracef("Block %s does not violate finality", blockHash)
return nil
}

View File

@ -7,7 +7,6 @@ import (
// consensusStateManager manages the node's consensus state
type consensusStateManager struct {
finalityDepth uint64
pruningDepth uint64
maxMassAcceptedByBlock uint64
maxBlockParents model.KType
@ -24,8 +23,9 @@ type consensusStateManager struct {
reachabilityManager model.ReachabilityManager
coinbaseManager model.CoinbaseManager
mergeDepthManager model.MergeDepthManager
headerTipsStore model.HeaderTipsStore
finalityManager model.FinalityManager
headerTipsStore model.HeaderTipsStore
blockStatusStore model.BlockStatusStore
ghostdagDataStore model.GHOSTDAGDataStore
consensusStateStore model.ConsensusStateStore
@ -42,7 +42,6 @@ type consensusStateManager struct {
// New instantiates a new ConsensusStateManager
func New(
databaseContext model.DBManager,
finalityDepth uint64,
pruningDepth uint64,
maxMassAcceptedByBlock uint64,
maxBlockParents model.KType,
@ -58,6 +57,7 @@ func New(
reachabilityManager model.ReachabilityManager,
coinbaseManager model.CoinbaseManager,
mergeDepthManager model.MergeDepthManager,
finalityManager model.FinalityManager,
blockStatusStore model.BlockStatusStore,
ghostdagDataStore model.GHOSTDAGDataStore,
@ -71,7 +71,6 @@ func New(
headerTipsStore model.HeaderTipsStore) (model.ConsensusStateManager, error) {
csm := &consensusStateManager{
finalityDepth: finalityDepth,
pruningDepth: pruningDepth,
maxMassAcceptedByBlock: maxMassAcceptedByBlock,
maxBlockParents: maxBlockParents,
@ -88,6 +87,7 @@ func New(
reachabilityManager: reachabilityManager,
coinbaseManager: coinbaseManager,
mergeDepthManager: mergeDepthManager,
finalityManager: finalityManager,
multisetStore: multisetStore,
blockStore: blockStore,

View File

@ -1,71 +0,0 @@
package consensusstatemanager
import (
"github.com/kaspanet/kaspad/domain/consensus/model"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
)
func (csm *consensusStateManager) checkFinalityViolation(
blockHash *externalapi.DomainHash) error {
log.Tracef("checkFinalityViolation start for block %s", blockHash)
defer log.Tracef("checkFinalityViolation end for block %s", blockHash)
if *blockHash == *csm.genesisHash {
log.Tracef("Block %s is the genesis block, "+
"and does not violate finality by definition", blockHash)
return nil
}
isViolatingFinality, err := csm.isViolatingFinality(blockHash)
if err != nil {
return err
}
if isViolatingFinality {
csm.blockStatusStore.Stage(blockHash, externalapi.StatusUTXOPendingVerification)
log.Warnf("Finality Violation Detected! Block %s violates finality!", blockHash)
return nil
}
log.Tracef("Block %s does not violate finality", blockHash)
return nil
}
func (csm *consensusStateManager) virtualFinalityPoint() (
*externalapi.DomainHash, error) {
log.Tracef("virtualFinalityPoint start")
defer log.Tracef("virtualFinalityPoint end")
virtualFinalityPoint, err := csm.dagTraversalManager.BlockAtDepth(
model.VirtualBlockHash, csm.finalityDepth)
if err != nil {
return nil, err
}
log.Tracef("The current virtual finality block is: %s", virtualFinalityPoint)
return virtualFinalityPoint, nil
}
func (csm *consensusStateManager) isViolatingFinality(
blockHash *externalapi.DomainHash) (bool, error) {
log.Tracef("isViolatingFinality start for block %s", blockHash)
defer log.Tracef("isViolatingFinality end for block %s", blockHash)
virtualFinalityPoint, err := csm.virtualFinalityPoint()
if err != nil {
return false, err
}
log.Tracef("The virtual finality point is: %s", virtualFinalityPoint)
isInSelectedParentChain, err := csm.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
}

View File

@ -202,7 +202,7 @@ func (csm *consensusStateManager) boundedMergeBreakingParents(
}
log.Tracef("The potentially kosherizing blocks are: %s", potentiallyKosherizingBlocks)
virtualFinalityPoint, err := csm.virtualFinalityPoint()
virtualFinalityPoint, err := csm.finalityManager.VirtualFinalityPoint()
if err != nil {
return nil, err
}

View File

@ -24,7 +24,3 @@ func (csm testConsensusStateManager) AddUTXOToMultiset(
func (csm testConsensusStateManager) ResolveBlockStatus(blockHash *externalapi.DomainHash) (externalapi.BlockStatus, error) {
return csm.resolveBlockStatus(blockHash)
}
func (csm testConsensusStateManager) VirtualFinalityPoint() (*externalapi.DomainHash, error) {
return csm.virtualFinalityPoint()
}

View File

@ -3,6 +3,7 @@ package dagtopologymanager
import (
"github.com/kaspanet/kaspad/domain/consensus/model"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/pkg/errors"
)
// dagTopologyManager exposes methods for querying relationships
@ -10,6 +11,7 @@ import (
type dagTopologyManager struct {
reachabilityManager model.ReachabilityManager
blockRelationStore model.BlockRelationStore
ghostdagStore model.GHOSTDAGDataStore
databaseContext model.DBReader
}
@ -17,12 +19,14 @@ type dagTopologyManager struct {
func New(
databaseContext model.DBReader,
reachabilityManager model.ReachabilityManager,
blockRelationStore model.BlockRelationStore) model.DAGTopologyManager {
blockRelationStore model.BlockRelationStore,
ghostdagStore model.GHOSTDAGDataStore) model.DAGTopologyManager {
return &dagTopologyManager{
databaseContext: databaseContext,
reachabilityManager: reachabilityManager,
blockRelationStore: blockRelationStore,
ghostdagStore: ghostdagStore,
}
}
@ -160,3 +164,36 @@ func (dtm *dagTopologyManager) SetParents(blockHash *externalapi.DomainHash, par
return nil
}
// ChildInSelectedParentChainOf returns the child of `context` that is in the selected-parent-chain of `highHash`
func (dtm *dagTopologyManager) ChildInSelectedParentChainOf(
blockHash, highHash *externalapi.DomainHash) (*externalapi.DomainHash, error) {
// Virtual doesn't have reachability data, therefore, it should be treated as a special case -
// use it's selected parent as highHash.
specifiedHighHash := highHash
if highHash == model.VirtualBlockHash {
ghostdagData, err := dtm.ghostdagStore.Get(dtm.databaseContext, highHash)
if err != nil {
return nil, err
}
selectedParent := ghostdagData.SelectedParent()
// In case where `blockHash` is an immediate parent of `highHash`
if *blockHash == *selectedParent {
return highHash, nil
}
highHash = selectedParent
}
isInSelectedParentChain, err := dtm.IsInSelectedParentChainOf(blockHash, highHash)
if err != nil {
return nil, err
}
if !isInSelectedParentChain {
return nil, errors.Errorf("blockHash(%s) is not in the selected-parent-chain of highHash(%s)",
blockHash, specifiedHighHash)
}
return dtm.reachabilityManager.FindAncestorOfThisAmongChildrenOfOther(highHash, blockHash)
}

View File

@ -0,0 +1,144 @@
package finalitymanager
import (
"errors"
"github.com/kaspanet/kaspad/domain/consensus/model"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/infrastructure/db/database"
)
type finalityManager struct {
databaseContext model.DBReader
dagTopologyManager model.DAGTopologyManager
finalityStore model.FinalityStore
ghostdagDataStore model.GHOSTDAGDataStore
genesisHash *externalapi.DomainHash
finalityDepth uint64
}
// New instantiates a new FinalityManager
func New(databaseContext model.DBReader,
dagTopologyManager model.DAGTopologyManager,
finalityStore model.FinalityStore,
ghostdagDataStore model.GHOSTDAGDataStore,
genesisHash *externalapi.DomainHash,
finalityDepth uint64) model.FinalityManager {
return &finalityManager{
databaseContext: databaseContext,
genesisHash: genesisHash,
dagTopologyManager: dagTopologyManager,
finalityStore: finalityStore,
ghostdagDataStore: ghostdagDataStore,
finalityDepth: finalityDepth,
}
}
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")
virtualFinalityPoint, err := fm.calculateFinalityPoint(model.VirtualBlockHash)
if err != nil {
return nil, err
}
log.Tracef("The current virtual finality block is: %s", virtualFinalityPoint)
return virtualFinalityPoint, nil
}
func (fm *finalityManager) FinalityPoint(blockHash *externalapi.DomainHash) (*externalapi.DomainHash, error) {
log.Tracef("FinalityPoint start")
defer log.Tracef("FinalityPoint end")
if *blockHash == *model.VirtualBlockHash {
return fm.VirtualFinalityPoint()
}
finalityPoint, err := fm.finalityStore.FinalityPoint(fm.databaseContext, blockHash)
if err != nil {
log.Tracef("%s finality point not found in store - calculating", blockHash)
if errors.Is(err, database.ErrNotFound) {
return fm.calculateAndStageFinalityPoint(blockHash)
}
return nil, err
}
return finalityPoint, nil
}
func (fm *finalityManager) calculateAndStageFinalityPoint(blockHash *externalapi.DomainHash) (*externalapi.DomainHash, error) {
finalityPoint, err := fm.calculateFinalityPoint(blockHash)
if err != nil {
return nil, err
}
fm.finalityStore.StageFinalityPoint(blockHash, finalityPoint)
return finalityPoint, nil
}
func (fm *finalityManager) calculateFinalityPoint(blockHash *externalapi.DomainHash) (*externalapi.DomainHash, error) {
log.Tracef("calculateFinalityPoint start")
defer log.Tracef("calculateFinalityPoint end")
ghostdagData, err := fm.ghostdagDataStore.Get(fm.databaseContext, blockHash)
if err != nil {
return nil, err
}
if ghostdagData.BlueScore() < fm.finalityDepth {
log.Tracef("%s blue score lower then finality depth - returning genesis as finality point", blockHash)
return fm.genesisHash, nil
}
selectedParent := ghostdagData.SelectedParent()
if *selectedParent == *fm.genesisHash {
return fm.genesisHash, nil
}
current, err := fm.finalityStore.FinalityPoint(fm.databaseContext, ghostdagData.SelectedParent())
if err != nil {
return nil, err
}
requiredBlueScore := ghostdagData.BlueScore() - fm.finalityDepth
log.Tracef("%s's finality point is the one having the highest blue score lower then %d", blockHash, requiredBlueScore)
var next *externalapi.DomainHash
for {
next, err = fm.dagTopologyManager.ChildInSelectedParentChainOf(current, blockHash)
if err != nil {
return nil, err
}
nextGHOSTDAGData, err := fm.ghostdagDataStore.Get(fm.databaseContext, next)
if err != nil {
return nil, err
}
if nextGHOSTDAGData.BlueScore() >= requiredBlueScore {
log.Tracef("%s's finality point is %s", blockHash, current)
return current, nil
}
current = next
}
}

View File

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

View File

@ -2,14 +2,15 @@ package ghostdagmanager_test
import (
"encoding/json"
"github.com/kaspanet/kaspad/domain/consensus/processes/ghostdagmanager"
"github.com/kaspanet/kaspad/util"
"math/big"
"os"
"path/filepath"
"reflect"
"testing"
"github.com/kaspanet/kaspad/domain/consensus/processes/ghostdagmanager"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/domain/consensus/model"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/processes/ghostdag2"
@ -226,6 +227,10 @@ type DAGTopologyManagerImpl struct {
parentsMap map[externalapi.DomainHash][]*externalapi.DomainHash
}
func (dt *DAGTopologyManagerImpl) ChildInSelectedParentChainOf(context, highHash *externalapi.DomainHash) (*externalapi.DomainHash, error) {
panic("implement me")
}
func (dt *DAGTopologyManagerImpl) Tips() ([]*externalapi.DomainHash, error) {
panic("implement me")
}

View File

@ -8,27 +8,27 @@ import (
)
type mergeDepthManager struct {
finalityDepth uint64
databaseContext model.DBReader
dagTopologyManager model.DAGTopologyManager
dagTraversalManager model.DAGTraversalManager
finalityManager model.FinalityManager
ghostdagDataStore model.GHOSTDAGDataStore
}
// New instantiates a new MergeDepthManager
func New(finalityDepth uint64,
func New(
databaseContext model.DBReader,
dagTopologyManager model.DAGTopologyManager,
dagTraversalManager model.DAGTraversalManager,
finalityManager model.FinalityManager,
ghostdagDataStore model.GHOSTDAGDataStore) model.MergeDepthManager {
return &mergeDepthManager{
finalityDepth: finalityDepth,
databaseContext: databaseContext,
dagTopologyManager: dagTopologyManager,
dagTraversalManager: dagTraversalManager,
finalityManager: finalityManager,
ghostdagDataStore: ghostdagDataStore,
}
@ -50,7 +50,7 @@ func (mdm *mergeDepthManager) CheckBoundedMergeDepth(blockHash *externalapi.Doma
return nil
}
finalityPoint, err := mdm.finalityPoint(blockHash)
finalityPoint, err := mdm.finalityManager.FinalityPoint(blockHash)
if err != nil {
return err
}
@ -102,14 +102,10 @@ func (mdm mergeDepthManager) NonBoundedMergeDepthViolatingBlues(blockHash *exter
}
func (mdm *mergeDepthManager) hasFinalityPointInOthersSelectedChain(this, other *externalapi.DomainHash) (bool, error) {
finalityPoint, err := mdm.finalityPoint(this)
finalityPoint, err := mdm.finalityManager.FinalityPoint(this)
if err != nil {
return false, err
}
return mdm.dagTopologyManager.IsInSelectedParentChainOf(finalityPoint, other)
}
func (mdm *mergeDepthManager) finalityPoint(blockHash *externalapi.DomainHash) (*externalapi.DomainHash, error) {
return mdm.dagTraversalManager.BlockAtDepth(blockHash, mdm.finalityDepth)
}

View File

@ -163,7 +163,7 @@ func (rt *reachabilityManager) addChild(node, child, reindexRoot *externalapi.Do
// Temporarily set the child's interval to be empty, at
// the start of node's remaining interval. This is done
// so that child-of-node checks (e.g.
// findAncestorOfThisAmongChildrenOfOther) will not fail for node.
// FindAncestorOfThisAmongChildrenOfOther) will not fail for node.
err = rt.stageInterval(child, newReachabilityInterval(remaining.Start, remaining.Start-1))
if err != nil {
return err
@ -411,7 +411,7 @@ func (rt *reachabilityManager) reindexIntervalsEarlierThanReindexRoot(node,
// The chosen child is:
// a. A reachability tree child of `commonAncestor`
// b. A reachability tree ancestor of `reindexRoot`
commonAncestorChosenChild, err := rt.findAncestorOfThisAmongChildrenOfOther(reindexRoot, commonAncestor)
commonAncestorChosenChild, err := rt.FindAncestorOfThisAmongChildrenOfOther(reindexRoot, commonAncestor)
if err != nil {
return err
}
@ -465,7 +465,7 @@ func (rt *reachabilityManager) reclaimIntervalBeforeChosenChild(rtn, commonAnces
break
}
current, err = rt.findAncestorOfThisAmongChildrenOfOther(reindexRoot, current)
current, err = rt.FindAncestorOfThisAmongChildrenOfOther(reindexRoot, current)
if err != nil {
return err
}
@ -587,7 +587,7 @@ func (rt *reachabilityManager) reclaimIntervalAfterChosenChild(node, commonAnces
break
}
current, err = rt.findAncestorOfThisAmongChildrenOfOther(reindexRoot, current)
current, err = rt.FindAncestorOfThisAmongChildrenOfOther(reindexRoot, current)
if err != nil {
return err
}
@ -799,7 +799,7 @@ func (rt *reachabilityManager) maybeMoveReindexRoot(reindexRoot, newTreeNode *ex
return commonAncestor, true, nil
}
reindexRootChosenChild, err := rt.findAncestorOfThisAmongChildrenOfOther(newTreeNode, reindexRoot)
reindexRootChosenChild, err := rt.FindAncestorOfThisAmongChildrenOfOther(newTreeNode, reindexRoot)
if err != nil {
return nil, false, err
}
@ -826,9 +826,9 @@ func (rt *reachabilityManager) maybeMoveReindexRoot(reindexRoot, newTreeNode *ex
return reindexRootChosenChild, true, nil
}
// findAncestorOfThisAmongChildrenOfOther finds the reachability tree child
// FindAncestorOfThisAmongChildrenOfOther finds the reachability tree child
// of node that is the ancestor of node.
func (rt *reachabilityManager) findAncestorOfThisAmongChildrenOfOther(this, other *externalapi.DomainHash) (*externalapi.DomainHash, error) {
func (rt *reachabilityManager) FindAncestorOfThisAmongChildrenOfOther(this, other *externalapi.DomainHash) (*externalapi.DomainHash, error) {
otherChildren, err := rt.children(other)
if err != nil {
return nil, err

View File

@ -120,3 +120,11 @@ func (tc *testConsensus) SyncManager() model.SyncManager {
func (tc *testConsensus) TransactionValidator() testapi.TestTransactionValidator {
return tc.testTransactionValidator
}
func (tc *testConsensus) FinalityManager() model.FinalityManager {
return tc.finalityManager
}
func (tc *testConsensus) FinalityStore() model.FinalityStore {
return tc.finalityStore
}