package consensus import ( "github.com/kaspanet/kaspad/infrastructure/logger" "github.com/kaspanet/kaspad/util/staging" "math/big" "sync" "github.com/kaspanet/kaspad/domain/consensus/database" "github.com/kaspanet/kaspad/domain/consensus/model" "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" "github.com/kaspanet/kaspad/domain/consensus/ruleerrors" "github.com/pkg/errors" ) type consensus struct { lock *sync.Mutex databaseContext model.DBManager genesisBlock *externalapi.DomainBlock genesisHash *externalapi.DomainHash blockProcessor model.BlockProcessor blockBuilder model.BlockBuilder consensusStateManager model.ConsensusStateManager transactionValidator model.TransactionValidator syncManager model.SyncManager pastMedianTimeManager model.PastMedianTimeManager blockValidator model.BlockValidator coinbaseManager model.CoinbaseManager dagTopologyManagers []model.DAGTopologyManager dagTraversalManager model.DAGTraversalManager difficultyManager model.DifficultyManager ghostdagManagers []model.GHOSTDAGManager headerTipsManager model.HeadersSelectedTipManager mergeDepthManager model.MergeDepthManager pruningManager model.PruningManager reachabilityManagers []model.ReachabilityManager finalityManager model.FinalityManager pruningProofManager model.PruningProofManager acceptanceDataStore model.AcceptanceDataStore blockStore model.BlockStore blockHeaderStore model.BlockHeaderStore pruningStore model.PruningStore ghostdagDataStores []model.GHOSTDAGDataStore blockRelationStores []model.BlockRelationStore blockStatusStore model.BlockStatusStore consensusStateStore model.ConsensusStateStore headersSelectedTipStore model.HeaderSelectedTipStore multisetStore model.MultisetStore reachabilityDataStores []model.ReachabilityDataStore utxoDiffStore model.UTXODiffStore finalityStore model.FinalityStore headersSelectedChainStore model.HeadersSelectedChainStore daaBlocksStore model.DAABlocksStore } func (s *consensus) ValidateAndInsertBlockWithTrustedData(block *externalapi.BlockWithTrustedData, validateUTXO bool) (*externalapi.BlockInsertionResult, error) { s.lock.Lock() defer s.lock.Unlock() return s.blockProcessor.ValidateAndInsertBlockWithTrustedData(block, validateUTXO) } // Init initializes consensus func (s *consensus) Init(skipAddingGenesis bool) error { s.lock.Lock() defer s.lock.Unlock() onEnd := logger.LogAndMeasureExecutionTime(log, "Init") defer onEnd() stagingArea := model.NewStagingArea() exists, err := s.blockStatusStore.Exists(s.databaseContext, stagingArea, model.VirtualGenesisBlockHash) if err != nil { return err } // There should always be a virtual genesis block. Initially only the genesis points to this block, but // on a node with pruned header all blocks without known parents points to it. if !exists { s.blockStatusStore.Stage(stagingArea, model.VirtualGenesisBlockHash, externalapi.StatusUTXOValid) for _, reachabilityManager := range s.reachabilityManagers { err = reachabilityManager.Init(stagingArea) if err != nil { return err } } for _, dagTopologyManager := range s.dagTopologyManagers { err = dagTopologyManager.SetParents(stagingArea, model.VirtualGenesisBlockHash, nil) if err != nil { return err } } s.consensusStateStore.StageTips(stagingArea, []*externalapi.DomainHash{model.VirtualGenesisBlockHash}) for _, ghostdagDataStore := range s.ghostdagDataStores { ghostdagDataStore.Stage(stagingArea, model.VirtualGenesisBlockHash, externalapi.NewBlockGHOSTDAGData( 0, big.NewInt(0), nil, nil, nil, nil, ), false) } err = staging.CommitAllChanges(s.databaseContext, stagingArea) if err != nil { return err } } // The genesis should be added to the DAG if it's a fresh consensus, unless said otherwise (on a // case where the consensus is used for a pruned headers node). if !skipAddingGenesis && s.blockStore.Count(stagingArea) == 0 { genesisWithTrustedData := &externalapi.BlockWithTrustedData{ Block: s.genesisBlock, DAAScore: 0, DAAWindow: nil, GHOSTDAGData: []*externalapi.BlockGHOSTDAGDataHashPair{ { GHOSTDAGData: externalapi.NewBlockGHOSTDAGData(0, big.NewInt(0), model.VirtualGenesisBlockHash, nil, nil, make(map[externalapi.DomainHash]externalapi.KType)), Hash: s.genesisHash, }, }, } _, err = s.blockProcessor.ValidateAndInsertBlockWithTrustedData(genesisWithTrustedData, true) if err != nil { return err } } return nil } func (s *consensus) PruningPointAndItsAnticone() ([]*externalapi.DomainHash, error) { s.lock.Lock() defer s.lock.Unlock() return s.pruningManager.PruningPointAndItsAnticone() } func (s *consensus) BlockWithTrustedData(blockHash *externalapi.DomainHash) (*externalapi.BlockWithTrustedData, error) { s.lock.Lock() defer s.lock.Unlock() return s.pruningManager.BlockWithTrustedData(model.NewStagingArea(), blockHash) } // BuildBlock builds a block over the current state, with the transactions // selected by the given transactionSelector func (s *consensus) BuildBlock(coinbaseData *externalapi.DomainCoinbaseData, transactions []*externalapi.DomainTransaction) (*externalapi.DomainBlock, error) { s.lock.Lock() defer s.lock.Unlock() return s.blockBuilder.BuildBlock(coinbaseData, transactions) } // ValidateAndInsertBlock validates the given block and, if valid, applies it // to the current state func (s *consensus) ValidateAndInsertBlock(block *externalapi.DomainBlock, shouldValidateAgainstUTXO bool) (*externalapi.BlockInsertionResult, error) { s.lock.Lock() defer s.lock.Unlock() return s.blockProcessor.ValidateAndInsertBlock(block, shouldValidateAgainstUTXO) } // ValidateTransactionAndPopulateWithConsensusData validates the given transaction // and populates it with any missing consensus data func (s *consensus) ValidateTransactionAndPopulateWithConsensusData(transaction *externalapi.DomainTransaction) error { s.lock.Lock() defer s.lock.Unlock() stagingArea := model.NewStagingArea() err := s.transactionValidator.ValidateTransactionInIsolation(transaction) if err != nil { return err } err = s.consensusStateManager.PopulateTransactionWithUTXOEntries(stagingArea, transaction) if err != nil { return err } err = s.transactionValidator.ValidateTransactionInContextIgnoringUTXO(stagingArea, transaction, model.VirtualBlockHash) if err != nil { return err } return s.transactionValidator.ValidateTransactionInContextAndPopulateFee( stagingArea, transaction, model.VirtualBlockHash) } func (s *consensus) GetBlock(blockHash *externalapi.DomainHash) (*externalapi.DomainBlock, error) { s.lock.Lock() defer s.lock.Unlock() stagingArea := model.NewStagingArea() block, err := s.blockStore.Block(s.databaseContext, stagingArea, blockHash) if err != nil { if errors.Is(err, database.ErrNotFound) { return nil, errors.Wrapf(err, "block %s does not exist", blockHash) } return nil, err } return block, nil } func (s *consensus) GetBlockEvenIfHeaderOnly(blockHash *externalapi.DomainHash) (*externalapi.DomainBlock, error) { s.lock.Lock() defer s.lock.Unlock() stagingArea := model.NewStagingArea() block, err := s.blockStore.Block(s.databaseContext, stagingArea, blockHash) if err == nil { return block, nil } if !errors.Is(err, database.ErrNotFound) { return nil, err } header, err := s.blockHeaderStore.BlockHeader(s.databaseContext, stagingArea, blockHash) if err != nil { if errors.Is(err, database.ErrNotFound) { return nil, errors.Wrapf(err, "block %s does not exist", blockHash) } return nil, err } return &externalapi.DomainBlock{Header: header}, nil } func (s *consensus) GetBlockHeader(blockHash *externalapi.DomainHash) (externalapi.BlockHeader, error) { s.lock.Lock() defer s.lock.Unlock() stagingArea := model.NewStagingArea() blockHeader, err := s.blockHeaderStore.BlockHeader(s.databaseContext, stagingArea, blockHash) if err != nil { if errors.Is(err, database.ErrNotFound) { return nil, errors.Wrapf(err, "block header %s does not exist", blockHash) } return nil, err } return blockHeader, nil } func (s *consensus) GetBlockInfo(blockHash *externalapi.DomainHash) (*externalapi.BlockInfo, error) { s.lock.Lock() defer s.lock.Unlock() stagingArea := model.NewStagingArea() blockInfo := &externalapi.BlockInfo{} exists, err := s.blockStatusStore.Exists(s.databaseContext, stagingArea, blockHash) if err != nil { return nil, err } blockInfo.Exists = exists if !exists { return blockInfo, nil } blockStatus, err := s.blockStatusStore.Get(s.databaseContext, stagingArea, blockHash) if err != nil { return nil, err } blockInfo.BlockStatus = blockStatus // If the status is invalid, then we don't have the necessary reachability data to check if it's in PruningPoint.Future. if blockStatus == externalapi.StatusInvalid { return blockInfo, nil } ghostdagData, err := s.ghostdagDataStores[0].Get(s.databaseContext, stagingArea, blockHash, false) if err != nil { return nil, err } blockInfo.BlueScore = ghostdagData.BlueScore() blockInfo.BlueWork = ghostdagData.BlueWork() return blockInfo, nil } func (s *consensus) GetBlockRelations(blockHash *externalapi.DomainHash) ( parents []*externalapi.DomainHash, selectedParent *externalapi.DomainHash, children []*externalapi.DomainHash, err error) { s.lock.Lock() defer s.lock.Unlock() stagingArea := model.NewStagingArea() blockRelation, err := s.blockRelationStores[0].BlockRelation(s.databaseContext, stagingArea, blockHash) if err != nil { return nil, nil, nil, err } blockGHOSTDAGData, err := s.ghostdagDataStores[0].Get(s.databaseContext, stagingArea, blockHash, false) if err != nil { return nil, nil, nil, err } return blockRelation.Parents, blockGHOSTDAGData.SelectedParent(), blockRelation.Children, nil } func (s *consensus) GetBlockAcceptanceData(blockHash *externalapi.DomainHash) (externalapi.AcceptanceData, error) { s.lock.Lock() defer s.lock.Unlock() stagingArea := model.NewStagingArea() err := s.validateBlockHashExists(stagingArea, blockHash) if err != nil { return nil, err } return s.acceptanceDataStore.Get(s.databaseContext, stagingArea, blockHash) } func (s *consensus) GetHashesBetween(lowHash, highHash *externalapi.DomainHash, maxBlocks uint64) ( hashes []*externalapi.DomainHash, actualHighHash *externalapi.DomainHash, err error) { s.lock.Lock() defer s.lock.Unlock() stagingArea := model.NewStagingArea() err = s.validateBlockHashExists(stagingArea, lowHash) if err != nil { return nil, nil, err } err = s.validateBlockHashExists(stagingArea, highHash) if err != nil { return nil, nil, err } return s.syncManager.GetHashesBetween(stagingArea, lowHash, highHash, maxBlocks) } func (s *consensus) GetMissingBlockBodyHashes(highHash *externalapi.DomainHash) ([]*externalapi.DomainHash, error) { s.lock.Lock() defer s.lock.Unlock() stagingArea := model.NewStagingArea() err := s.validateBlockHashExists(stagingArea, highHash) if err != nil { return nil, err } return s.syncManager.GetMissingBlockBodyHashes(stagingArea, highHash) } func (s *consensus) GetPruningPointUTXOs(expectedPruningPointHash *externalapi.DomainHash, fromOutpoint *externalapi.DomainOutpoint, limit int) ([]*externalapi.OutpointAndUTXOEntryPair, error) { s.lock.Lock() defer s.lock.Unlock() stagingArea := model.NewStagingArea() pruningPointHash, err := s.pruningStore.PruningPoint(s.databaseContext, stagingArea) if err != nil { return nil, err } if !expectedPruningPointHash.Equal(pruningPointHash) { return nil, errors.Wrapf(ruleerrors.ErrWrongPruningPointHash, "expected pruning point %s but got %s", expectedPruningPointHash, pruningPointHash) } pruningPointUTXOs, err := s.pruningStore.PruningPointUTXOs(s.databaseContext, fromOutpoint, limit) if err != nil { return nil, err } return pruningPointUTXOs, nil } func (s *consensus) GetVirtualUTXOs(expectedVirtualParents []*externalapi.DomainHash, fromOutpoint *externalapi.DomainOutpoint, limit int) ([]*externalapi.OutpointAndUTXOEntryPair, error) { s.lock.Lock() defer s.lock.Unlock() stagingArea := model.NewStagingArea() virtualParents, err := s.dagTopologyManagers[0].Parents(stagingArea, model.VirtualBlockHash) if err != nil { return nil, err } if !externalapi.HashesEqual(expectedVirtualParents, virtualParents) { return nil, errors.Wrapf(ruleerrors.ErrGetVirtualUTXOsWrongVirtualParents, "expected virtual parents %s but got %s", expectedVirtualParents, virtualParents) } virtualUTXOs, err := s.consensusStateStore.VirtualUTXOs(s.databaseContext, fromOutpoint, limit) if err != nil { return nil, err } return virtualUTXOs, nil } func (s *consensus) PruningPoint() (*externalapi.DomainHash, error) { s.lock.Lock() defer s.lock.Unlock() stagingArea := model.NewStagingArea() return s.pruningStore.PruningPoint(s.databaseContext, stagingArea) } func (s *consensus) PruningPointHeaders() ([]externalapi.BlockHeader, error) { s.lock.Lock() defer s.lock.Unlock() stagingArea := model.NewStagingArea() lastPruningPointIndex, err := s.pruningStore.CurrentPruningPointIndex(s.databaseContext, stagingArea) if err != nil { return nil, err } headers := make([]externalapi.BlockHeader, 0, lastPruningPointIndex) for i := uint64(0); i <= lastPruningPointIndex; i++ { pruningPoint, err := s.pruningStore.PruningPointByIndex(s.databaseContext, stagingArea, i) if err != nil { return nil, err } header, err := s.blockHeaderStore.BlockHeader(s.databaseContext, stagingArea, pruningPoint) if err != nil { return nil, err } headers = append(headers, header) } return headers, nil } func (s *consensus) ClearImportedPruningPointData() error { s.lock.Lock() defer s.lock.Unlock() return s.pruningManager.ClearImportedPruningPointData() } func (s *consensus) AppendImportedPruningPointUTXOs(outpointAndUTXOEntryPairs []*externalapi.OutpointAndUTXOEntryPair) error { s.lock.Lock() defer s.lock.Unlock() return s.pruningManager.AppendImportedPruningPointUTXOs(outpointAndUTXOEntryPairs) } func (s *consensus) ValidateAndInsertImportedPruningPoint(newPruningPoint *externalapi.DomainHash) error { s.lock.Lock() defer s.lock.Unlock() return s.blockProcessor.ValidateAndInsertImportedPruningPoint(newPruningPoint) } func (s *consensus) GetVirtualSelectedParent() (*externalapi.DomainHash, error) { s.lock.Lock() defer s.lock.Unlock() stagingArea := model.NewStagingArea() virtualGHOSTDAGData, err := s.ghostdagDataStores[0].Get(s.databaseContext, stagingArea, model.VirtualBlockHash, false) if err != nil { return nil, err } return virtualGHOSTDAGData.SelectedParent(), nil } func (s *consensus) Tips() ([]*externalapi.DomainHash, error) { s.lock.Lock() defer s.lock.Unlock() stagingArea := model.NewStagingArea() return s.consensusStateStore.Tips(stagingArea, s.databaseContext) } func (s *consensus) GetVirtualInfo() (*externalapi.VirtualInfo, error) { s.lock.Lock() defer s.lock.Unlock() stagingArea := model.NewStagingArea() blockRelations, err := s.blockRelationStores[0].BlockRelation(s.databaseContext, stagingArea, model.VirtualBlockHash) if err != nil { return nil, err } bits, err := s.difficultyManager.RequiredDifficulty(stagingArea, model.VirtualBlockHash) if err != nil { return nil, err } pastMedianTime, err := s.pastMedianTimeManager.PastMedianTime(stagingArea, model.VirtualBlockHash) if err != nil { return nil, err } virtualGHOSTDAGData, err := s.ghostdagDataStores[0].Get(s.databaseContext, stagingArea, model.VirtualBlockHash, false) if err != nil { return nil, err } daaScore, err := s.daaBlocksStore.DAAScore(s.databaseContext, stagingArea, model.VirtualBlockHash) if err != nil { return nil, err } return &externalapi.VirtualInfo{ ParentHashes: blockRelations.Parents, Bits: bits, PastMedianTime: pastMedianTime, BlueScore: virtualGHOSTDAGData.BlueScore(), DAAScore: daaScore, }, nil } func (s *consensus) GetVirtualDAAScore() (uint64, error) { s.lock.Lock() defer s.lock.Unlock() stagingArea := model.NewStagingArea() return s.daaBlocksStore.DAAScore(s.databaseContext, stagingArea, model.VirtualBlockHash) } func (s *consensus) CreateBlockLocatorFromPruningPoint(highHash *externalapi.DomainHash, limit uint32) (externalapi.BlockLocator, error) { s.lock.Lock() defer s.lock.Unlock() stagingArea := model.NewStagingArea() err := s.validateBlockHashExists(stagingArea, highHash) if err != nil { return nil, err } pruningPoint, err := s.pruningStore.PruningPoint(s.databaseContext, stagingArea) if err != nil { return nil, err } return s.syncManager.CreateBlockLocator(stagingArea, pruningPoint, highHash, limit) } func (s *consensus) CreateFullHeadersSelectedChainBlockLocator() (externalapi.BlockLocator, error) { s.lock.Lock() defer s.lock.Unlock() stagingArea := model.NewStagingArea() lowHash, err := s.pruningStore.PruningPoint(s.databaseContext, stagingArea) if err != nil { return nil, err } highHash, err := s.headersSelectedTipStore.HeadersSelectedTip(s.databaseContext, stagingArea) if err != nil { return nil, err } return s.syncManager.CreateHeadersSelectedChainBlockLocator(stagingArea, lowHash, highHash) } func (s *consensus) CreateHeadersSelectedChainBlockLocator(lowHash, highHash *externalapi.DomainHash) (externalapi.BlockLocator, error) { s.lock.Lock() defer s.lock.Unlock() stagingArea := model.NewStagingArea() return s.syncManager.CreateHeadersSelectedChainBlockLocator(stagingArea, lowHash, highHash) } func (s *consensus) GetSyncInfo() (*externalapi.SyncInfo, error) { s.lock.Lock() defer s.lock.Unlock() stagingArea := model.NewStagingArea() return s.syncManager.GetSyncInfo(stagingArea) } func (s *consensus) IsValidPruningPoint(blockHash *externalapi.DomainHash) (bool, error) { s.lock.Lock() defer s.lock.Unlock() stagingArea := model.NewStagingArea() err := s.validateBlockHashExists(stagingArea, blockHash) if err != nil { return false, err } return s.pruningManager.IsValidPruningPoint(stagingArea, blockHash) } func (s *consensus) ArePruningPointsViolatingFinality(pruningPoints []externalapi.BlockHeader) (bool, error) { s.lock.Lock() defer s.lock.Unlock() stagingArea := model.NewStagingArea() return s.pruningManager.ArePruningPointsViolatingFinality(stagingArea, pruningPoints) } func (s *consensus) ImportPruningPoints(pruningPoints []externalapi.BlockHeader) error { s.lock.Lock() defer s.lock.Unlock() stagingArea := model.NewStagingArea() err := s.consensusStateManager.ImportPruningPoints(stagingArea, pruningPoints) if err != nil { return err } err = staging.CommitAllChanges(s.databaseContext, stagingArea) if err != nil { return err } return nil } func (s *consensus) GetVirtualSelectedParentChainFromBlock(blockHash *externalapi.DomainHash) (*externalapi.SelectedChainPath, error) { s.lock.Lock() defer s.lock.Unlock() stagingArea := model.NewStagingArea() err := s.validateBlockHashExists(stagingArea, blockHash) if err != nil { return nil, err } return s.consensusStateManager.GetVirtualSelectedParentChainFromBlock(stagingArea, blockHash) } func (s *consensus) validateBlockHashExists(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash) error { exists, err := s.blockStatusStore.Exists(s.databaseContext, stagingArea, blockHash) if err != nil { return err } if !exists { return errors.Errorf("block %s does not exist", blockHash) } return nil } func (s *consensus) IsInSelectedParentChainOf(blockHashA *externalapi.DomainHash, blockHashB *externalapi.DomainHash) (bool, error) { s.lock.Lock() defer s.lock.Unlock() stagingArea := model.NewStagingArea() err := s.validateBlockHashExists(stagingArea, blockHashA) if err != nil { return false, err } err = s.validateBlockHashExists(stagingArea, blockHashB) if err != nil { return false, err } return s.dagTopologyManagers[0].IsInSelectedParentChainOf(stagingArea, blockHashA, blockHashB) } func (s *consensus) GetHeadersSelectedTip() (*externalapi.DomainHash, error) { s.lock.Lock() defer s.lock.Unlock() stagingArea := model.NewStagingArea() return s.headersSelectedTipStore.HeadersSelectedTip(s.databaseContext, stagingArea) } func (s *consensus) Anticone(blockHash *externalapi.DomainHash) ([]*externalapi.DomainHash, error) { s.lock.Lock() defer s.lock.Unlock() stagingArea := model.NewStagingArea() err := s.validateBlockHashExists(stagingArea, blockHash) if err != nil { return nil, err } tips, err := s.consensusStateStore.Tips(stagingArea, s.databaseContext) if err != nil { return nil, err } return s.dagTraversalManager.AnticoneFromBlocks(stagingArea, tips, blockHash) } func (s *consensus) EstimateNetworkHashesPerSecond(startHash *externalapi.DomainHash, windowSize int) (uint64, error) { s.lock.Lock() defer s.lock.Unlock() return s.difficultyManager.EstimateNetworkHashesPerSecond(startHash, windowSize) } func (s *consensus) PopulateMass(transaction *externalapi.DomainTransaction) { s.transactionValidator.PopulateMass(transaction) } func (s *consensus) ResolveVirtual() error { // In order to prevent a situation that the consensus lock is held for too much time, we // release the lock each time resolve 100 blocks. for i := 0; ; i++ { if i%10 == 0 { log.Infof("Resolving virtual. This may take some time...") } var isCompletelyResolved bool var err error func() { s.lock.Lock() defer s.lock.Unlock() isCompletelyResolved, err = s.consensusStateManager.ResolveVirtual(100) }() if err != nil { return err } if isCompletelyResolved { log.Infof("Resolved virtual") return nil } } } func (s *consensus) BuildPruningPointProof() (*externalapi.PruningPointProof, error) { s.lock.Lock() defer s.lock.Unlock() return s.pruningProofManager.BuildPruningPointProof(model.NewStagingArea()) } func (s *consensus) ValidatePruningPointProof(pruningPointProof *externalapi.PruningPointProof) error { s.lock.Lock() defer s.lock.Unlock() return s.pruningProofManager.ValidatePruningPointProof(pruningPointProof) } func (s *consensus) ApplyPruningPointProof(pruningPointProof *externalapi.PruningPointProof) error { stagingArea := model.NewStagingArea() err := s.pruningProofManager.ApplyPruningPointProof(stagingArea, pruningPointProof) if err != nil { return err } err = staging.CommitAllChanges(s.databaseContext, stagingArea) if err != nil { return err } return nil }