From b8ca33d91dd9ca4d2b91ab09ae56e750d97a6c74 Mon Sep 17 00:00:00 2001 From: Ori Newman Date: Mon, 11 Jan 2021 15:51:45 +0200 Subject: [PATCH] Add selected chain store and optimize block locator with it (#1394) * Add selected chain store and optimize block locator with it * Fix build error * Fix comments * Fix IsStaged * Rename CalculateSelectedParentChainChanges to CalculateChainPath and SelectedParentChainChanges->SelectedChainPath * Use binary.LittleEndian directly to allow compiler optimizations * Remove boolean from HeadersSelectedChainStore interface * Prevent endless loop in block locator --- app/protocol/flows/blockrelay/ibd.go | 9 +- app/rpc/rpccontext/chain_changed.go | 2 +- domain/consensus/consensus.go | 37 +-- .../database/binaryserialization/hash.go | 13 + .../binaryserialization/selected_chain.go | 15 ++ .../headersselectedchainstore.go | 230 ++++++++++++++++++ domain/consensus/factory.go | 38 +-- .../consensus/model/externalapi/consensus.go | 3 +- .../model/externalapi/insertblockresult.go | 6 +- ...atastructures_headersselectedchainstore.go | 13 + ...terface_processes_consensusstatemanager.go | 4 +- ...interface_processes_dagtraversalmanager.go | 2 + .../model/interface_processes_syncmanager.go | 1 + .../consensus/model/testapi/test_consensus.go | 1 + .../blockprocessor/blockprocessor.go | 58 +++-- .../blockprocessor/validateandinsertblock.go | 2 +- .../processes/coinbasemanager/payload.go | 6 +- .../add_block_to_virtual.go | 2 +- .../find_selected_parent_chain_changes.go | 54 +--- ...find_selected_parent_chain_changes_test.go | 4 +- .../consensusstatemanager/update_virtual.go | 7 +- .../dagtraversalmanager.go | 50 ++++ .../headersselectedtipmanager.go | 79 ++++++ .../headersselectedtipmanager_test.go | 77 ++++++ .../headertipsmanager.go | 55 ----- .../processes/syncmanager/blocklocator.go | 43 ++++ .../processes/syncmanager/syncmanager.go | 32 ++- domain/consensus/test_consensus.go | 1 + domain/consensus/test_consensus_getters.go | 4 + .../lrucacheuint64tohash.go | 57 +++++ domain/utxoindex/utxoindex.go | 2 +- 31 files changed, 713 insertions(+), 194 deletions(-) create mode 100644 domain/consensus/database/binaryserialization/hash.go create mode 100644 domain/consensus/database/binaryserialization/selected_chain.go create mode 100644 domain/consensus/datastructures/headersselectedchainstore/headersselectedchainstore.go create mode 100644 domain/consensus/model/interface_datastructures_headersselectedchainstore.go create mode 100644 domain/consensus/processes/headersselectedtipmanager/headersselectedtipmanager.go create mode 100644 domain/consensus/processes/headersselectedtipmanager/headersselectedtipmanager_test.go delete mode 100644 domain/consensus/processes/headersselectedtipmanager/headertipsmanager.go create mode 100644 domain/consensus/utils/lrucacheuint64tohash/lrucacheuint64tohash.go diff --git a/app/protocol/flows/blockrelay/ibd.go b/app/protocol/flows/blockrelay/ibd.go index d83877d9e..2eac07659 100644 --- a/app/protocol/flows/blockrelay/ibd.go +++ b/app/protocol/flows/blockrelay/ibd.go @@ -131,7 +131,7 @@ func (flow *handleRelayInvsFlow) findHighestSharedBlockHash(targetHash *external for !lowHash.Equal(highHash) { log.Debugf("Sending a blockLocator to %s between %s and %s", flow.peer, lowHash, highHash) - blockLocator, err := flow.Domain().Consensus().CreateBlockLocator(lowHash, highHash, 0) + blockLocator, err := flow.Domain().Consensus().CreateHeadersSelectedChainBlockLocator(lowHash, highHash) if err != nil { return nil, err } @@ -169,6 +169,13 @@ func (flow *handleRelayInvsFlow) findHighestSharedBlockHash(targetHash *external log.Debugf("The index of the highest hash in the original "+ "blockLocator sent to %s is %d", flow.peer, highestHashIndex) + // If the block locator contains only two adjacent chain blocks, the + // syncer will always find the same highest chain block, so to avoid + // an endless loop, we explicitly stop the loop in such situation. + if len(blockLocator) == 2 && highestHashIndex == 1 { + return highestHash, nil + } + locatorHashAboveHighestHash := highestHash if highestHashIndex > 0 { locatorHashAboveHighestHash = blockLocator[highestHashIndex-1] diff --git a/app/rpc/rpccontext/chain_changed.go b/app/rpc/rpccontext/chain_changed.go index 2375e3ce8..cd8d6c18a 100644 --- a/app/rpc/rpccontext/chain_changed.go +++ b/app/rpc/rpccontext/chain_changed.go @@ -9,7 +9,7 @@ import ( // ConvertVirtualSelectedParentChainChangesToChainChangedNotificationMessage converts // VirtualSelectedParentChainChanges to VirtualSelectedParentChainChangedNotificationMessage func (ctx *Context) ConvertVirtualSelectedParentChainChangesToChainChangedNotificationMessage( - selectedParentChainChanges *externalapi.SelectedParentChainChanges) (*appmessage.VirtualSelectedParentChainChangedNotificationMessage, error) { + selectedParentChainChanges *externalapi.SelectedChainPath) (*appmessage.VirtualSelectedParentChainChangedNotificationMessage, error) { removedChainBlockHashes := make([]string, len(selectedParentChainChanges.Removed)) for i, removed := range selectedParentChainChanges.Removed { diff --git a/domain/consensus/consensus.go b/domain/consensus/consensus.go index 614d19b97..08a14e10c 100644 --- a/domain/consensus/consensus.go +++ b/domain/consensus/consensus.go @@ -32,19 +32,20 @@ type consensus struct { 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 - headersSelectedTipStore model.HeaderSelectedTipStore - 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 + headersSelectedChainStore model.HeadersSelectedChainStore } // BuildBlock builds a block over the current state, with the transactions @@ -297,6 +298,14 @@ func (s *consensus) CreateBlockLocator(lowHash, highHash *externalapi.DomainHash return s.syncManager.CreateBlockLocator(lowHash, highHash, limit) } +func (s *consensus) CreateHeadersSelectedChainBlockLocator(lowHash, + highHash *externalapi.DomainHash) (externalapi.BlockLocator, error) { + s.lock.Lock() + defer s.lock.Unlock() + + return s.syncManager.CreateHeadersSelectedChainBlockLocator(lowHash, highHash) +} + func (s *consensus) FindNextBlockLocatorBoundaries(blockLocator externalapi.BlockLocator) (lowHash, highHash *externalapi.DomainHash, err error) { s.lock.Lock() defer s.lock.Unlock() @@ -327,7 +336,7 @@ func (s *consensus) IsValidPruningPoint(blockHash *externalapi.DomainHash) (bool return s.pruningManager.IsValidPruningPoint(blockHash) } -func (s *consensus) GetVirtualSelectedParentChainFromBlock(blockHash *externalapi.DomainHash) (*externalapi.SelectedParentChainChanges, error) { +func (s *consensus) GetVirtualSelectedParentChainFromBlock(blockHash *externalapi.DomainHash) (*externalapi.SelectedChainPath, error) { s.lock.Lock() defer s.lock.Unlock() diff --git a/domain/consensus/database/binaryserialization/hash.go b/domain/consensus/database/binaryserialization/hash.go new file mode 100644 index 000000000..a405eaa4f --- /dev/null +++ b/domain/consensus/database/binaryserialization/hash.go @@ -0,0 +1,13 @@ +package binaryserialization + +import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" + +// SerializeHash serializes hash to a slice of bytes +func SerializeHash(hash *externalapi.DomainHash) []byte { + return hash.ByteSlice() +} + +// DeserializeHash a slice of bytes to a hash +func DeserializeHash(hashBytes []byte) (*externalapi.DomainHash, error) { + return externalapi.NewDomainHashFromByteSlice(hashBytes) +} diff --git a/domain/consensus/database/binaryserialization/selected_chain.go b/domain/consensus/database/binaryserialization/selected_chain.go new file mode 100644 index 000000000..dbce0f9b3 --- /dev/null +++ b/domain/consensus/database/binaryserialization/selected_chain.go @@ -0,0 +1,15 @@ +package binaryserialization + +import "encoding/binary" + +// SerializeChainBlockIndex serializes chain block index +func SerializeChainBlockIndex(index uint64) []byte { + var keyBytes [8]byte + binary.LittleEndian.PutUint64(keyBytes[:], index) + return keyBytes[:] +} + +// DeserializeChainBlockIndex deserializes chain block index to uint64 +func DeserializeChainBlockIndex(indexBytes []byte) uint64 { + return binary.LittleEndian.Uint64(indexBytes) +} diff --git a/domain/consensus/datastructures/headersselectedchainstore/headersselectedchainstore.go b/domain/consensus/datastructures/headersselectedchainstore/headersselectedchainstore.go new file mode 100644 index 000000000..34406ec91 --- /dev/null +++ b/domain/consensus/datastructures/headersselectedchainstore/headersselectedchainstore.go @@ -0,0 +1,230 @@ +package headersselectedchainstore + +import ( + "encoding/binary" + "github.com/kaspanet/kaspad/domain/consensus/database/binaryserialization" + "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/lrucache" + "github.com/kaspanet/kaspad/domain/consensus/utils/lrucacheuint64tohash" + "github.com/kaspanet/kaspad/infrastructure/db/database" + "github.com/pkg/errors" +) + +var bucketChainBlockHashByIndex = dbkeys.MakeBucket([]byte("chain-block-hash-by-index")) +var bucketChainBlockIndexByHash = dbkeys.MakeBucket([]byte("chain-block-index-by-hash")) +var highestChainBlockIndexKey = dbkeys.MakeBucket().Key([]byte("highest-chain-block-index")) + +type headersSelectedChainStore struct { + stagingAddedByHash map[externalapi.DomainHash]uint64 + stagingRemovedByHash map[externalapi.DomainHash]struct{} + stagingAddedByIndex map[uint64]*externalapi.DomainHash + stagingRemovedByIndex map[uint64]struct{} + cacheByIndex *lrucacheuint64tohash.LRUCache + cacheByHash *lrucache.LRUCache + cacheHighestChainBlockIndex uint64 +} + +// New instantiates a new HeadersSelectedChainStore +func New(cacheSize int) model.HeadersSelectedChainStore { + return &headersSelectedChainStore{ + stagingAddedByHash: make(map[externalapi.DomainHash]uint64), + stagingRemovedByHash: make(map[externalapi.DomainHash]struct{}), + stagingAddedByIndex: make(map[uint64]*externalapi.DomainHash), + stagingRemovedByIndex: make(map[uint64]struct{}), + cacheByIndex: lrucacheuint64tohash.New(cacheSize), + cacheByHash: lrucache.New(cacheSize), + } +} + +// Stage stages the given chain changes +func (hscs *headersSelectedChainStore) Stage(dbContext model.DBReader, + chainChanges *externalapi.SelectedChainPath) error { + + if hscs.IsStaged() { + return errors.Errorf("can't stage when there's already staged data") + } + + for _, blockHash := range chainChanges.Removed { + index, err := hscs.GetIndexByHash(dbContext, blockHash) + if err != nil { + return err + } + + hscs.stagingRemovedByIndex[index] = struct{}{} + hscs.stagingRemovedByHash[*blockHash] = struct{}{} + } + + currentIndex := uint64(0) + highestChainBlockIndex, exists, err := hscs.highestChainBlockIndex(dbContext) + if err != nil { + return err + } + + if exists { + currentIndex = highestChainBlockIndex - uint64(len(chainChanges.Removed)) + 1 + } + + for _, blockHash := range chainChanges.Added { + hscs.stagingAddedByIndex[currentIndex] = blockHash + hscs.stagingAddedByHash[*blockHash] = currentIndex + currentIndex++ + } + + return nil +} + +func (hscs *headersSelectedChainStore) IsStaged() bool { + return len(hscs.stagingAddedByHash) != 0 || + len(hscs.stagingRemovedByHash) != 0 || + len(hscs.stagingAddedByIndex) != 0 || + len(hscs.stagingAddedByIndex) != 0 +} + +func (hscs *headersSelectedChainStore) Discard() { + hscs.stagingAddedByHash = make(map[externalapi.DomainHash]uint64) + hscs.stagingRemovedByHash = make(map[externalapi.DomainHash]struct{}) + hscs.stagingAddedByIndex = make(map[uint64]*externalapi.DomainHash) + hscs.stagingRemovedByIndex = make(map[uint64]struct{}) +} + +func (hscs *headersSelectedChainStore) Commit(dbTx model.DBTransaction) error { + if !hscs.IsStaged() { + return nil + } + + for hash := range hscs.stagingRemovedByHash { + hashCopy := hash + err := dbTx.Delete(hscs.hashAsKey(&hashCopy)) + if err != nil { + return err + } + hscs.cacheByHash.Remove(&hashCopy) + } + + for index := range hscs.stagingRemovedByIndex { + err := dbTx.Delete(hscs.indexAsKey(index)) + if err != nil { + return err + } + hscs.cacheByIndex.Remove(index) + } + + highestIndex := uint64(0) + for hash, index := range hscs.stagingAddedByHash { + hashCopy := hash + err := dbTx.Put(hscs.hashAsKey(&hashCopy), hscs.serializeIndex(index)) + if err != nil { + return err + } + + err = dbTx.Put(hscs.indexAsKey(index), binaryserialization.SerializeHash(&hashCopy)) + if err != nil { + return err + } + + hscs.cacheByHash.Add(&hashCopy, index) + hscs.cacheByIndex.Add(index, &hashCopy) + + if index > highestIndex { + highestIndex = index + } + } + + err := dbTx.Put(highestChainBlockIndexKey, hscs.serializeIndex(highestIndex)) + if err != nil { + return err + } + + hscs.cacheHighestChainBlockIndex = highestIndex + + hscs.Discard() + return nil +} + +// Get gets the chain block index for the given blockHash +func (hscs *headersSelectedChainStore) GetIndexByHash(dbContext model.DBReader, blockHash *externalapi.DomainHash) (uint64, error) { + if index, ok := hscs.stagingAddedByHash[*blockHash]; ok { + return index, nil + } + + if _, ok := hscs.stagingRemovedByHash[*blockHash]; ok { + return 0, errors.Wrapf(database.ErrNotFound, "couldn't find block %s", blockHash) + } + + if index, ok := hscs.cacheByHash.Get(blockHash); ok { + return index.(uint64), nil + } + + indexBytes, err := dbContext.Get(hscs.hashAsKey(blockHash)) + if err != nil { + return 0, err + } + + index := hscs.deserializeIndex(indexBytes) + hscs.cacheByHash.Add(blockHash, index) + return index, nil +} + +func (hscs *headersSelectedChainStore) GetHashByIndex(dbContext model.DBReader, index uint64) (*externalapi.DomainHash, error) { + if blockHash, ok := hscs.stagingAddedByIndex[index]; ok { + return blockHash, nil + } + + if _, ok := hscs.stagingRemovedByIndex[index]; ok { + return nil, errors.Wrapf(database.ErrNotFound, "couldn't find chain block with index %d", index) + } + + if blockHash, ok := hscs.cacheByIndex.Get(index); ok { + return blockHash, nil + } + + hashBytes, err := dbContext.Get(hscs.indexAsKey(index)) + if err != nil { + return nil, err + } + + blockHash, err := binaryserialization.DeserializeHash(hashBytes) + if err != nil { + return nil, err + } + hscs.cacheByIndex.Add(index, blockHash) + return blockHash, nil +} + +func (hscs *headersSelectedChainStore) serializeIndex(index uint64) []byte { + return binaryserialization.SerializeChainBlockIndex(index) +} + +func (hscs *headersSelectedChainStore) deserializeIndex(indexBytes []byte) uint64 { + return binaryserialization.DeserializeChainBlockIndex(indexBytes) +} + +func (hscs *headersSelectedChainStore) hashAsKey(hash *externalapi.DomainHash) model.DBKey { + return bucketChainBlockIndexByHash.Key(hash.ByteSlice()) +} + +func (hscs *headersSelectedChainStore) indexAsKey(index uint64) model.DBKey { + var keyBytes [8]byte + binary.BigEndian.PutUint64(keyBytes[:], index) + return bucketChainBlockHashByIndex.Key(keyBytes[:]) +} + +func (hscs *headersSelectedChainStore) highestChainBlockIndex(dbContext model.DBReader) (uint64, bool, error) { + if hscs.cacheHighestChainBlockIndex != 0 { + return hscs.cacheHighestChainBlockIndex, true, nil + } + + indexBytes, err := dbContext.Get(highestChainBlockIndexKey) + if err != nil { + if errors.Is(err, database.ErrNotFound) { + return 0, false, nil + } + return 0, false, err + } + + index := hscs.deserializeIndex(indexBytes) + hscs.cacheHighestChainBlockIndex = index + return index, true, nil +} diff --git a/domain/consensus/factory.go b/domain/consensus/factory.go index f831a9a90..20608919f 100644 --- a/domain/consensus/factory.go +++ b/domain/consensus/factory.go @@ -1,6 +1,7 @@ package consensus import ( + "github.com/kaspanet/kaspad/domain/consensus/datastructures/headersselectedchainstore" "io/ioutil" "os" "sync" @@ -89,6 +90,7 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat ghostdagDataStore := ghostdagdatastore.New(pruningWindowSizeForCaches) headersSelectedTipStore := headersselectedtipstore.New() finalityStore := finalitystore.New(200) + headersSelectedChainStore := headersselectedchainstore.New(pruningWindowSizeForCaches) // Processes reachabilityManager := reachabilitymanager.New( @@ -148,7 +150,8 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat dagParams.CoinbasePayloadScriptPublicKeyMaxLength, ghostdagDataStore, acceptanceDataStore) - headerTipsManager := headersselectedtipmanager.New(dbManager, dagTopologyManager, ghostdagManager, headersSelectedTipStore) + headerTipsManager := headersselectedtipmanager.New(dbManager, dagTopologyManager, dagTraversalManager, + ghostdagManager, headersSelectedTipStore, headersSelectedChainStore) genesisHash := dagParams.GenesisHash finalityManager := finalitymanager.New( dbManager, @@ -258,7 +261,8 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat blockStatusStore, blockHeaderStore, blockStore, - pruningStore) + pruningStore, + headersSelectedChainStore) blockBuilder := blockbuilder.New( dbManager, @@ -300,7 +304,8 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat utxoDiffStore, blockHeaderStore, headersSelectedTipStore, - finalityStore) + finalityStore, + headersSelectedChainStore) c := &consensus{ lock: &sync.Mutex{}, @@ -324,19 +329,20 @@ 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, - headersSelectedTipStore: headersSelectedTipStore, - 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, + headersSelectedChainStore: headersSelectedChainStore, } genesisInfo, err := c.GetBlockInfo(genesisHash) diff --git a/domain/consensus/model/externalapi/consensus.go b/domain/consensus/model/externalapi/consensus.go index 8921d9a28..535d99664 100644 --- a/domain/consensus/model/externalapi/consensus.go +++ b/domain/consensus/model/externalapi/consensus.go @@ -18,12 +18,13 @@ type Consensus interface { ValidateAndInsertPruningPoint(newPruningPoint *DomainBlock, serializedUTXOSet []byte) error GetVirtualSelectedParent() (*DomainHash, error) CreateBlockLocator(lowHash, highHash *DomainHash, limit uint32) (BlockLocator, error) + CreateHeadersSelectedChainBlockLocator(lowHash, highHash *DomainHash) (BlockLocator, error) FindNextBlockLocatorBoundaries(blockLocator BlockLocator) (lowHash, highHash *DomainHash, err error) GetSyncInfo() (*SyncInfo, error) Tips() ([]*DomainHash, error) GetVirtualInfo() (*VirtualInfo, error) IsValidPruningPoint(blockHash *DomainHash) (bool, error) - GetVirtualSelectedParentChainFromBlock(blockHash *DomainHash) (*SelectedParentChainChanges, error) + GetVirtualSelectedParentChainFromBlock(blockHash *DomainHash) (*SelectedChainPath, error) IsInSelectedParentChainOf(blockHashA *DomainHash, blockHashB *DomainHash) (bool, error) GetHeadersSelectedTip() (*DomainHash, error) } diff --git a/domain/consensus/model/externalapi/insertblockresult.go b/domain/consensus/model/externalapi/insertblockresult.go index 48379a32d..f90960aad 100644 --- a/domain/consensus/model/externalapi/insertblockresult.go +++ b/domain/consensus/model/externalapi/insertblockresult.go @@ -2,11 +2,11 @@ package externalapi // BlockInsertionResult is auxiliary data returned from ValidateAndInsertBlock type BlockInsertionResult struct { - VirtualSelectedParentChainChanges *SelectedParentChainChanges + VirtualSelectedParentChainChanges *SelectedChainPath } -// SelectedParentChainChanges is the set of changes made to the selected parent chain -type SelectedParentChainChanges struct { +// SelectedChainPath is a path the of the selected chains between two blocks. +type SelectedChainPath struct { Added []*DomainHash Removed []*DomainHash } diff --git a/domain/consensus/model/interface_datastructures_headersselectedchainstore.go b/domain/consensus/model/interface_datastructures_headersselectedchainstore.go new file mode 100644 index 000000000..60c916c84 --- /dev/null +++ b/domain/consensus/model/interface_datastructures_headersselectedchainstore.go @@ -0,0 +1,13 @@ +package model + +import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" + +// HeadersSelectedChainStore represents a store of the headers selected chain +type HeadersSelectedChainStore interface { + Store + Stage(dbContext DBReader, + chainChanges *externalapi.SelectedChainPath) error + IsStaged() bool + GetIndexByHash(dbContext DBReader, blockHash *externalapi.DomainHash) (uint64, error) + GetHashByIndex(dbContext DBReader, index uint64) (*externalapi.DomainHash, error) +} diff --git a/domain/consensus/model/interface_processes_consensusstatemanager.go b/domain/consensus/model/interface_processes_consensusstatemanager.go index be966c26d..d9a74b1ee 100644 --- a/domain/consensus/model/interface_processes_consensusstatemanager.go +++ b/domain/consensus/model/interface_processes_consensusstatemanager.go @@ -4,10 +4,10 @@ import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" // ConsensusStateManager manages the node's consensus state type ConsensusStateManager interface { - AddBlock(blockHash *externalapi.DomainHash) (*externalapi.SelectedParentChainChanges, error) + AddBlock(blockHash *externalapi.DomainHash) (*externalapi.SelectedChainPath, error) PopulateTransactionWithUTXOEntries(transaction *externalapi.DomainTransaction) error UpdatePruningPoint(newPruningPoint *externalapi.DomainBlock, serializedUTXOSet []byte) error RestorePastUTXOSetIterator(blockHash *externalapi.DomainHash) (ReadOnlyUTXOSetIterator, error) CalculatePastUTXOAndAcceptanceData(blockHash *externalapi.DomainHash) (UTXODiff, externalapi.AcceptanceData, Multiset, error) - GetVirtualSelectedParentChainFromBlock(blockHash *externalapi.DomainHash) (*externalapi.SelectedParentChainChanges, error) + GetVirtualSelectedParentChainFromBlock(blockHash *externalapi.DomainHash) (*externalapi.SelectedChainPath, error) } diff --git a/domain/consensus/model/interface_processes_dagtraversalmanager.go b/domain/consensus/model/interface_processes_dagtraversalmanager.go index f020760af..9121ba263 100644 --- a/domain/consensus/model/interface_processes_dagtraversalmanager.go +++ b/domain/consensus/model/interface_processes_dagtraversalmanager.go @@ -13,4 +13,6 @@ type DAGTraversalManager interface { BlueWindow(highHash *externalapi.DomainHash, windowSize int) ([]*externalapi.DomainHash, error) NewDownHeap() BlockHeap NewUpHeap() BlockHeap + CalculateChainPath( + fromBlockHash, toBlockHash *externalapi.DomainHash) (*externalapi.SelectedChainPath, error) } diff --git a/domain/consensus/model/interface_processes_syncmanager.go b/domain/consensus/model/interface_processes_syncmanager.go index 925497066..5596f98e7 100644 --- a/domain/consensus/model/interface_processes_syncmanager.go +++ b/domain/consensus/model/interface_processes_syncmanager.go @@ -7,6 +7,7 @@ type SyncManager interface { GetHashesBetween(lowHash, highHash *externalapi.DomainHash, maxBlueScoreDifference uint64) ([]*externalapi.DomainHash, error) GetMissingBlockBodyHashes(highHash *externalapi.DomainHash) ([]*externalapi.DomainHash, error) CreateBlockLocator(lowHash, highHash *externalapi.DomainHash, limit uint32) (externalapi.BlockLocator, error) + CreateHeadersSelectedChainBlockLocator(lowHash, highHash *externalapi.DomainHash) (externalapi.BlockLocator, error) FindNextBlockLocatorBoundaries(blockLocator externalapi.BlockLocator) (lowHash, highHash *externalapi.DomainHash, err error) GetSyncInfo() (*externalapi.SyncInfo, error) } diff --git a/domain/consensus/model/testapi/test_consensus.go b/domain/consensus/model/testapi/test_consensus.go index c36a69a36..b1ec12320 100644 --- a/domain/consensus/model/testapi/test_consensus.go +++ b/domain/consensus/model/testapi/test_consensus.go @@ -40,6 +40,7 @@ type TestConsensus interface { PruningStore() model.PruningStore ReachabilityDataStore() model.ReachabilityDataStore UTXODiffStore() model.UTXODiffStore + HeadersSelectedChainStore() model.HeadersSelectedChainStore BlockBuilder() TestBlockBuilder BlockProcessor() model.BlockProcessor diff --git a/domain/consensus/processes/blockprocessor/blockprocessor.go b/domain/consensus/processes/blockprocessor/blockprocessor.go index cb007a39a..c21817150 100644 --- a/domain/consensus/processes/blockprocessor/blockprocessor.go +++ b/domain/consensus/processes/blockprocessor/blockprocessor.go @@ -24,19 +24,20 @@ type blockProcessor struct { 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 - headersSelectedTipStore model.HeaderSelectedTipStore - 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 + headersSelectedChainStore model.HeadersSelectedChainStore stores []model.Store } @@ -70,6 +71,7 @@ func New( blockHeaderStore model.BlockHeaderStore, headersSelectedTipStore model.HeaderSelectedTipStore, finalityStore model.FinalityStore, + headersSelectedChainStore model.HeadersSelectedChainStore, ) model.BlockProcessor { return &blockProcessor{ @@ -86,20 +88,21 @@ 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, - headersSelectedTipStore: headersSelectedTipStore, - 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, + headersSelectedChainStore: headersSelectedChainStore, stores: []model.Store{ consensusStateStore, @@ -116,6 +119,7 @@ func New( blockHeaderStore, headersSelectedTipStore, finalityStore, + headersSelectedChainStore, }, } } diff --git a/domain/consensus/processes/blockprocessor/validateandinsertblock.go b/domain/consensus/processes/blockprocessor/validateandinsertblock.go index b95cf82ef..778966bbf 100644 --- a/domain/consensus/processes/blockprocessor/validateandinsertblock.go +++ b/domain/consensus/processes/blockprocessor/validateandinsertblock.go @@ -81,7 +81,7 @@ func (bp *blockProcessor) validateAndInsertBlock(block *externalapi.DomainBlock, return nil, err } - var selectedParentChainChanges *externalapi.SelectedParentChainChanges + var selectedParentChainChanges *externalapi.SelectedChainPath isHeaderOnlyBlock := isHeaderOnlyBlock(block) if !isHeaderOnlyBlock { // There's no need to update the consensus state manager when diff --git a/domain/consensus/processes/coinbasemanager/payload.go b/domain/consensus/processes/coinbasemanager/payload.go index a5ad83c7d..ee98d371d 100644 --- a/domain/consensus/processes/coinbasemanager/payload.go +++ b/domain/consensus/processes/coinbasemanager/payload.go @@ -9,8 +9,6 @@ import ( "github.com/pkg/errors" ) -var byteOrder = binary.LittleEndian - const uint64Len = 8 const uint16Len = 2 const lengthOfscriptPubKeyLength = 1 @@ -25,7 +23,7 @@ func (c *coinbaseManager) serializeCoinbasePayload(blueScore uint64, coinbaseDat } payload := make([]byte, uint64Len+lengthOfVersionScriptPubKey+lengthOfscriptPubKeyLength+scriptLengthOfScriptPubKey+len(coinbaseData.ExtraData)) - byteOrder.PutUint64(payload[:uint64Len], blueScore) + binary.LittleEndian.PutUint64(payload[:uint64Len], blueScore) if len(coinbaseData.ScriptPublicKey.Script) > math.MaxUint8 { return nil, errors.Errorf("script public key is bigger than %d", math.MaxUint8) } @@ -47,7 +45,7 @@ func (c *coinbaseManager) ExtractCoinbaseDataAndBlueScore(coinbaseTx *externalap "coinbase payload is less than the minimum length of %d", minLength) } - blueScore = byteOrder.Uint64(coinbaseTx.Payload[:uint64Len]) + blueScore = binary.LittleEndian.Uint64(coinbaseTx.Payload[:uint64Len]) scriptPubKeyVersion := uint16(coinbaseTx.Payload[uint64Len]) scriptPubKeyScriptLength := coinbaseTx.Payload[uint64Len+lengthOfVersionScriptPubKey] diff --git a/domain/consensus/processes/consensusstatemanager/add_block_to_virtual.go b/domain/consensus/processes/consensusstatemanager/add_block_to_virtual.go index 4546c1c8f..104c3d101 100644 --- a/domain/consensus/processes/consensusstatemanager/add_block_to_virtual.go +++ b/domain/consensus/processes/consensusstatemanager/add_block_to_virtual.go @@ -9,7 +9,7 @@ import ( // AddBlock 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) AddBlock(blockHash *externalapi.DomainHash) (*externalapi.SelectedParentChainChanges, error) { +func (csm *consensusStateManager) AddBlock(blockHash *externalapi.DomainHash) (*externalapi.SelectedChainPath, error) { logger.LogAndMeasureExecutionTime(log, "csm.AddBlock") log.Debugf("Resolving whether the block %s is the next virtual selected parent", blockHash) diff --git a/domain/consensus/processes/consensusstatemanager/find_selected_parent_chain_changes.go b/domain/consensus/processes/consensusstatemanager/find_selected_parent_chain_changes.go index d27b92e4c..caaa1abcb 100644 --- a/domain/consensus/processes/consensusstatemanager/find_selected_parent_chain_changes.go +++ b/domain/consensus/processes/consensusstatemanager/find_selected_parent_chain_changes.go @@ -6,7 +6,7 @@ import ( ) func (csm *consensusStateManager) GetVirtualSelectedParentChainFromBlock( - blockHash *externalapi.DomainHash) (*externalapi.SelectedParentChainChanges, error) { + blockHash *externalapi.DomainHash) (*externalapi.SelectedChainPath, error) { // Calculate chain changes between the given blockHash and the // virtual's selected parent. Note that we explicitly don't @@ -18,55 +18,5 @@ func (csm *consensusStateManager) GetVirtualSelectedParentChainFromBlock( } virtualSelectedParent := virtualGHOSTDAGData.SelectedParent() - return csm.calculateSelectedParentChainChanges(blockHash, virtualSelectedParent) -} - -func (csm *consensusStateManager) calculateSelectedParentChainChanges( - fromBlockHash, toBlockHash *externalapi.DomainHash) (*externalapi.SelectedParentChainChanges, error) { - - // Walk down from fromBlockHash until we reach the common selected - // parent chain ancestor of fromBlockHash and toBlockHash. Note - // that this slice will be empty if fromBlockHash is the selected - // parent of toBlockHash - var removed []*externalapi.DomainHash - current := fromBlockHash - for { - isCurrentInTheSelectedParentChainOfNewVirtualSelectedParent, err := csm.dagTopologyManager.IsInSelectedParentChainOf(current, toBlockHash) - if err != nil { - return nil, err - } - if isCurrentInTheSelectedParentChainOfNewVirtualSelectedParent { - break - } - removed = append(removed, current) - - currentGHOSTDAGData, err := csm.ghostdagDataStore.Get(csm.databaseContext, current) - if err != nil { - return nil, err - } - current = currentGHOSTDAGData.SelectedParent() - } - commonAncestor := current - - // Walk down from the toBlockHash to the common ancestor - var added []*externalapi.DomainHash - current = toBlockHash - for !current.Equal(commonAncestor) { - added = append(added, current) - currentGHOSTDAGData, err := csm.ghostdagDataStore.Get(csm.databaseContext, current) - if err != nil { - return nil, err - } - current = currentGHOSTDAGData.SelectedParent() - } - - // Reverse the order of `added` so that it's sorted from low hash to high hash - for i, j := 0, len(added)-1; i < j; i, j = i+1, j-1 { - added[i], added[j] = added[j], added[i] - } - - return &externalapi.SelectedParentChainChanges{ - Added: added, - Removed: removed, - }, nil + return csm.dagTraversalManager.CalculateChainPath(blockHash, virtualSelectedParent) } diff --git a/domain/consensus/processes/consensusstatemanager/find_selected_parent_chain_changes_test.go b/domain/consensus/processes/consensusstatemanager/find_selected_parent_chain_changes_test.go index cbeb6ea87..12313e634 100644 --- a/domain/consensus/processes/consensusstatemanager/find_selected_parent_chain_changes_test.go +++ b/domain/consensus/processes/consensusstatemanager/find_selected_parent_chain_changes_test.go @@ -10,10 +10,10 @@ import ( "github.com/kaspanet/kaspad/domain/dagconfig" ) -func TestCalculateSelectedParentChainChanges(t *testing.T) { +func TestCalculateChainPath(t *testing.T) { testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) { factory := consensus.NewFactory() - consensus, teardown, err := factory.NewTestConsensus(params, false, "TestCalculateSelectedParentChainChanges") + consensus, teardown, err := factory.NewTestConsensus(params, false, "TestCalculateChainPath") if err != nil { t.Fatalf("Error setting up consensus: %+v", err) } diff --git a/domain/consensus/processes/consensusstatemanager/update_virtual.go b/domain/consensus/processes/consensusstatemanager/update_virtual.go index a2752bcfa..503f667c5 100644 --- a/domain/consensus/processes/consensusstatemanager/update_virtual.go +++ b/domain/consensus/processes/consensusstatemanager/update_virtual.go @@ -6,7 +6,7 @@ import ( ) func (csm *consensusStateManager) updateVirtual(newBlockHash *externalapi.DomainHash, - tips []*externalapi.DomainHash) (*externalapi.SelectedParentChainChanges, error) { + tips []*externalapi.DomainHash) (*externalapi.SelectedChainPath, error) { log.Debugf("updateVirtual start for block %s", newBlockHash) defer log.Debugf("updateVirtual end for block %s", newBlockHash) @@ -64,14 +64,15 @@ func (csm *consensusStateManager) updateVirtual(newBlockHash *externalapi.Domain } log.Debugf("Calculating selected parent chain changes") - var selectedParentChainChanges *externalapi.SelectedParentChainChanges + var selectedParentChainChanges *externalapi.SelectedChainPath if !newBlockHash.Equal(csm.genesisHash) { newVirtualGHOSTDAGData, err := csm.ghostdagDataStore.Get(csm.databaseContext, model.VirtualBlockHash) if err != nil { return nil, err } newVirtualSelectedParent := newVirtualGHOSTDAGData.SelectedParent() - selectedParentChainChanges, err = csm.calculateSelectedParentChainChanges(oldVirtualSelectedParent, newVirtualSelectedParent) + selectedParentChainChanges, err = csm.dagTraversalManager. + CalculateChainPath(oldVirtualSelectedParent, newVirtualSelectedParent) if err != nil { return nil, err } diff --git a/domain/consensus/processes/dagtraversalmanager/dagtraversalmanager.go b/domain/consensus/processes/dagtraversalmanager/dagtraversalmanager.go index f054f0beb..27aaa1145 100644 --- a/domain/consensus/processes/dagtraversalmanager/dagtraversalmanager.go +++ b/domain/consensus/processes/dagtraversalmanager/dagtraversalmanager.go @@ -145,3 +145,53 @@ func (dtm *dagTraversalManager) LowestChainBlockAboveOrEqualToBlueScore(highHash return currentHash, nil } + +func (dtm *dagTraversalManager) CalculateChainPath( + fromBlockHash, toBlockHash *externalapi.DomainHash) (*externalapi.SelectedChainPath, error) { + + // Walk down from fromBlockHash until we reach the common selected + // parent chain ancestor of fromBlockHash and toBlockHash. Note + // that this slice will be empty if fromBlockHash is the selected + // parent of toBlockHash + var removed []*externalapi.DomainHash + current := fromBlockHash + for { + isCurrentInTheSelectedParentChainOfNewVirtualSelectedParent, err := dtm.dagTopologyManager.IsInSelectedParentChainOf(current, toBlockHash) + if err != nil { + return nil, err + } + if isCurrentInTheSelectedParentChainOfNewVirtualSelectedParent { + break + } + removed = append(removed, current) + + currentGHOSTDAGData, err := dtm.ghostdagDataStore.Get(dtm.databaseContext, current) + if err != nil { + return nil, err + } + current = currentGHOSTDAGData.SelectedParent() + } + commonAncestor := current + + // Walk down from the toBlockHash to the common ancestor + var added []*externalapi.DomainHash + current = toBlockHash + for !current.Equal(commonAncestor) { + added = append(added, current) + currentGHOSTDAGData, err := dtm.ghostdagDataStore.Get(dtm.databaseContext, current) + if err != nil { + return nil, err + } + current = currentGHOSTDAGData.SelectedParent() + } + + // Reverse the order of `added` so that it's sorted from low hash to high hash + for i, j := 0, len(added)-1; i < j; i, j = i+1, j-1 { + added[i], added[j] = added[j], added[i] + } + + return &externalapi.SelectedChainPath{ + Added: added, + Removed: removed, + }, nil +} diff --git a/domain/consensus/processes/headersselectedtipmanager/headersselectedtipmanager.go b/domain/consensus/processes/headersselectedtipmanager/headersselectedtipmanager.go new file mode 100644 index 000000000..c62a1646b --- /dev/null +++ b/domain/consensus/processes/headersselectedtipmanager/headersselectedtipmanager.go @@ -0,0 +1,79 @@ +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 + dagTraversalManager model.DAGTraversalManager + ghostdagManager model.GHOSTDAGManager + headersSelectedTipStore model.HeaderSelectedTipStore + headersSelectedChainStore model.HeadersSelectedChainStore +} + +// New instantiates a new HeadersSelectedTipManager +func New(databaseContext model.DBReader, + dagTopologyManager model.DAGTopologyManager, + dagTraversalManager model.DAGTraversalManager, + ghostdagManager model.GHOSTDAGManager, + headersSelectedTipStore model.HeaderSelectedTipStore, + headersSelectedChainStore model.HeadersSelectedChainStore) model.HeadersSelectedTipManager { + + return &headerTipsManager{ + databaseContext: databaseContext, + dagTopologyManager: dagTopologyManager, + dagTraversalManager: dagTraversalManager, + ghostdagManager: ghostdagManager, + headersSelectedTipStore: headersSelectedTipStore, + headersSelectedChainStore: headersSelectedChainStore, + } +} + +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) + + err := h.headersSelectedChainStore.Stage(h.databaseContext, &externalapi.SelectedChainPath{ + Added: []*externalapi.DomainHash{hash}, + Removed: nil, + }) + if err != nil { + return err + } + } 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.Equal(headersSelectedTip) { + h.headersSelectedTipStore.Stage(newHeadersSelectedTip) + + chainChanges, err := h.dagTraversalManager.CalculateChainPath(headersSelectedTip, newHeadersSelectedTip) + if err != nil { + return err + } + + err = h.headersSelectedChainStore.Stage(h.databaseContext, chainChanges) + if err != nil { + return err + } + } + } + + return nil +} diff --git a/domain/consensus/processes/headersselectedtipmanager/headersselectedtipmanager_test.go b/domain/consensus/processes/headersselectedtipmanager/headersselectedtipmanager_test.go new file mode 100644 index 000000000..ce83241f4 --- /dev/null +++ b/domain/consensus/processes/headersselectedtipmanager/headersselectedtipmanager_test.go @@ -0,0 +1,77 @@ +package headersselectedtipmanager_test + +import ( + "github.com/kaspanet/kaspad/domain/consensus" + "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" + "github.com/kaspanet/kaspad/domain/consensus/utils/testutils" + "github.com/kaspanet/kaspad/domain/dagconfig" + "github.com/kaspanet/kaspad/infrastructure/db/database" + "github.com/pkg/errors" + "testing" +) + +func TestAddHeaderTip(t *testing.T) { + testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) { + factory := consensus.NewFactory() + tc, tearDown, err := factory.NewTestConsensus(params, false, "TestAddHeaderTip") + if err != nil { + t.Fatalf("NewTestConsensus: %s", err) + } + defer tearDown(false) + + checkExpectedSelectedChain := func(expectedSelectedChain []*externalapi.DomainHash) { + for i, blockHash := range expectedSelectedChain { + chainBlockHash, err := tc.HeadersSelectedChainStore().GetHashByIndex(tc.DatabaseContext(), uint64(i)) + if err != nil { + t.Fatalf("GetHashByIndex: %+v", err) + } + + if !blockHash.Equal(chainBlockHash) { + t.Fatalf("chain block %d is expected to be %s but got %s", i, blockHash, chainBlockHash) + } + + index, err := tc.HeadersSelectedChainStore().GetIndexByHash(tc.DatabaseContext(), blockHash) + if err != nil { + t.Fatalf("GetIndexByHash: %+v", err) + } + + if uint64(i) != index { + t.Fatalf("chain block %s is expected to be %d but got %d", blockHash, i, index) + } + } + + _, err := tc.HeadersSelectedChainStore().GetHashByIndex(tc.DatabaseContext(), + uint64(len(expectedSelectedChain)+1)) + if !errors.Is(err, database.ErrNotFound) { + t.Fatalf("index %d is not expected to exist, but instead got error: %+v", + uint64(len(expectedSelectedChain)+1), err) + } + } + + expectedSelectedChain := []*externalapi.DomainHash{params.GenesisHash} + tipHash := params.GenesisHash + for i := 0; i < 10; i++ { + var err error + tipHash, _, err = tc.AddBlock([]*externalapi.DomainHash{tipHash}, nil, nil) + if err != nil { + t.Fatalf("AddBlock: %+v", err) + } + + expectedSelectedChain = append(expectedSelectedChain, tipHash) + checkExpectedSelectedChain(expectedSelectedChain) + } + + expectedSelectedChain = []*externalapi.DomainHash{params.GenesisHash} + tipHash = params.GenesisHash + for i := 0; i < 11; i++ { + var err error + tipHash, _, err = tc.AddBlock([]*externalapi.DomainHash{tipHash}, nil, nil) + if err != nil { + t.Fatalf("AddBlock: %+v", err) + } + + expectedSelectedChain = append(expectedSelectedChain, tipHash) + } + checkExpectedSelectedChain(expectedSelectedChain) + }) +} diff --git a/domain/consensus/processes/headersselectedtipmanager/headertipsmanager.go b/domain/consensus/processes/headersselectedtipmanager/headertipsmanager.go deleted file mode 100644 index f4bb6f16b..000000000 --- a/domain/consensus/processes/headersselectedtipmanager/headertipsmanager.go +++ /dev/null @@ -1,55 +0,0 @@ -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.Equal(headersSelectedTip) { - h.headersSelectedTipStore.Stage(newHeadersSelectedTip) - } - } - - return nil -} diff --git a/domain/consensus/processes/syncmanager/blocklocator.go b/domain/consensus/processes/syncmanager/blocklocator.go index 137eaa171..62ec0427a 100644 --- a/domain/consensus/processes/syncmanager/blocklocator.go +++ b/domain/consensus/processes/syncmanager/blocklocator.go @@ -98,3 +98,46 @@ func (sm *syncManager) findNextBlockLocatorBoundaries(blockLocator externalapi.B } return highestKnownHash, lowestUnknownHash, nil } + +func (sm *syncManager) createHeadersSelectedChainBlockLocator(lowHash, + highHash *externalapi.DomainHash) (externalapi.BlockLocator, error) { + + if highHash.Equal(sm.genesisBlockHash) && lowHash.Equal(sm.genesisBlockHash) { + return externalapi.BlockLocator{sm.genesisBlockHash}, nil + } + + lowHashIndex, err := sm.headersSelectedChainStore.GetIndexByHash(sm.databaseContext, lowHash) + if err != nil { + return nil, err + } + + highHashIndex, err := sm.headersSelectedChainStore.GetIndexByHash(sm.databaseContext, highHash) + if err != nil { + return nil, err + } + + if highHashIndex < lowHashIndex { + return nil, errors.Errorf("cannot build block locator while highHash is lower than lowHash") + } + + locator := externalapi.BlockLocator{} + currentIndex := highHashIndex + step := uint64(1) + for currentIndex > lowHashIndex { + blockHash, err := sm.headersSelectedChainStore.GetHashByIndex(sm.databaseContext, currentIndex) + if err != nil { + return nil, err + } + + locator = append(locator, blockHash) + if currentIndex < step { + break + } + + currentIndex -= step + step *= 2 + } + + locator = append(locator, lowHash) + return locator, nil +} diff --git a/domain/consensus/processes/syncmanager/syncmanager.go b/domain/consensus/processes/syncmanager/syncmanager.go index a81faa58b..48ea10d65 100644 --- a/domain/consensus/processes/syncmanager/syncmanager.go +++ b/domain/consensus/processes/syncmanager/syncmanager.go @@ -15,11 +15,12 @@ type syncManager struct { ghostdagManager model.GHOSTDAGManager pruningManager model.PruningManager - ghostdagDataStore model.GHOSTDAGDataStore - blockStatusStore model.BlockStatusStore - blockHeaderStore model.BlockHeaderStore - blockStore model.BlockStore - pruningStore model.PruningStore + ghostdagDataStore model.GHOSTDAGDataStore + blockStatusStore model.BlockStatusStore + blockHeaderStore model.BlockHeaderStore + blockStore model.BlockStore + pruningStore model.PruningStore + headersSelectedChainStore model.HeadersSelectedChainStore } // New instantiates a new SyncManager @@ -35,16 +36,18 @@ func New( blockStatusStore model.BlockStatusStore, blockHeaderStore model.BlockHeaderStore, blockStore model.BlockStore, - pruningStore model.PruningStore) model.SyncManager { + pruningStore model.PruningStore, + headersSelectedChainStore model.HeadersSelectedChainStore) model.SyncManager { return &syncManager{ databaseContext: databaseContext, genesisBlockHash: genesisBlockHash, - dagTraversalManager: dagTraversalManager, - dagTopologyManager: dagTopologyManager, - ghostdagManager: ghostdagManager, - pruningManager: pruningManager, + dagTraversalManager: dagTraversalManager, + dagTopologyManager: dagTopologyManager, + ghostdagManager: ghostdagManager, + pruningManager: pruningManager, + headersSelectedChainStore: headersSelectedChainStore, ghostdagDataStore: ghostdagDataStore, blockStatusStore: blockStatusStore, @@ -77,6 +80,15 @@ func (sm *syncManager) CreateBlockLocator(lowHash, highHash *externalapi.DomainH return sm.createBlockLocator(lowHash, highHash, limit) } +func (sm *syncManager) CreateHeadersSelectedChainBlockLocator(lowHash, + highHash *externalapi.DomainHash) (externalapi.BlockLocator, error) { + + onEnd := logger.LogAndMeasureExecutionTime(log, "CreateHeadersSelectedChainBlockLocator") + defer onEnd() + + return sm.createHeadersSelectedChainBlockLocator(lowHash, highHash) +} + func (sm *syncManager) FindNextBlockLocatorBoundaries(blockLocator externalapi.BlockLocator) (lowHash, highHash *externalapi.DomainHash, err error) { onEnd := logger.LogAndMeasureExecutionTime(log, "FindNextBlockLocatorBoundaries") defer onEnd() diff --git a/domain/consensus/test_consensus.go b/domain/consensus/test_consensus.go index 31481a7d7..7c18b13e7 100644 --- a/domain/consensus/test_consensus.go +++ b/domain/consensus/test_consensus.go @@ -95,4 +95,5 @@ func (tc *testConsensus) DiscardAllStores() { tc.PruningStore().Discard() tc.ReachabilityDataStore().Discard() tc.UTXODiffStore().Discard() + tc.HeadersSelectedChainStore().Discard() } diff --git a/domain/consensus/test_consensus_getters.go b/domain/consensus/test_consensus_getters.go index 8cb6a5219..8811bbb5c 100644 --- a/domain/consensus/test_consensus_getters.go +++ b/domain/consensus/test_consensus_getters.go @@ -133,3 +133,7 @@ func (tc *testConsensus) FinalityManager() model.FinalityManager { func (tc *testConsensus) FinalityStore() model.FinalityStore { return tc.finalityStore } + +func (tc *testConsensus) HeadersSelectedChainStore() model.HeadersSelectedChainStore { + return tc.headersSelectedChainStore +} diff --git a/domain/consensus/utils/lrucacheuint64tohash/lrucacheuint64tohash.go b/domain/consensus/utils/lrucacheuint64tohash/lrucacheuint64tohash.go new file mode 100644 index 000000000..c2317d5f0 --- /dev/null +++ b/domain/consensus/utils/lrucacheuint64tohash/lrucacheuint64tohash.go @@ -0,0 +1,57 @@ +package lrucacheuint64tohash + +import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" + +// LRUCache is a least-recently-used cache from +// uint64 to DomainHash +type LRUCache struct { + cache map[uint64]*externalapi.DomainHash + capacity int +} + +// New creates a new LRUCache +func New(capacity int) *LRUCache { + return &LRUCache{ + cache: make(map[uint64]*externalapi.DomainHash, capacity+1), + capacity: capacity, + } +} + +// Add adds an entry to the LRUCache +func (c *LRUCache) Add(key uint64, value *externalapi.DomainHash) { + c.cache[key] = value + + if len(c.cache) > c.capacity { + c.evictRandom() + } +} + +// Get returns the entry for the given key, or (nil, false) otherwise +func (c *LRUCache) Get(key uint64) (*externalapi.DomainHash, bool) { + value, ok := c.cache[key] + if !ok { + return nil, false + } + return value, true +} + +// Has returns whether the LRUCache contains the given key +func (c *LRUCache) Has(key uint64) bool { + _, ok := c.cache[key] + return ok +} + +// Remove removes the entry for the the given key. Does nothing if +// the entry does not exist +func (c *LRUCache) Remove(key uint64) { + delete(c.cache, key) +} + +func (c *LRUCache) evictRandom() { + var keyToEvict uint64 + for key := range c.cache { + keyToEvict = key + break + } + c.Remove(keyToEvict) +} diff --git a/domain/utxoindex/utxoindex.go b/domain/utxoindex/utxoindex.go index 1d881f882..24b14352c 100644 --- a/domain/utxoindex/utxoindex.go +++ b/domain/utxoindex/utxoindex.go @@ -29,7 +29,7 @@ func New(consensus externalapi.Consensus, database database.Database) *UTXOIndex } // Update updates the UTXO index with the given DAG selected parent chain changes -func (ui *UTXOIndex) Update(chainChanges *externalapi.SelectedParentChainChanges) (*UTXOChanges, error) { +func (ui *UTXOIndex) Update(chainChanges *externalapi.SelectedChainPath) (*UTXOChanges, error) { onEnd := logger.LogAndMeasureExecutionTime(log, "UTXOIndex.Update") defer onEnd()