diff --git a/domain/consensus/consensus.go b/domain/consensus/consensus.go index 27b4aa681..87b3ad6ac 100644 --- a/domain/consensus/consensus.go +++ b/domain/consensus/consensus.go @@ -20,8 +20,9 @@ type Consensus interface { GetPruningPointUTXOSet() ([]byte, error) SetPruningPointUTXOSet(pruningPoint *externalapi.DomainHash, serializedUTXOSet []byte) error GetVirtualSelectedParent() (*externalapi.DomainBlock, error) - CreateBlockLocator(lowHash, highHash *externalapi.DomainHash) (*externalapi.BlockLocator, error) - FindNextBlockLocatorBoundaries(blockLocator *externalapi.BlockLocator) (lowHash, highHash *externalapi.DomainHash, err error) + CreateBlockLocator(lowHash, highHash *externalapi.DomainHash) (externalapi.BlockLocator, error) + FindNextBlockLocatorBoundaries(blockLocator externalapi.BlockLocator) (lowHash, highHash *externalapi.DomainHash, err error) + GetSyncInfo() (*externalapi.SyncInfo, error) } type consensus struct { @@ -106,11 +107,11 @@ func (s *consensus) GetBlockInfo(blockHash *externalapi.DomainHash) (*externalap } blockInfo.BlockStatus = &blockStatus - isBlockHeaderInPruningPointFutureAndVirtualPast, err := s.syncManager.IsBlockHeaderInPruningPointFutureAndVirtualPast(blockHash) + isBlockInHeaderPruningPointFutureAndVirtualPast, err := s.syncManager.IsBlockInHeaderPruningPointFutureAndVirtualPast(blockHash) if err != nil { return nil, err } - blockInfo.IsBlockHeaderInPruningPointFutureAndVirtualPast = isBlockHeaderInPruningPointFutureAndVirtualPast + blockInfo.IsBlockInHeaderPruningPointFutureAndVirtualPast = isBlockInHeaderPruningPointFutureAndVirtualPast return blockInfo, nil } @@ -139,10 +140,14 @@ func (s *consensus) GetVirtualSelectedParent() (*externalapi.DomainBlock, error) return s.GetBlock(virtualGHOSTDAGData.SelectedParent) } -func (s *consensus) CreateBlockLocator(lowHash, highHash *externalapi.DomainHash) (*externalapi.BlockLocator, error) { +func (s *consensus) CreateBlockLocator(lowHash, highHash *externalapi.DomainHash) (externalapi.BlockLocator, error) { return s.syncManager.CreateBlockLocator(lowHash, highHash) } -func (s *consensus) FindNextBlockLocatorBoundaries(blockLocator *externalapi.BlockLocator) (lowHash, highHash *externalapi.DomainHash, err error) { +func (s *consensus) FindNextBlockLocatorBoundaries(blockLocator externalapi.BlockLocator) (lowHash, highHash *externalapi.DomainHash, err error) { return s.syncManager.FindNextBlockLocatorBoundaries(blockLocator) } + +func (s *consensus) GetSyncInfo() (*externalapi.SyncInfo, error) { + return s.syncManager.GetSyncInfo() +} diff --git a/domain/consensus/factory.go b/domain/consensus/factory.go index ade2bc9df..29b27704f 100644 --- a/domain/consensus/factory.go +++ b/domain/consensus/factory.go @@ -183,8 +183,13 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat utxoDiffStore, blockHeaderStore, headerTipsStore) - - syncManager := syncmanager.New(dagTraversalManager) + syncManager := syncmanager.New( + dbManager, + &genesisHash, + dagTraversalManager, + dagTopologyManager, + ghostdagDataStore, + blockStatusStore) return &consensus{ databaseContext: dbManager, diff --git a/domain/consensus/model/externalapi/blockinfo.go b/domain/consensus/model/externalapi/blockinfo.go index e5097a650..97b1a05db 100644 --- a/domain/consensus/model/externalapi/blockinfo.go +++ b/domain/consensus/model/externalapi/blockinfo.go @@ -5,5 +5,5 @@ type BlockInfo struct { Exists bool BlockStatus *BlockStatus - IsBlockHeaderInPruningPointFutureAndVirtualPast bool + IsBlockInHeaderPruningPointFutureAndVirtualPast bool } diff --git a/domain/consensus/model/externalapi/blocklocator.go b/domain/consensus/model/externalapi/blocklocator.go index a1e2c6972..92503d6a4 100644 --- a/domain/consensus/model/externalapi/blocklocator.go +++ b/domain/consensus/model/externalapi/blocklocator.go @@ -1,6 +1,17 @@ package externalapi -// BlockLocator is a type used in the process of locating -// a specific block in a remote DAG -type BlockLocator struct { -} +// BlockLocator is used to help locate a specific block. The algorithm for +// building the block locator is to add block hashes in reverse order on the +// block's selected parent chain until the desired stop block is reached. +// In order to keep the list of locator hashes to a reasonable number of entries, +// the step between each entry is doubled each loop iteration to exponentially +// decrease the number of hashes as a function of the distance from the block +// being located. +// +// For example, assume a selected parent chain with IDs as depicted below, and the +// stop block is genesis: +// genesis -> 1 -> 2 -> ... -> 15 -> 16 -> 17 -> 18 +// +// The block locator for block 17 would be the hashes of blocks: +// [17 16 14 11 7 2 genesis] +type BlockLocator []*DomainHash diff --git a/domain/consensus/model/externalapi/sync.go b/domain/consensus/model/externalapi/sync.go new file mode 100644 index 000000000..3ab5ad277 --- /dev/null +++ b/domain/consensus/model/externalapi/sync.go @@ -0,0 +1,19 @@ +package externalapi + +// Each of the following represent one of the possible sync +// states of the consensus +const ( + SyncStateNormal SyncState = iota + SyncStateMissingUTXOSet + SyncStateHeadersFirst + SyncStateMissingBlockBodies +) + +// SyncState represents the current sync state of the consensus +type SyncState uint8 + +// SyncInfo holds info about the current sync state of the consensus +type SyncInfo struct { + State SyncState + IBDRootUTXOBlockHash *DomainHash +} diff --git a/domain/consensus/model/interface_processes_dagtraversalmanager.go b/domain/consensus/model/interface_processes_dagtraversalmanager.go index 76547d263..14819de59 100644 --- a/domain/consensus/model/interface_processes_dagtraversalmanager.go +++ b/domain/consensus/model/interface_processes_dagtraversalmanager.go @@ -6,6 +6,7 @@ import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" // in the DAG type DAGTraversalManager interface { HighestChainBlockBelowBlueScore(highHash *externalapi.DomainHash, blueScore uint64) (*externalapi.DomainHash, error) + LowestChainBlockAboveOrEqualToBlueScore(highHash *externalapi.DomainHash, blueScore uint64) (*externalapi.DomainHash, error) SelectedParentIterator(highHash *externalapi.DomainHash) SelectedParentIterator BlueWindow(highHash *externalapi.DomainHash, windowSize uint64) ([]*externalapi.DomainHash, error) NewDownHeap() BlockHeap diff --git a/domain/consensus/model/interface_processes_syncmanager.go b/domain/consensus/model/interface_processes_syncmanager.go index 1aed72a72..88586a89b 100644 --- a/domain/consensus/model/interface_processes_syncmanager.go +++ b/domain/consensus/model/interface_processes_syncmanager.go @@ -6,7 +6,8 @@ import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" type SyncManager interface { GetHashesBetween(lowHash, highHash *externalapi.DomainHash) ([]*externalapi.DomainHash, error) GetMissingBlockBodyHashes(highHash *externalapi.DomainHash) ([]*externalapi.DomainHash, error) - CreateBlockLocator(lowHash, highHash *externalapi.DomainHash) (*externalapi.BlockLocator, error) - FindNextBlockLocatorBoundaries(blockLocator *externalapi.BlockLocator) (lowHash, highHash *externalapi.DomainHash, err error) - IsBlockHeaderInPruningPointFutureAndVirtualPast(blockHash *externalapi.DomainHash) (bool, error) + CreateBlockLocator(lowHash, highHash *externalapi.DomainHash) (externalapi.BlockLocator, error) + FindNextBlockLocatorBoundaries(blockLocator externalapi.BlockLocator) (lowHash, highHash *externalapi.DomainHash, err error) + IsBlockInHeaderPruningPointFutureAndVirtualPast(blockHash *externalapi.DomainHash) (bool, error) + GetSyncInfo() (*externalapi.SyncInfo, error) } diff --git a/domain/consensus/processes/dagtraversalmanager/dagtraversalmanager.go b/domain/consensus/processes/dagtraversalmanager/dagtraversalmanager.go index 44c984d3f..bf9fd2562 100644 --- a/domain/consensus/processes/dagtraversalmanager/dagtraversalmanager.go +++ b/domain/consensus/processes/dagtraversalmanager/dagtraversalmanager.go @@ -68,7 +68,6 @@ func (dtm *dagTraversalManager) SelectedParentIterator(highHash *externalapi.Dom // blueScore in the block with the given highHash's selected // parent chain func (dtm *dagTraversalManager) HighestChainBlockBelowBlueScore(highHash *externalapi.DomainHash, blueScore uint64) (*externalapi.DomainHash, error) { - blockHash := highHash chainBlock, err := dtm.ghostdagDataStore.Get(dtm.databaseContext, highHash) if err != nil { @@ -93,3 +92,28 @@ func (dtm *dagTraversalManager) HighestChainBlockBelowBlueScore(highHash *extern } return blockHash, nil } + +func (dtm *dagTraversalManager) LowestChainBlockAboveOrEqualToBlueScore(highHash *externalapi.DomainHash, blueScore uint64) (*externalapi.DomainHash, error) { + highBlockGHOSTDAGData, err := dtm.ghostdagDataStore.Get(dtm.databaseContext, highHash) + if err != nil { + return nil, err + } + + currentHash := highHash + currentBlockGHOSTDAGData := highBlockGHOSTDAGData + iterator := dtm.SelectedParentIterator(highHash) + for iterator.Next() { + selectedParentBlockGHOSTDAGData, err := dtm.ghostdagDataStore.Get(dtm.databaseContext, currentBlockGHOSTDAGData.SelectedParent) + if err != nil { + return nil, err + } + + if selectedParentBlockGHOSTDAGData.BlueScore < blueScore { + break + } + currentHash = selectedParentBlockGHOSTDAGData.SelectedParent + currentBlockGHOSTDAGData = selectedParentBlockGHOSTDAGData + } + + return currentHash, nil +} diff --git a/domain/consensus/processes/syncmanager/antipast.go b/domain/consensus/processes/syncmanager/antipast.go new file mode 100644 index 000000000..f5add3a07 --- /dev/null +++ b/domain/consensus/processes/syncmanager/antipast.go @@ -0,0 +1,114 @@ +package syncmanager + +import ( + "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" + "github.com/kaspanet/kaspad/domain/consensus/utils/hashset" + "github.com/pkg/errors" +) + +const maxHashesInAntiPastHashesBetween = 1 << 17 + +// antiPastHashesBetween returns the hashes of the blocks between the +// lowHash's antiPast and highHash's antiPast, or up to +// maxHashesInAntiPastHashesBetween. +func (sm *syncManager) antiPastHashesBetween(lowHash, highHash *externalapi.DomainHash) ([]*externalapi.DomainHash, error) { + lowBlockGHOSTDAGData, err := sm.ghostdagDataStore.Get(sm.databaseContext, lowHash) + if err != nil { + return nil, err + } + lowBlockBlueScore := lowBlockGHOSTDAGData.BlueScore + highBlockGHOSTDAGData, err := sm.ghostdagDataStore.Get(sm.databaseContext, highHash) + if err != nil { + return nil, err + } + highBlockBlueScore := highBlockGHOSTDAGData.BlueScore + if lowBlockBlueScore >= highBlockBlueScore { + return nil, errors.Errorf("low hash blueScore >= high hash blueScore (%d >= %d)", + lowBlockBlueScore, highBlockBlueScore) + } + + // In order to get no more then maxHashesInAntiPastHashesBetween + // blocks from th future of the lowHash (including itself), + // we iterate the selected parent chain of the highNode and + // stop once we reach + // highBlockBlueScore-lowBlockBlueScore+1 <= maxHashesInAntiPastHashesBetween. + // That stop point becomes the new highHash. + // Using blueScore as an approximation is considered to be + // fairly accurate because we presume that most DAG blocks are + // blue. + for highBlockBlueScore-lowBlockBlueScore+1 > maxHashesInAntiPastHashesBetween { + highHash = highBlockGHOSTDAGData.SelectedParent + } + + // Collect every node in highHash's past (including itself) but + // NOT in the lowHash's past (excluding itself) into an up-heap + // (a heap sorted by blueScore from lowest to greatest). + visited := hashset.New() + candidateHashes := sm.dagTraversalManager.NewUpHeap() + queue := sm.dagTraversalManager.NewDownHeap() + err = queue.Push(highHash) + if err != nil { + return nil, err + } + for queue.Len() > 0 { + current := queue.Pop() + if visited.Contains(current) { + continue + } + visited.Add(current) + var isCurrentAncestorOfLowHash bool + if current == lowHash { + isCurrentAncestorOfLowHash = false + } else { + var err error + isCurrentAncestorOfLowHash, err = sm.dagTopologyManager.IsAncestorOf(current, lowHash) + if err != nil { + return nil, err + } + } + if isCurrentAncestorOfLowHash { + continue + } + err = candidateHashes.Push(current) + if err != nil { + return nil, err + } + parents, err := sm.dagTopologyManager.Parents(current) + if err != nil { + return nil, err + } + for _, parent := range parents { + err := queue.Push(parent) + if err != nil { + return nil, err + } + } + } + + // Pop candidateHashes into a slice. Since candidateHashes is + // an up-heap, it's guaranteed to be ordered from low to high + hashesLength := maxHashesInAntiPastHashesBetween + if candidateHashes.Len() < hashesLength { + hashesLength = candidateHashes.Len() + } + hashes := make([]*externalapi.DomainHash, hashesLength) + for i := 0; i < hashesLength; i++ { + hashes[i] = candidateHashes.Pop() + } + return hashes, nil +} + +func (sm *syncManager) missingBlockBodyHashes(highHash *externalapi.DomainHash) ([]*externalapi.DomainHash, error) { + panic("implement me") +} + +func (sm *syncManager) isBlockInHeaderPruningPointFutureAndVirtualPast(blockHash *externalapi.DomainHash) (bool, error) { + exists, err := sm.blockStatusStore.Exists(sm.databaseContext, blockHash) + if err != nil { + return false, err + } + if !exists { + return false, nil + } + panic("implement me") +} diff --git a/domain/consensus/processes/syncmanager/blocklocator.go b/domain/consensus/processes/syncmanager/blocklocator.go new file mode 100644 index 000000000..1ad2ecd01 --- /dev/null +++ b/domain/consensus/processes/syncmanager/blocklocator.go @@ -0,0 +1,90 @@ +package syncmanager + +import ( + "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" + "github.com/pkg/errors" +) + +// createBlockLocator creates a block locator for the passed high and low hashes. +// See the BlockLocator type comments for more details. +func (sm *syncManager) createBlockLocator(lowHash, highHash *externalapi.DomainHash) (externalapi.BlockLocator, error) { + // We use the selected parent of the high block, so that the + // block locator won't contain it. + highBlockGHOSTDAGData, err := sm.ghostdagDataStore.Get(sm.databaseContext, highHash) + if err != nil { + return nil, err + } + highHash = highBlockGHOSTDAGData.SelectedParent + + lowBlockGHOSTDAGData, err := sm.ghostdagDataStore.Get(sm.databaseContext, lowHash) + if err != nil { + return nil, err + } + lowBlockBlueScore := lowBlockGHOSTDAGData.BlueScore + + currentHash := highHash + step := uint64(1) + locator := make(externalapi.BlockLocator, 0) + for currentHash != nil { + locator = append(locator, currentHash) + + currentBlockGHOSTDAGData, err := sm.ghostdagDataStore.Get(sm.databaseContext, currentHash) + if err != nil { + return nil, err + } + currentBlockBlueScore := currentBlockGHOSTDAGData.BlueScore + + // Nothing more to add once the low node has been added. + if currentBlockBlueScore <= lowBlockBlueScore { + if currentHash != lowHash { + return nil, errors.Errorf("highHash and lowHash are " + + "not in the same selected parent chain.") + } + break + } + + // Calculate blueScore of previous node to include ensuring the + // final node is lowNode. + nextBlueScore := currentBlockBlueScore - step + if nextBlueScore < lowBlockGHOSTDAGData.BlueScore { + nextBlueScore = lowBlockGHOSTDAGData.BlueScore + } + + // Walk down currentHash's selected parent chain to the appropriate ancestor + currentHash, err = sm.dagTraversalManager.LowestChainBlockAboveOrEqualToBlueScore(currentHash, nextBlueScore) + if err != nil { + return nil, err + } + + // Double the distance between included hashes + step *= 2 + } + + return locator, nil +} + +// findNextBlockLocatorBoundaries finds the lowest unknown block locator +// hash and the highest known block locator hash. This is used to create the +// next block locator to find the highest shared known chain block with a +// remote kaspad. +func (sm *syncManager) findNextBlockLocatorBoundaries(blockLocator externalapi.BlockLocator) (lowHash, highHash *externalapi.DomainHash, err error) { + // Find the most recent locator block hash in the DAG. In case none of + // the hashes in the locator are in the DAG, fall back to the genesis block. + lowHash = sm.genesisBlockHash + nextBlockLocatorIndex := int64(len(blockLocator) - 1) + for i, hash := range blockLocator { + exists, err := sm.blockStatusStore.Exists(sm.databaseContext, hash) + if err != nil { + return nil, nil, err + } + if exists { + lowHash = hash + nextBlockLocatorIndex = int64(i) - 1 + break + } + } + if nextBlockLocatorIndex < 0 { + return nil, lowHash, nil + } + return blockLocator[nextBlockLocatorIndex], lowHash, nil +} diff --git a/domain/consensus/processes/syncmanager/log.go b/domain/consensus/processes/syncmanager/log.go new file mode 100644 index 000000000..54583d207 --- /dev/null +++ b/domain/consensus/processes/syncmanager/log.go @@ -0,0 +1,7 @@ +package syncmanager + +import ( + "github.com/kaspanet/kaspad/infrastructure/logger" +) + +var log, _ = logger.Get(logger.SubsystemTags.SYNC) diff --git a/domain/consensus/processes/syncmanager/syncmanager.go b/domain/consensus/processes/syncmanager/syncmanager.go index 84658ec81..a85b15eaf 100644 --- a/domain/consensus/processes/syncmanager/syncmanager.go +++ b/domain/consensus/processes/syncmanager/syncmanager.go @@ -3,35 +3,79 @@ package syncmanager import ( "github.com/kaspanet/kaspad/domain/consensus/model" "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" + "github.com/kaspanet/kaspad/infrastructure/logger" ) type syncManager struct { + databaseContext model.DBReader + genesisBlockHash *externalapi.DomainHash + dagTraversalManager model.DAGTraversalManager + dagTopologyManager model.DAGTopologyManager + + ghostdagDataStore model.GHOSTDAGDataStore + blockStatusStore model.BlockStatusStore } // New instantiates a new SyncManager -func New(dagTraversalManager model.DAGTraversalManager) model.SyncManager { +func New( + databaseContext model.DBReader, + genesisBlockHash *externalapi.DomainHash, + dagTraversalManager model.DAGTraversalManager, + dagTopologyManager model.DAGTopologyManager, + ghostdagDataStore model.GHOSTDAGDataStore, + blockStatusStore model.BlockStatusStore) model.SyncManager { + return &syncManager{ + databaseContext: databaseContext, + genesisBlockHash: genesisBlockHash, + dagTraversalManager: dagTraversalManager, + dagTopologyManager: dagTopologyManager, + + ghostdagDataStore: ghostdagDataStore, + blockStatusStore: blockStatusStore, } } -func (s syncManager) GetHashesBetween(lowHash, highHash *externalapi.DomainHash) ([]*externalapi.DomainHash, error) { - panic("implement me") +func (sm *syncManager) GetHashesBetween(lowHash, highHash *externalapi.DomainHash) ([]*externalapi.DomainHash, error) { + onEnd := logger.LogAndMeasureExecutionTime(log, "GetHashesBetween") + defer onEnd() + + return sm.antiPastHashesBetween(lowHash, highHash) } -func (s syncManager) GetMissingBlockBodyHashes(highHash *externalapi.DomainHash) ([]*externalapi.DomainHash, error) { - panic("implement me") +func (sm *syncManager) GetMissingBlockBodyHashes(highHash *externalapi.DomainHash) ([]*externalapi.DomainHash, error) { + onEnd := logger.LogAndMeasureExecutionTime(log, "GetMissingBlockBodyHashes") + defer onEnd() + + return sm.missingBlockBodyHashes(highHash) } -func (s syncManager) CreateBlockLocator(lowHash, highHash *externalapi.DomainHash) (*externalapi.BlockLocator, error) { - panic("implement me") +func (sm *syncManager) IsBlockInHeaderPruningPointFutureAndVirtualPast(blockHash *externalapi.DomainHash) (bool, error) { + onEnd := logger.LogAndMeasureExecutionTime(log, "IsBlockInHeaderPruningPointFutureAndVirtualPast") + defer onEnd() + + return sm.isBlockInHeaderPruningPointFutureAndVirtualPast(blockHash) } -func (s syncManager) FindNextBlockLocatorBoundaries(blockLocator *externalapi.BlockLocator) (lowHash, highHash *externalapi.DomainHash, err error) { - panic("implement me") +func (sm *syncManager) CreateBlockLocator(lowHash, highHash *externalapi.DomainHash) (externalapi.BlockLocator, error) { + onEnd := logger.LogAndMeasureExecutionTime(log, "CreateBlockLocator") + defer onEnd() + + return sm.createBlockLocator(lowHash, highHash) } -func (s syncManager) IsBlockHeaderInPruningPointFutureAndVirtualPast(blockHash *externalapi.DomainHash) (bool, error) { +func (sm *syncManager) FindNextBlockLocatorBoundaries(blockLocator externalapi.BlockLocator) (lowHash, highHash *externalapi.DomainHash, err error) { + onEnd := logger.LogAndMeasureExecutionTime(log, "FindNextBlockLocatorBoundaries") + defer onEnd() + + return sm.findNextBlockLocatorBoundaries(blockLocator) +} + +func (sm *syncManager) GetSyncInfo() (*externalapi.SyncInfo, error) { + onEnd := logger.LogAndMeasureExecutionTime(log, "GetSyncInfo") + defer onEnd() + panic("implement me") }