import { codec } from '@liskhq/lisk-codec' ; import * as assert from 'assert' ; import { EventEmitter } from 'events' ; import { BlockHeader , Chain , CONSENSUS_STATE_FINALIZED_HEIGHT_KEY , getValidators , StateStore , } from '@liskhq/lisk-chain' ; import { EVENT_BFT_FINALIZED_HEIGHT_CHANGED , FinalityManager } from './finality_manager' ; import * as forkChoiceRule from './fork_choice_rule' ; import { BFTPersistedValues , ForkStatus } from './types' ; import { BFT_ROUND_THRESHOLD } from './constant' ; export const EVENT_BFT_BLOCK_FINALIZED = 'EVENT_BFT_BLOCK_FINALIZED' ; export const BFTFinalizedHeightCodecSchema = { type : 'object' , $id : '/BFT/FinalizedHeight' , title : 'Lisk BFT Finalized Height' , properties : { finalizedHeight : { dataType : 'uint32' , fieldNumber : 1 , } , } , required : [ 'finalizedHeight' ] , } ; codec . addSchema ( BFTFinalizedHeightCodecSchema ) ; /**
 * BFT class responsible to hold integration logic for finality manager with the framework
 */ export class BFT extends EventEmitter { public readonly constants : { threshold : number ; genesisHeight : number ; } ; private _finalityManager ? : FinalityManager ; private readonly _chain : Chain ; public constructor ( { chain , threshold , genesisHeight , } ) { super ( ) ; this . _chain = chain ; this . constants = { threshold , genesisHeight , } ; } public async init ( stateStore ) { this . _finalityManager = await this . _initFinalityManager ( stateStore ) ; this . finalityManager . on ( EVENT_BFT_FINALIZED_HEIGHT_CHANGED , updatedFinalizedHeight => { this . emit ( EVENT_BFT_FINALIZED_HEIGHT_CHANGED , updatedFinalizedHeight ) ; } ) ; } public get finalityManager ( ) : FinalityManager { return this . _finalityManager as FinalityManager ; } public async applyBlockHeader ( block , stateStore ) { await this . finalityManager . addBlockHeader ( block , stateStore ) ; const { finalizedHeight } = this . finalityManager ; await stateStore . consensus . set ( CONSENSUS_STATE_FINALIZED_HEIGHT_KEY , codec . encode ( BFTFinalizedHeightCodecSchema , { finalizedHeight } ) ) ; } public async verifyBlockHeader ( blockHeader , stateStore ) { const isCompliant = await this . isBFTProtocolCompliant ( blockHeader , stateStore ) ; const reward = this . _chain . calculateExpectedReward ( blockHeader , stateStore ) ; const expectedReward = isCompliant ? reward : reward / BigInt ( 4 ) ; if ( blockHeader . reward !== expectedReward ) { throw new Error ( `Invalid block reward: ${ blockHeader . reward . toString ( ) } expected: ${ expectedReward . toString ( ) }` ) ; } await this . finalityManager . verifyBlockHeaders ( blockHeader , stateStore ) ; } public forkChoice ( blockHeader , lastBlockHeader ) { const receivedBlock = { ... blockHeader , receivedAt : this . _chain . slots . timeSinceGenesis ( ) , } ; if ( forkChoiceRule . isValidBlock ( lastBlockHeader , receivedBlock ) ) { return ForkStatus . VALID_BLOCK ; } if ( forkChoiceRule . isIdenticalBlock ( lastBlockHeader , receivedBlock ) ) { return ForkStatus . IDENTICAL_BLOCK ; } if ( forkChoiceRule . isDoubleForging ( lastBlockHeader , receivedBlock ) ) { return ForkStatus . DOUBLE_FORGING ; } if ( forkChoiceRule . isTieBreak ( { slots : this . _chain . slots , lastAppliedBlock : lastBlockHeader , receivedBlock , } ) ) { return ForkStatus . TIE_BREAK ; } if ( forkChoiceRule . isDifferentChain ( lastBlockHeader , receivedBlock ) ) { return ForkStatus . DIFFERENT_CHAIN ; } return ForkStatus . DISCARD ; } public async isBFTProtocolCompliant ( blockHeader , stateStore ) { assert ( blockHeader , 'No block was provided to be verified' ) ; const validators = await getValidators ( stateStore ) ; const numberOfVotingValidators = validators . filter ( validator => validator . isConsensusParticipant ) . length ; const heightThreshold = numberOfVotingValidators * BFT_ROUND_THRESHOLD ; if ( blockHeader . asset . maxHeightPreviouslyForged === 0 ) { return true ; } if ( blockHeader . height <= blockHeader . asset . maxHeightPreviouslyForged ) { return false ; } if ( blockHeader . height - blockHeader . asset . maxHeightPreviouslyForged > heightThreshold ) { return true ; } const maxHeightPreviouslyForgedBlock = stateStore . chain . lastBlockHeaders . find ( bftHeader => bftHeader . height === blockHeader . asset . maxHeightPreviouslyForged ) ; if ( ! maxHeightPreviouslyForgedBlock ) { throw new Error ( `Block at height ${ blockHeader . asset . maxHeightPreviouslyForged } must be in the lastBlockHeaders.` ) ; } if ( ! blockHeader . generatorPublicKey . equals ( maxHeightPreviouslyForgedBlock . generatorPublicKey ) ) { return false ; } return true ; } public async getMaxHeightPrevoted ( lastMaxHeightPrevoted ) { return this . finalityManager . getMaxHeightPrevoted ( lastMaxHeightPrevoted ) ; } public get finalizedHeight ( ) : number { return this . finalityManager . finalizedHeight ; } private async _initFinalityManager ( stateStore ) { const storedFinalizedHeightBuffer = await stateStore . consensus . get ( CONSENSUS_STATE_FINALIZED_HEIGHT_KEY ) ; const finalizedHeight = storedFinalizedHeightBuffer === undefined ? this . constants . genesisHeight : codec . decode < BFTPersistedValues > ( BFTFinalizedHeightCodecSchema , storedFinalizedHeightBuffer ) . finalizedHeight ; const finalityManager = new FinalityManager ( { chain : this . _chain , finalizedHeight , genesisHeight : this . constants . genesisHeight , threshold : this . constants . threshold , } ) ; return finalityManager ; } }