diff --git a/packages/js-drive/lib/abci/handlers/prepareProposalHandlerFactory.js b/packages/js-drive/lib/abci/handlers/prepareProposalHandlerFactory.js index f567d9c3342..b90a89101a6 100644 --- a/packages/js-drive/lib/abci/handlers/prepareProposalHandlerFactory.js +++ b/packages/js-drive/lib/abci/handlers/prepareProposalHandlerFactory.js @@ -143,6 +143,13 @@ function prepareProposalHandlerFactory( + ` (valid txs = ${validTxCount}, invalid txs = ${invalidTxCount})`, ); + proposalBlockExecutionContext.setPrepareProposalResult({ + appHash, + txResults, + consensusParamUpdates, + validatorSetUpdate, + }); + return new ResponsePrepareProposal({ appHash, txResults, diff --git a/packages/js-drive/lib/abci/handlers/processProposalHandlerFactory.js b/packages/js-drive/lib/abci/handlers/processProposalHandlerFactory.js index 8287532c45c..a65523b2477 100644 --- a/packages/js-drive/lib/abci/handlers/processProposalHandlerFactory.js +++ b/packages/js-drive/lib/abci/handlers/processProposalHandlerFactory.js @@ -62,6 +62,29 @@ function processProposalHandlerFactory( consensusLogger.debug('ProcessProposal ABCI method requested'); consensusLogger.trace({ abciRequest: request }); + const prepareProposalResult = proposalBlockExecutionContext.getPrepareProposalResult(); + + if (prepareProposalResult + && proposalBlockExecutionContext.getHeight().toNumber() === height.toNumber() + && proposalBlockExecutionContext.getRound() === round) { + consensusLogger.debug('Returning cached result'); + + const { + appHash, + txResults, + consensusParamUpdates, + validatorSetUpdate, + } = prepareProposalResult; + + return new ResponseProcessProposal({ + status: proposalStatus.ACCEPT, + appHash, + txResults, + consensusParamUpdates, + validatorSetUpdate, + }); + } + if (coreChainLockUpdate) { const chainLockIsValid = await verifyChainLock(coreChainLockUpdate); diff --git a/packages/js-drive/lib/blockExecution/BlockExecutionContext.js b/packages/js-drive/lib/blockExecution/BlockExecutionContext.js index eec2053dd5b..b1c21afa7cd 100644 --- a/packages/js-drive/lib/blockExecution/BlockExecutionContext.js +++ b/packages/js-drive/lib/blockExecution/BlockExecutionContext.js @@ -219,6 +219,28 @@ class BlockExecutionContext { return this.round; } + /** + * Set PrepareProposal Result + * + * @param {Object} prepareProposalResult + * + * @returns {BlockExecutionContext} + */ + setPrepareProposalResult(prepareProposalResult) { + this.prepareProposalResult = prepareProposalResult; + + return this; + } + + /** + * Get PrepareProposal Result + * + * @return {Object} + */ + getPrepareProposalResult() { + return this.prepareProposalResult; + } + /** * Reset state */ @@ -234,6 +256,7 @@ class BlockExecutionContext { this.round = null; this.epochInfo = null; this.timeMs = null; + this.prepareProposalResult = null; } /** @@ -257,11 +280,12 @@ class BlockExecutionContext { this.height = blockExecutionContext.height; this.coreChainLockedHeight = blockExecutionContext.coreChainLockedHeight; this.version = blockExecutionContext.version; - this.consensusLogger = blockExecutionContext.consensusLogger; + this.consensusLogger = blockExecutionContext.consensusLogger || null; this.withdrawalTransactionsMap = blockExecutionContext.withdrawalTransactionsMap; this.round = blockExecutionContext.round; this.epochInfo = blockExecutionContext.epochInfo; this.timeMs = blockExecutionContext.timeMs; + this.prepareProposalResult = blockExecutionContext.prepareProposalResult || null; } /** @@ -281,11 +305,13 @@ class BlockExecutionContext { this.version = Consensus.fromObject(object.version); this.withdrawalTransactionsMap = object.withdrawalTransactionsMap; this.round = object.round; + this.prepareProposalResult = object.prepareProposalResult; } /** * @param {Object} options * @param {boolean} [options.skipConsensusLogger=false] + * @param {boolean} [options.skipPrepareProposalResult=false] * @return {{ * dataContracts: Object[], * height: number, @@ -322,6 +348,10 @@ class BlockExecutionContext { object.consensusLogger = this.consensusLogger; } + if (!options.skipPrepareProposalResult) { + object.prepareProposalResult = this.prepareProposalResult; + } + return object; } } diff --git a/packages/js-drive/lib/blockExecution/BlockExecutionContextRepository.js b/packages/js-drive/lib/blockExecution/BlockExecutionContextRepository.js index 553b697c787..bcd6d45c585 100644 --- a/packages/js-drive/lib/blockExecution/BlockExecutionContextRepository.js +++ b/packages/js-drive/lib/blockExecution/BlockExecutionContextRepository.js @@ -24,6 +24,7 @@ class BlockExecutionContextRepository { BlockExecutionContextRepository.EXTERNAL_STORE_KEY_NAME, await cbor.encodeAsync(blockExecutionContext.toObject({ skipConsensusLogger: true, + skipPrepareProposalResult: true, })), options, ); diff --git a/packages/js-drive/lib/test/fixtures/getBlockExecutionContextObjectFixture.js b/packages/js-drive/lib/test/fixtures/getBlockExecutionContextObjectFixture.js index 16339b949a9..c9abca30cd6 100644 --- a/packages/js-drive/lib/test/fixtures/getBlockExecutionContextObjectFixture.js +++ b/packages/js-drive/lib/test/fixtures/getBlockExecutionContextObjectFixture.js @@ -2,6 +2,10 @@ const { tendermint: { abci: { CommitInfo, + ValidatorSetUpdate, + }, + types: { + ConsensusParams, }, }, } = require('@dashevo/abci/types'); @@ -60,6 +64,25 @@ function getBlockExecutionContextObjectFixture(dataContract = getDataContractFix [hash(txTwoBytes).toString('hex')]: txTwoBytes, }, round: 42, + prepareProposalResult: { + appHash: Buffer.alloc(32, 3), + txResults: new Array(3).fill({ code: 0 }), + consensusParamUpdates: new ConsensusParams({ + block: { + maxBytes: 1, + maxGas: 2, + }, + evidence: { + maxAgeDuration: null, + maxAgeNumBlocks: 1, + maxBytes: 2, + }, + version: { + appVersion: 1, + }, + }), + validatorSetUpdate: new ValidatorSetUpdate(), + }, }; } diff --git a/packages/js-drive/lib/test/mock/BlockExecutionContextMock.js b/packages/js-drive/lib/test/mock/BlockExecutionContextMock.js index 0b7590ef2b5..dd2f95d5bee 100644 --- a/packages/js-drive/lib/test/mock/BlockExecutionContextMock.js +++ b/packages/js-drive/lib/test/mock/BlockExecutionContextMock.js @@ -55,6 +55,8 @@ class BlockExecutionContextMock { this.getTimeMs = sinon.stub(); this.setRound = sinon.stub(); this.getRound = sinon.stub(); + this.getPrepareProposalResult = sinon.stub(); + this.setPrepareProposalResult = sinon.stub(); } } diff --git a/packages/js-drive/test/integration/blockExecution/BlockExecutionContextRepository.spec.js b/packages/js-drive/test/integration/blockExecution/BlockExecutionContextRepository.spec.js index 240bce1275a..6895d6e4359 100644 --- a/packages/js-drive/test/integration/blockExecution/BlockExecutionContextRepository.spec.js +++ b/packages/js-drive/test/integration/blockExecution/BlockExecutionContextRepository.spec.js @@ -58,6 +58,7 @@ describe('BlockExecutionContextRepository', () => { expect(rawBlockExecutionContext).to.deep.equal(blockExecutionContext.toObject({ skipConsensusLogger: true, + skipPrepareProposalResult: true, })); }); @@ -76,8 +77,10 @@ describe('BlockExecutionContextRepository', () => { expect(fetchedBlockExecutionContext.toObject({ skipConsensusLogger: true, + skipPrepareProposalResult: true, })).to.deep.equal(blockExecutionContext.toObject({ skipConsensusLogger: true, + skipPrepareProposalResult: true, })); }); @@ -100,8 +103,10 @@ describe('BlockExecutionContextRepository', () => { expect(fetchedBlockExecutionContext.toObject({ skipConsensusLogger: true, + skipPrepareProposalResult: true, })).to.deep.equal(blockExecutionContext.toObject({ skipConsensusLogger: true, + skipPrepareProposalResult: true, })); }); }); diff --git a/packages/js-drive/test/unit/abci/handlers/prepareProposalHandlerFactory.spec.js b/packages/js-drive/test/unit/abci/handlers/prepareProposalHandlerFactory.spec.js index ba320703258..6631ea60c05 100644 --- a/packages/js-drive/test/unit/abci/handlers/prepareProposalHandlerFactory.spec.js +++ b/packages/js-drive/test/unit/abci/handlers/prepareProposalHandlerFactory.spec.js @@ -32,7 +32,7 @@ describe('prepareProposalHandlerFactory', () => { let validatorSetUpdate; let coreChainLockUpdate; let endBlockResult; - let proposalBlockExecutionMock; + let proposalBlockExecutionContextMock; let round; beforeEach(function beforeEach() { @@ -60,7 +60,7 @@ describe('prepareProposalHandlerFactory', () => { }); validatorSetUpdate = new ValidatorSetUpdate(); - proposalBlockExecutionMock = new BlockExecutionContextMock(this.sinon); + proposalBlockExecutionContextMock = new BlockExecutionContextMock(this.sinon); loggerMock = new LoggerMock(this.sinon); @@ -86,7 +86,7 @@ describe('prepareProposalHandlerFactory', () => { prepareProposalHandler = prepareProposalHandlerFactory( deliverTxMock, loggerMock, - proposalBlockExecutionMock, + proposalBlockExecutionContextMock, beginBlockMock, endBlockMock, updateCoreChainLockMock, @@ -173,6 +173,13 @@ describe('prepareProposalHandlerFactory', () => { expect(fees.storageFee).to.equal(3); expect(fees.processingFee).to.equal(6); + + expect(proposalBlockExecutionContextMock.setPrepareProposalResult).to.be.calledOnceWithExactly({ + appHash, + txResults: new Array(3).fill({ code: 0 }), + consensusParamUpdates, + validatorSetUpdate, + }); }); it('should cut txs that are not fit into the size limit', async () => { diff --git a/packages/js-drive/test/unit/abci/handlers/processProposalHandlerFactory.spec.js b/packages/js-drive/test/unit/abci/handlers/processProposalHandlerFactory.spec.js index ba262317555..4adcd9f7ed5 100644 --- a/packages/js-drive/test/unit/abci/handlers/processProposalHandlerFactory.spec.js +++ b/packages/js-drive/test/unit/abci/handlers/processProposalHandlerFactory.spec.js @@ -163,4 +163,35 @@ describe('processProposalHandlerFactory', () => { expect(result).to.be.an.instanceOf(ResponseProcessProposal); expect(result.status).to.equal(2); }); + + it('should return prepareProposalResult from execution context', async () => { + proposalBlockExecutionContextMock.getHeight.returns(request.height); + proposalBlockExecutionContextMock.getRound.returns(request.round); + + proposalBlockExecutionContextMock.getPrepareProposalResult.returns({ + appHash, + txResults: new Array(3).fill({ code: 0 }), + consensusParamUpdates, + validatorSetUpdate, + }); + + const result = await processProposalHandler(request); + + expect(proposalBlockExecutionContextMock.getPrepareProposalResult).to.be.calledOnce(); + + expect(result).to.be.an.instanceOf(ResponseProcessProposal); + expect(result.status).to.equal(1); + expect(result.appHash).to.equal(appHash); + expect(result.txResults).to.be.deep.equal(new Array(3).fill({ code: 0 })); + expect(result.consensusParamUpdates).to.be.equal(consensusParamUpdates); + expect(result.validatorSetUpdate).to.be.equal(validatorSetUpdate); + + expect(beginBlockMock).to.not.be.called(); + + expect(deliverTxMock).to.not.be.called(); + + expect(verifyChainLockMock).to.not.be.called(); + + expect(endBlockMock).to.not.be.called(); + }); }); diff --git a/packages/js-drive/test/unit/blockExecution/BlockExecutionContext.spec.js b/packages/js-drive/test/unit/blockExecution/BlockExecutionContext.spec.js index 52e5e4f3ff1..90b09bbb355 100644 --- a/packages/js-drive/test/unit/blockExecution/BlockExecutionContext.spec.js +++ b/packages/js-drive/test/unit/blockExecution/BlockExecutionContext.spec.js @@ -26,6 +26,7 @@ describe('BlockExecutionContext', () => { let version; let epochInfo; let timeMs; + let prepareProposalResult; beforeEach(() => { blockExecutionContext = new BlockExecutionContext(); @@ -42,6 +43,7 @@ describe('BlockExecutionContext', () => { version = Consensus.fromObject(plainObject.version); epochInfo = plainObject.epochInfo; timeMs = plainObject.timeMs; + prepareProposalResult = plainObject.prepareProposalResult; }); describe('#addDataContract', () => { @@ -224,6 +226,30 @@ describe('BlockExecutionContext', () => { }); }); + describe('#setPrepareProposalResult', () => { + it('should set PrepareProposal result', async () => { + const result = blockExecutionContext.setPrepareProposalResult( + plainObject.prepareProposalResult, + ); + + expect(result).to.equal(blockExecutionContext); + + expect(blockExecutionContext.prepareProposalResult).to.deep.equal( + plainObject.prepareProposalResult, + ); + }); + }); + + describe('#getPrepareProposalResult', () => { + it('should get PrepareProposal result', async () => { + blockExecutionContext.prepareProposalResult = plainObject.prepareProposalResult; + + expect(blockExecutionContext.getPrepareProposalResult()).to.deep.equal( + plainObject.prepareProposalResult, + ); + }); + }); + describe('#setTimeMs', () => { it('should set time', async () => { blockExecutionContext.setTimeMs(timeMs); @@ -307,6 +333,7 @@ describe('BlockExecutionContext', () => { blockExecutionContext.timeMs = timeMs; blockExecutionContext.withdrawalTransactionsMap = plainObject.withdrawalTransactionsMap; blockExecutionContext.round = plainObject.round; + blockExecutionContext.prepareProposalResult = plainObject.prepareProposalResult; expect(blockExecutionContext.toObject()).to.deep.equal(plainObject); }); @@ -322,6 +349,7 @@ describe('BlockExecutionContext', () => { blockExecutionContext.round = plainObject.round; blockExecutionContext.epochInfo = epochInfo; blockExecutionContext.timeMs = timeMs; + blockExecutionContext.prepareProposalResult = prepareProposalResult; const result = blockExecutionContext.toObject({ skipConsensusLogger: true }); @@ -329,6 +357,25 @@ describe('BlockExecutionContext', () => { expect(result).to.deep.equal(plainObject); }); + + it('should skipPrepareProposalResult if the option passed', () => { + blockExecutionContext.dataContracts = [dataContract]; + blockExecutionContext.lastCommitInfo = lastCommitInfo; + blockExecutionContext.height = height; + blockExecutionContext.coreChainLockedHeight = coreChainLockedHeight; + blockExecutionContext.version = version; + blockExecutionContext.consensusLogger = logger; + blockExecutionContext.withdrawalTransactionsMap = plainObject.withdrawalTransactionsMap; + blockExecutionContext.round = plainObject.round; + blockExecutionContext.epochInfo = epochInfo; + blockExecutionContext.timeMs = timeMs; + + const result = blockExecutionContext.toObject({ skipPrepareProposalResult: true }); + + delete plainObject.prepareProposalResult; + + expect(result).to.deep.equal(plainObject); + }); }); describe('#fromObject', () => {