diff --git a/framework/src/node/consensus/certificate_generation/commit_pool.ts b/framework/src/node/consensus/certificate_generation/commit_pool.ts index 8152936714c..1eb55d63e3b 100644 --- a/framework/src/node/consensus/certificate_generation/commit_pool.ts +++ b/framework/src/node/consensus/certificate_generation/commit_pool.ts @@ -18,15 +18,9 @@ import { createAggSig } from '@liskhq/lisk-cryptography'; import { EMPTY_BUFFER } from './constants'; import { BFTParameterNotFoundError } from '../../../modules/bft/errors'; import { APIContext } from '../../state_machine/types'; -import { BFTAPI, PkSigPair, ValidatorAPI } from '../types'; +import { BFTAPI, PkSigPair, ValidatorAPI, AggregateCommit } from '../types'; import { COMMIT_RANGE_STORED } from './constants'; -import { - AggregateCommit, - Certificate, - CommitPoolConfig, - SingleCommit, - ValidatorInfo, -} from './types'; +import { Certificate, CommitPoolConfig, SingleCommit, ValidatorInfo } from './types'; import { computeCertificateFromBlockHeader, verifyAggregateCertificateSignature, diff --git a/framework/src/node/consensus/certificate_generation/types.ts b/framework/src/node/consensus/certificate_generation/types.ts index e963039f950..8b06592b9b0 100644 --- a/framework/src/node/consensus/certificate_generation/types.ts +++ b/framework/src/node/consensus/certificate_generation/types.ts @@ -47,9 +47,3 @@ export interface ValidatorInfo { readonly blsPublicKey: Buffer; readonly blsSecretKey: Buffer; } - -export interface AggregateCommit { - readonly height: number; - readonly aggregationBits: Buffer; - readonly certificateSignature: Buffer; -} diff --git a/framework/src/node/consensus/consensus.ts b/framework/src/node/consensus/consensus.ts index c468bb6610d..77f504721c6 100644 --- a/framework/src/node/consensus/consensus.ts +++ b/framework/src/node/consensus/consensus.ts @@ -49,6 +49,7 @@ import { ValidatorAPI, BFTAPI } from './types'; import { APIContext, createAPIContext } from '../state_machine'; import { forkChoice, ForkStatus } from './fork_choice/fork_choice_rule'; import { createNewAPIContext } from '../state_machine/api_context'; +import { CommitPool } from './types'; interface ConsensusArgs { stateMachine: StateMachine; @@ -57,6 +58,7 @@ interface ConsensusArgs { genesisConfig: GenesisConfig; bftAPI: BFTAPI; validatorAPI: ValidatorAPI; + commitPool: CommitPool; } interface InitArgs { @@ -93,6 +95,7 @@ export class Consensus { private readonly _validatorAPI: ValidatorAPI; private readonly _bftAPI: BFTAPI; private readonly _genesisConfig: GenesisConfig; + private readonly _commitPool: CommitPool; // init parameters private _logger!: Logger; @@ -112,6 +115,7 @@ export class Consensus { this._validatorAPI = args.validatorAPI; this._bftAPI = args.bftAPI; this._genesisConfig = args.genesisConfig; + this._commitPool = args.commitPool; } public async init(args: InitArgs): Promise { @@ -513,6 +517,9 @@ export class Consensus { // verify Block signature await this._verifyAssetsSignature(apiContext, block); + // verify aggregate commits + await this._verifyAggregateCommit(apiContext, block); + // Validate a block block.validate(); @@ -638,6 +645,23 @@ export class Consensus { } } + private async _verifyAggregateCommit(apiContext: APIContext, block: Block): Promise { + if (!block.header.aggregateCommit) { + throw new Error( + `Aggregate Commit is "undefined" for the block with id: ${block.header.id.toString('hex')}`, + ); + } + const isVerified = await this._commitPool.verifyAggregateCommit( + apiContext, + block.header.aggregateCommit, + ); + if (!isVerified) { + throw new Error( + `Invalid aggregateCommit for the block with id: ${block.header.id.toString('hex')}`, + ); + } + } + private async _verifyValidatorsHash(apiContext: APIContext, block: Block): Promise { if (!block.header.validatorsHash) { throw new Error( diff --git a/framework/src/node/consensus/types.ts b/framework/src/node/consensus/types.ts index 7f4032699e3..9f45c5bc115 100644 --- a/framework/src/node/consensus/types.ts +++ b/framework/src/node/consensus/types.ts @@ -15,7 +15,7 @@ import { BFTParameters } from '../../modules/bft/schemas'; import { BFTHeights } from '../../modules/bft/types'; import { ValidatorKeys } from '../../modules/validators/types'; -import { BlockHeader, ImmutableAPIContext } from '../state_machine'; +import { BlockHeader, ImmutableAPIContext, APIContext } from '../state_machine'; export interface BFTHeader { id: Buffer; @@ -56,3 +56,16 @@ export interface PkSigPair { publicKey: Buffer; signature: Buffer; } + +export interface AggregateCommit { + readonly height: number; + readonly aggregationBits: Buffer; + readonly certificateSignature: Buffer; +} + +export interface CommitPool { + verifyAggregateCommit: ( + apiContext: APIContext, + aggregateCommit: AggregateCommit, + ) => Promise; +} diff --git a/framework/src/node/node.ts b/framework/src/node/node.ts index 4a675fca8a3..97532db05fa 100644 --- a/framework/src/node/node.ts +++ b/framework/src/node/node.ts @@ -37,6 +37,7 @@ import { APP_EVENT_NETWORK_READY, APP_EVENT_TRANSACTION_NEW, } from './events'; +import { CommitPool } from './consensus/types'; const MINIMUM_MODULE_ID = 2; @@ -93,6 +94,7 @@ export class Node { genesisConfig: this._options.genesis, bftAPI: this._bftModule.api, validatorAPI: this._validatorsModule.api, + commitPool: {} as CommitPool, // TODO Initialize commit pool }); this._generator = new Generator({ chain: this._chain, diff --git a/framework/test/unit/node/consensus/certificate_generation/commit_pool.spec.ts b/framework/test/unit/node/consensus/certificate_generation/commit_pool.spec.ts index 73888f647e2..10063bf9e89 100644 --- a/framework/test/unit/node/consensus/certificate_generation/commit_pool.spec.ts +++ b/framework/test/unit/node/consensus/certificate_generation/commit_pool.spec.ts @@ -32,7 +32,6 @@ import { } from '../../../../../src/node/consensus/certificate_generation/constants'; import { certificateSchema } from '../../../../../src/node/consensus/certificate_generation/schema'; import { - AggregateCommit, Certificate, SingleCommit, } from '../../../../../src/node/consensus/certificate_generation/types'; @@ -42,6 +41,7 @@ import { computeCertificateFromBlockHeader, signCertificate, } from '../../../../../src/node/consensus/certificate_generation/utils'; +import { AggregateCommit } from '../../../../../src/node/consensus/types'; jest.mock('@liskhq/lisk-cryptography', () => ({ __esModule: true, diff --git a/framework/test/unit/node/consensus/consensus.spec.ts b/framework/test/unit/node/consensus/consensus.spec.ts index faf5ad08b3e..bca7c16b553 100644 --- a/framework/test/unit/node/consensus/consensus.spec.ts +++ b/framework/test/unit/node/consensus/consensus.spec.ts @@ -47,6 +47,7 @@ import * as forkchoice from '../../../../src/node/consensus/fork_choice/fork_cho import { postBlockEventSchema } from '../../../../src/node/consensus/schema'; import { APIContext } from '../../../../src/node/state_machine'; import { createTransientAPIContext } from '../../../../src/testing'; +import { CommitPool } from '../../../../src/node/consensus/types'; describe('consensus', () => { const genesis = (genesisBlock() as unknown) as Block; @@ -56,6 +57,7 @@ describe('consensus', () => { let stateMachine: StateMachine; let bftAPI: BFTAPI; let validatorAPI: ValidatorAPI; + let commitPool: CommitPool; let dbMock: any; @@ -99,6 +101,9 @@ describe('consensus', () => { getValidatorAccount: jest.fn(), getSlotNumber: jest.fn(), } as never; + commitPool = { + verifyAggregateCommit: jest.fn(), + }; consensus = new Consensus({ chain, network, @@ -106,6 +111,7 @@ describe('consensus', () => { bftAPI, validatorAPI, genesisConfig: {} as any, + commitPool, }); dbMock = { get: jest.fn(), @@ -852,6 +858,42 @@ describe('consensus', () => { }); }); + describe('aggregateCommit', () => { + it('should throw error when aggregateCommit is undefined', async () => { + Object.defineProperty(block.header, `aggregateCommit`, { value: undefined }); + + await expect( + consensus['_verifyAggregateCommit'](apiContext, block as any), + ).rejects.toThrow( + `Aggregate Commit is "undefined" for the block with id: ${block.header.id.toString( + 'hex', + )}`, + ); + }); + + it('should throw error for invalid aggregateCommit', async () => { + when(consensus['_commitPool'].verifyAggregateCommit as never) + .calledWith(apiContext, block.header.aggregateCommit) + .mockResolvedValue(false as never); + + await expect( + consensus['_verifyAggregateCommit'](apiContext, block as any), + ).rejects.toThrow( + `Invalid aggregateCommit for the block with id: ${block.header.id.toString('hex')}`, + ); + }); + + it('should be success for valid aggregateCommit', async () => { + when(consensus['_commitPool'].verifyAggregateCommit as never) + .calledWith(apiContext, block.header.aggregateCommit) + .mockResolvedValue(true as never); + + await expect( + consensus['_verifyAggregateCommit'](apiContext, block as any), + ).resolves.toBeUndefined(); + }); + }); + describe('validateBlockAsset', () => { it('should throw error if a module is not registered', async () => { const assetList = [