UNPKG

lisk-framework

Version:

Lisk blockchain application platform

452 lines 21.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Generator = void 0; const fs = require("fs"); const events_1 = require("events"); const lisk_chain_1 = require("@liskhq/lisk-chain"); const lisk_codec_1 = require("@liskhq/lisk-codec"); const lisk_cryptography_1 = require("@liskhq/lisk-cryptography"); const lisk_db_1 = require("@liskhq/lisk-db"); const lisk_transaction_pool_1 = require("@liskhq/lisk-transaction-pool"); const lisk_tree_1 = require("@liskhq/lisk-tree"); const lisk_utils_1 = require("@liskhq/lisk-utils"); const lisk_validator_1 = require("@liskhq/lisk-validator"); const events_2 = require("../events"); const broadcaster_1 = require("./broadcaster"); const constants_1 = require("./constants"); const endpoint_1 = require("./endpoint"); const generator_store_1 = require("./generator_store"); const network_endpoint_1 = require("./network_endpoint"); const schemas_1 = require("./schemas"); const strategies_1 = require("./strategies"); const generated_info_1 = require("./generated_info"); const constants_2 = require("../consensus/constants"); const abi_1 = require("../../abi"); const consensus_1 = require("../consensus"); const path_1 = require("../../utils/path"); const metrics_1 = require("../metrics/metrics"); const single_commit_handler_1 = require("./single_commit_handler"); const BLOCK_VERSION = 2; class Generator { constructor(args) { this.events = new events_1.EventEmitter(); this._metrics = { blockGeneration: metrics_1.defaultMetrics.counter('generator_blockGeneration'), }; this._abi = args.abi; this._keypairs = new lisk_utils_1.dataStructures.BufferMap(); this._pool = new lisk_transaction_pool_1.TransactionPool({ maxPayloadLength: args.config.genesis.maxTransactionsSize, verifyTransaction: async (transaction) => this._verifyTransaction(transaction), }); this._config = args.config; this._blockTime = args.config.genesis.blockTime; this._chain = args.chain; this._bft = args.bft; this._consensus = args.consensus; this._network = args.network; this._broadcaster = new broadcaster_1.Broadcaster({ network: this._network, transactionPool: this._pool, interval: constants_1.DEFAULT_RELEASE_INTERVAL, limit: constants_1.DEFAULT_RELEASE_LIMIT, }); this._endpoint = new endpoint_1.Endpoint({ abi: this._abi, keypair: this._keypairs, consensus: this._consensus, blockTime: this._blockTime, chain: this._chain, }); this._networkEndpoint = new network_endpoint_1.NetworkEndpoint({ abi: this._abi, broadcaster: this._broadcaster, chain: this._chain, network: this._network, pool: this._pool, }); this._forgingStrategy = new strategies_1.HighFeeGenerationStrategy({ maxTransactionsSize: this._chain.constants.maxTransactionsSize, abi: this._abi, pool: this._pool, }); this._generationJob = new lisk_utils_1.jobHandlers.Scheduler(async () => this._generateLoop().catch(err => { this._logger.error({ err: err }, 'Failed to generate a block'); }), constants_1.FORGE_INTERVAL); } async init(args) { this._logger = args.logger; this._generatorDB = args.generatorDB; this._blockchainDB = args.blockchainDB; this._genesisHeight = args.genesisHeight; this._singleCommitHandler = new single_commit_handler_1.SingleCommitHandler(this._logger, this._chain, this._consensus, this._bft, this._keypairs, this._blockchainDB); this._broadcaster.init({ logger: this._logger, }); this._endpoint.init({ generatorDB: this._generatorDB, genesisHeight: this._genesisHeight, singleCommitHandler: this._singleCommitHandler, }); this._networkEndpoint.init({ logger: this._logger, }); await this._saveKeysFromFile(); await this._loadGenerators(); this._network.registerHandler(constants_1.NETWORK_EVENT_POST_TRANSACTIONS_ANNOUNCEMENT, ({ data, peerId }) => { this._networkEndpoint .handleEventPostTransactionsAnnouncement(data, peerId) .catch(err => this._logger.error({ err: err, peerId }, 'Fail to handle transaction announcement')); }); this._network.registerEndpoint(constants_1.NETWORK_RPC_GET_TRANSACTIONS, async ({ data, peerId }) => this._networkEndpoint.handleRPCGetTransactions(data, peerId)); this._networkEndpoint.event.on(constants_1.GENERATOR_EVENT_NEW_TRANSACTION_ANNOUNCEMENT, e => { this.events.emit(constants_1.GENERATOR_EVENT_NEW_TRANSACTION_ANNOUNCEMENT, e); }); this._networkEndpoint.event.on(constants_1.GENERATOR_EVENT_NEW_TRANSACTION, e => { this.events.emit(constants_1.GENERATOR_EVENT_NEW_TRANSACTION, e); }); await this._singleCommitHandler.initAllSingleCommits(); } get endpoint() { return this._endpoint; } get txpool() { return this._pool; } get broadcaster() { return this._broadcaster; } async start() { this._networkEndpoint.start(); this._pool.events.on(lisk_transaction_pool_1.events.EVENT_TRANSACTION_ADDED, (event) => { this.events.emit(constants_1.GENERATOR_EVENT_NEW_TRANSACTION, { transaction: event.transaction.toJSON(), }); }); this._pool.events.on(lisk_transaction_pool_1.events.EVENT_TRANSACTION_REMOVED, (event) => { this._logger.debug(event, 'Transaction was removed from the pool.'); }); this._broadcaster.start(); await this._pool.start(); this._generationJob.start(); this._network.events.on(events_2.EVENT_NETWORK_READY, () => { this._loadTransactionsFromNetwork().catch(err => this._logger.error({ err: err }, 'Failed to load unconfirmed transactions from the network')); }); this._consensus.events.on(constants_2.CONSENSUS_EVENT_FINALIZED_HEIGHT_CHANGED, ({ from, to }) => { this._singleCommitHandler .handleFinalizedHeightChanged(from, to) .catch((err) => this._logger.error({ err }, 'Fail to certify single commit')); }); } async stop() { this._pool.events.removeAllListeners(lisk_transaction_pool_1.events.EVENT_TRANSACTION_REMOVED); this._broadcaster.stop(); this._pool.stop(); this._generationJob.stop(); this._networkEndpoint.stop(); } onNewBlock(block) { if (block.transactions.length) { for (const transaction of block.transactions) { this._pool.remove(transaction); } } } getPooledTransactions() { return this._pool.getAll(); } onDeleteBlock(block) { if (block.transactions.length) { for (const transaction of block.transactions) { this._pool.add(transaction).catch((err) => { this._logger.error({ err }, 'Failed to add transaction back to the pool'); }); } } } async generateBlock(input) { const block = await this._generateBlock({ ...input, }).catch(async (err) => { await this._abi.clear({}); throw err; }); return block; } async _loadTransactionsFromNetwork() { for (let retry = 0; retry < constants_1.LOAD_TRANSACTION_RETRIES; retry += 1) { try { await this._getUnconfirmedTransactionsFromNetwork(); return; } catch (err) { if (err && retry === constants_1.LOAD_TRANSACTION_RETRIES - 1) { this._logger.error({ err: err }, `Failed to get transactions from network after ${constants_1.LOAD_TRANSACTION_RETRIES} retries`); } } } } async _verifyTransaction(transaction) { const { result } = await this._abi.verifyTransaction({ contextID: Buffer.alloc(0), transaction, header: this._chain.lastBlock.header.toObject(), onlyCommand: false, }); return result; } async _saveKeysFromFile() { if (!this._config.generator.keys.fromFile) { return; } const filePath = (0, path_1.getPathFromDataPath)(this._config.generator.keys.fromFile, this._config.system.dataPath); this._logger.debug({ filePath }, 'Reading validator keys from a file'); const keysFile = JSON.parse(fs.readFileSync(filePath, 'utf-8')); lisk_validator_1.validator.validate(schemas_1.keysFileSchema, keysFile); const generatorStore = new generator_store_1.GeneratorStore(this._generatorDB); const batch = new lisk_db_1.Batch(); const subStore = generatorStore.getGeneratorStore(constants_1.GENERATOR_STORE_KEY_PREFIX); for (const key of keysFile.keys) { this._logger.info({ address: key.address }, 'saving generator from file'); if (key.encrypted && Object.keys(key.encrypted).length) { await subStore.set(lisk_cryptography_1.address.getAddressFromLisk32Address(key.address), lisk_codec_1.codec.encode(schemas_1.generatorKeysSchema, { type: 'encrypted', data: lisk_codec_1.codec.encode(schemas_1.encryptedMessageSchema, key.encrypted), })); } else if (key.plain) { await subStore.set(lisk_cryptography_1.address.getAddressFromLisk32Address(key.address), lisk_codec_1.codec.encode(schemas_1.generatorKeysSchema, { type: 'plain', data: lisk_codec_1.codec.encode(schemas_1.plainGeneratorKeysSchema, { blsKey: Buffer.from(key.plain.blsKey, 'hex'), blsPrivateKey: Buffer.from(key.plain.blsPrivateKey, 'hex'), generatorKey: Buffer.from(key.plain.generatorKey, 'hex'), generatorPrivateKey: Buffer.from(key.plain.generatorPrivateKey, 'hex'), }), })); } } generatorStore.finalize(batch); await this._generatorDB.write(batch); } async _loadGenerators() { const generatorStore = new generator_store_1.GeneratorStore(this._generatorDB); const subStore = generatorStore.getGeneratorStore(constants_1.GENERATOR_STORE_KEY_PREFIX); const encodedGeneratorKeysList = await subStore.iterate({ gte: Buffer.alloc(20, 0), lte: Buffer.alloc(20, 255), }); for (const { key, value } of encodedGeneratorKeysList) { const encodedGeneratorKeys = lisk_codec_1.codec.decode(schemas_1.generatorKeysSchema, value); if (encodedGeneratorKeys.type === 'plain') { const keys = lisk_codec_1.codec.decode(schemas_1.plainGeneratorKeysSchema, encodedGeneratorKeys.data); this._keypairs.set(key, { publicKey: keys.generatorKey, privateKey: keys.generatorPrivateKey, blsPublicKey: keys.blsKey, blsSecretKey: keys.blsPrivateKey, }); this._logger.info(`Block generation enabled for address: ${lisk_cryptography_1.address.getLisk32AddressFromAddress(key)}`); } } } async _getUnconfirmedTransactionsFromNetwork() { this._logger.info('Loading transactions from the network'); const { data } = (await this._network.request({ procedure: constants_1.NETWORK_RPC_GET_TRANSACTIONS, })); const transactionResponse = lisk_codec_1.codec.decode(schemas_1.getTransactionsResponseSchema, data); lisk_validator_1.validator.validate(schemas_1.getTransactionsResponseSchema, transactionResponse); const transactions = transactionResponse.transactions.map(transaction => lisk_chain_1.Transaction.fromBytes(transaction)); for (const transaction of transactions) { const { error } = await this._pool.add(transaction); if (error) { this._logger.error({ err: error }, 'Failed to add transaction to pool.'); throw error; } } } async _generateLoop() { if (this._consensus.syncing()) { return; } const stateStore = new lisk_chain_1.StateStore(this._blockchainDB); const MS_IN_A_SEC = 1000; const currentTime = Math.floor(new Date().getTime() / MS_IN_A_SEC); const currentSlot = this._bft.method.getSlotNumber(currentTime); const currentSlotTime = this._bft.method.getSlotTime(currentSlot); const waitThreshold = this._blockTime / 5; const lastBlockSlot = this._bft.method.getSlotNumber(this._chain.lastBlock.header.timestamp); if (currentSlot === lastBlockSlot) { this._logger.trace({ slot: currentSlot }, 'Block already generated for the current slot'); return; } const nextHeight = this._chain.lastBlock.header.height + 1; const generator = await this._bft.method.getGeneratorAtTimestamp(stateStore, nextHeight, currentTime); const validatorKeypair = this._keypairs.get(generator.address); if (validatorKeypair === undefined) { this._logger.debug({ currentSlot }, 'Waiting for validator slot'); return; } if (lastBlockSlot < currentSlot - 1 && currentTime <= currentSlotTime + waitThreshold) { this._logger.info('Skipping forging to wait for last block'); this._logger.debug({ currentSlot, lastBlockSlot, waitThreshold, }, 'Slot information'); return; } const generatedBlock = await this._generateBlock({ height: nextHeight, generatorAddress: generator.address, privateKey: validatorKeypair.privateKey, timestamp: currentTime, }); this._logger.info({ id: generatedBlock.header.id, height: generatedBlock.header.height, generatorAddress: lisk_cryptography_1.address.getLisk32AddressFromAddress(generator.address), }, 'Generated new block'); await this._consensus.execute(generatedBlock); } async _generateBlock(input) { var _a; const { generatorAddress, timestamp, privateKey, height } = input; const stateStore = new lisk_chain_1.StateStore(this._blockchainDB); const generatorStore = new generator_store_1.GeneratorStore((_a = input.db) !== null && _a !== void 0 ? _a : this._generatorDB); const { maxHeightPrevoted } = await this._bft.method.getBFTHeights(stateStore); const { height: maxHeightGenerated } = await (0, generated_info_1.getOrDefaultLastGeneratedInfo)(generatorStore, generatorAddress); const aggregateCommit = await this._consensus.getAggregateCommit(stateStore); const impliesMaxPrevotes = await this._bft.method.impliesMaximalPrevotes(stateStore, { height, maxHeightGenerated, generatorAddress, }); const blockHeader = new lisk_chain_1.BlockHeader({ generatorAddress, height, previousBlockID: this._chain.lastBlock.header.id, version: BLOCK_VERSION, maxHeightPrevoted, maxHeightGenerated, impliesMaxPrevotes, aggregateCommit, assetRoot: Buffer.alloc(0), stateRoot: Buffer.alloc(0), eventRoot: Buffer.alloc(0), transactionRoot: Buffer.alloc(0), validatorsHash: Buffer.alloc(0), signature: Buffer.alloc(0), timestamp, }); const blockEvents = []; let transactions; const { contextID } = await this._abi.initStateMachine({ header: blockHeader.toObject(), }); const { assets } = await this._abi.insertAssets({ contextID, finalizedHeight: this._chain.finalizedHeight, }); const blockAssets = new lisk_chain_1.BlockAssets(assets); const maxRemovalHeight = await this._consensus.getMaxRemovalHeight(); await this._bft.beforeTransactionsExecute(stateStore, blockHeader, maxRemovalHeight); const { events: beforeTxsEvents } = await this._abi.beforeTransactionsExecute({ contextID, assets: blockAssets.getAll(), }); blockEvents.push(...beforeTxsEvents.map(e => new lisk_chain_1.Event(e))); if (input.transactions) { const { transactions: executedTxs, events: txEvents } = await this._executeTransactions(contextID, blockHeader, blockAssets, input.transactions); blockEvents.push(...txEvents); transactions = executedTxs; } else { const { transactions: executedTxs, events: txEvents } = await this._forgingStrategy.getTransactionsForBlock(contextID, blockHeader, blockAssets); blockEvents.push(...txEvents); transactions = executedTxs; } const afterResult = await this._abi.afterTransactionsExecute({ contextID, assets: blockAssets.getAll(), transactions: transactions.map(tx => tx.toObject()), }); blockEvents.push(...afterResult.events.map(e => new lisk_chain_1.Event(e))); if (!(0, consensus_1.isEmptyConsensusUpdate)(afterResult.preCommitThreshold, afterResult.certificateThreshold, afterResult.nextValidators)) { await this._bft.method.setBFTParameters(stateStore, afterResult.preCommitThreshold, afterResult.certificateThreshold, afterResult.nextValidators); } stateStore.finalize(new lisk_db_1.Batch()); const txTree = new lisk_tree_1.MerkleTree(); await txTree.init(transactions.map(tx => tx.id)); const transactionRoot = txTree.root; blockHeader.transactionRoot = transactionRoot; blockHeader.assetRoot = await blockAssets.getRoot(); const keypairs = []; for (let index = 0; index < blockEvents.length; index += 1) { const e = blockEvents[index]; e.setIndex(index); const pairs = e.keyPair(); for (const pair of pairs) { keypairs.push(pair); } } const smt = new lisk_db_1.SparseMerkleTree(lisk_chain_1.EVENT_KEY_LENGTH); const eventRoot = await smt.update(constants_1.EMPTY_HASH, keypairs); blockHeader.eventRoot = eventRoot; const { stateRoot } = await this._abi.commit({ contextID, dryRun: true, expectedStateRoot: Buffer.alloc(0), stateRoot: this._chain.lastBlock.header.stateRoot, }); blockHeader.stateRoot = stateRoot; const { validatorsHash } = await this._bft.method.getBFTParameters(stateStore, height + 1); blockHeader.validatorsHash = validatorsHash; blockHeader.sign(this._chain.chainID, privateKey); const generatedBlock = new lisk_chain_1.Block(blockHeader, transactions, blockAssets); await (0, generated_info_1.setLastGeneratedInfo)(generatorStore, blockHeader.generatorAddress, blockHeader); const batch = new lisk_db_1.Batch(); generatorStore.finalize(batch); await this._generatorDB.write(batch); await this._abi.clear({}); this._metrics.blockGeneration.inc(1); return generatedBlock; } async _executeTransactions(contextID, header, assets, transactions) { const executedTransactions = []; const executedEvents = []; for (const transaction of transactions) { try { const { result: verifyResult } = await this._abi.verifyTransaction({ contextID, transaction, header: header.toObject(), onlyCommand: false, }); if (verifyResult !== abi_1.TransactionVerifyResult.OK) { throw new Error('Transaction is not valid'); } const { events: txEvents, result: executeResult } = await this._abi.executeTransaction({ contextID, header: header.toObject(), transaction, assets: assets.getAll(), dryRun: false, }); if (executeResult === abi_1.TransactionExecutionResult.INVALID) { this._pool.remove(transaction); throw new Error('Transaction is not valid'); } executedTransactions.push(transaction); executedEvents.push(...txEvents.map(e => new lisk_chain_1.Event(e))); } catch (error) { continue; } } return { transactions: executedTransactions, events: executedEvents }; } } exports.Generator = Generator; //# sourceMappingURL=generator.js.map