From 5ad1799cb2d335001928897c05e893ddffa3203c Mon Sep 17 00:00:00 2001 From: Konstantin Shuplenkov Date: Fri, 17 Dec 2021 16:25:35 +0300 Subject: [PATCH 1/8] feat(drive): add network address to validator set --- .../validator/createValidatorSetUpdate.js | 3 ++ .../lib/core/fetchProTxInfoFactory.js | 36 +++++++++++++++++++ packages/js-drive/lib/createDIContainer.js | 4 +++ packages/js-drive/lib/validator/Validator.js | 28 +++++++++++++-- .../lib/validator/ValidatorNetworkInfo.js | 29 +++++++++++++++ .../js-drive/lib/validator/ValidatorSet.js | 11 ++++-- .../lib/validator/fetchNetworkInfoFactory.js | 24 +++++++++++++ 7 files changed, 131 insertions(+), 4 deletions(-) create mode 100644 packages/js-drive/lib/core/fetchProTxInfoFactory.js create mode 100644 packages/js-drive/lib/validator/ValidatorNetworkInfo.js create mode 100644 packages/js-drive/lib/validator/fetchNetworkInfoFactory.js diff --git a/packages/js-drive/lib/abci/handlers/validator/createValidatorSetUpdate.js b/packages/js-drive/lib/abci/handlers/validator/createValidatorSetUpdate.js index dc2f3bce397..60287f54b5e 100644 --- a/packages/js-drive/lib/abci/handlers/validator/createValidatorSetUpdate.js +++ b/packages/js-drive/lib/abci/handlers/validator/createValidatorSetUpdate.js @@ -18,9 +18,12 @@ const { function createValidatorSetUpdate(validatorSet) { const validatorUpdates = validatorSet.getValidators() .map((validator) => { + const networkInfo = validator.getNetworkInfo(); + const validatorUpdate = new ValidatorUpdate({ power: validator.getVotingPower(), proTxHash: validator.getProTxHash(), + nodeAddress: `tcp://${networkInfo.getHost()}:${networkInfo.getPort()}`, }); if (validator.getPublicKeyShare()) { diff --git a/packages/js-drive/lib/core/fetchProTxInfoFactory.js b/packages/js-drive/lib/core/fetchProTxInfoFactory.js new file mode 100644 index 00000000000..ce200e2e2aa --- /dev/null +++ b/packages/js-drive/lib/core/fetchProTxInfoFactory.js @@ -0,0 +1,36 @@ +/** + * @param {RpcClient} + * @return {fetchProTxInfo} + */ +function fetchProTxInfoFactory(coreRpcClient) { + /** + * @typedef fetchProTxInfo + * + * @param {string} proTxHash + * + * @returns {Promise<{{ state: { service: string } }}>} + */ + async function fetchProTxInfo(proTxHash) { + try { + const { + result, + } = await coreRpcClient.protx( + 'info', + proTxHash, + ); + + return result; + } catch (e) { + // RPC_INVALID_PARAMETER: protx not found + if (e.code === -8) { + throw new Error(`Protx with hash ${proTxHash} was not found`); + } + + throw e; + } + } + + return fetchProTxInfo; +} + +module.exports = fetchProTxInfoFactory; diff --git a/packages/js-drive/lib/createDIContainer.js b/packages/js-drive/lib/createDIContainer.js index 962ad3183b7..fcdfed64541 100644 --- a/packages/js-drive/lib/createDIContainer.js +++ b/packages/js-drive/lib/createDIContainer.js @@ -129,6 +129,8 @@ const fetchQuorumMembersFactory = require('./core/fetchQuorumMembersFactory'); const getRandomQuorum = require('./core/getRandomQuorum'); const createQueryResponseFactory = require('./abci/handlers/query/response/createQueryResponseFactory'); const BlockExecutionContextRepository = require('./blockExecution/BlockExecutionContextRepository'); +const fetchProTxInfoFactory = require('./core/fetchProTxInfoFactory'); +const fetchNetworkInfoFactory = require('./validator/fetchNetworkInfoFactory'); /** * @@ -306,6 +308,7 @@ function createDIContainer(options) { decodeChainLock: asValue(decodeChainLock), fetchQuorumMembers: asFunction(fetchQuorumMembersFactory), getRandomQuorum: asValue(getRandomQuorum), + fetchProTxInfo: asFunction(fetchProTxInfoFactory), }); /** @@ -1006,6 +1009,7 @@ function createDIContainer(options) { * Register validator quorums */ container.register({ + fetchNetworkInfo: asFunction(fetchNetworkInfoFactory), validatorSet: asClass(ValidatorSet), }); diff --git a/packages/js-drive/lib/validator/Validator.js b/packages/js-drive/lib/validator/Validator.js index 613a24c99b0..579d4eb93b5 100644 --- a/packages/js-drive/lib/validator/Validator.js +++ b/packages/js-drive/lib/validator/Validator.js @@ -8,6 +8,7 @@ class Validator { constructor(proTxHash, pubKeyShare = undefined) { this.proTxHash = proTxHash; this.pubKeyShare = pubKeyShare; + this.networkInfo = null; } /** @@ -36,12 +37,31 @@ class Validator { return Validator.DEFAULT_DASH_VOTING_POWER; } + /** + * Get network info + * + * @returns {ValidatorNetworkInfo} + */ + getNetworkInfo() { + return this.networkInfo; + } + + /** + * Set network info + * + * @param {ValidatorNetworkInfo} networkInfo + */ + setNetworkInfo(networkInfo) { + this.networkInfo = networkInfo; + } + /** * @param {Object} member + * @param {ValidatorNetworkInfo} networkInfo * @param {boolean} [pubKeyShareRequired=false] * @return {Validator} */ - static createFromQuorumMember(member, pubKeyShareRequired = false) { + static createFromQuorumMember(member, networkInfo, pubKeyShareRequired = false) { const proTxHash = Buffer.from(member.proTxHash, 'hex'); let pubKeyShare; @@ -51,7 +71,11 @@ class Validator { throw new PublicKeyShareIsNotPresentError(member); } - return new Validator(proTxHash, pubKeyShare); + const validator = new Validator(proTxHash, pubKeyShare); + + validator.setNetworkInfo(networkInfo); + + return validator; } } diff --git a/packages/js-drive/lib/validator/ValidatorNetworkInfo.js b/packages/js-drive/lib/validator/ValidatorNetworkInfo.js new file mode 100644 index 00000000000..91593bd4f8f --- /dev/null +++ b/packages/js-drive/lib/validator/ValidatorNetworkInfo.js @@ -0,0 +1,29 @@ +class ValidatorNetworkInfo { + /** + * + * @param {string} host + * @param {number} port + */ + constructor(host, port) { + this.host = host; + this.port = port; + } + + /** + * Get validator host + * @returns {string} + */ + getHost() { + return this.host; + } + + /** + * Get validator port + * @returns {number} + */ + getPort() { + return this.port; + } +} + +module.exports = ValidatorNetworkInfo; diff --git a/packages/js-drive/lib/validator/ValidatorSet.js b/packages/js-drive/lib/validator/ValidatorSet.js index 7bf14a82b1c..1722eae3bb9 100644 --- a/packages/js-drive/lib/validator/ValidatorSet.js +++ b/packages/js-drive/lib/validator/ValidatorSet.js @@ -8,6 +8,7 @@ class ValidatorSet { * @param {fetchQuorumMembers} fetchQuorumMembers * @param {number} validatorSetLLMQType * @param {RpcClient} coreRpcClient + * @param {fetchNetworkInfo} fetchNetworkInfo */ constructor( simplifiedMasternodeList, @@ -15,12 +16,14 @@ class ValidatorSet { fetchQuorumMembers, validatorSetLLMQType, coreRpcClient, + fetchNetworkInfo, ) { this.simplifiedMasternodeList = simplifiedMasternodeList; this.getRandomQuorum = getRandomQuorum; this.fetchQuorumMembers = fetchQuorumMembers; this.validatorSetLLMQType = validatorSetLLMQType; this.coreRpcClient = coreRpcClient; + this.fetchNetworkInfo = fetchNetworkInfo; this.quorum = null; this.validators = []; @@ -133,9 +136,13 @@ class ValidatorSet { const isThisNodeMember = !!quorumMembers .find((member) => member.valid && member.proTxHash === proTxHash); - this.validators = quorumMembers + this.validators = await Promise.all(quorumMembers .filter((member) => member.valid) - .map((member) => Validator.createFromQuorumMember(member, isThisNodeMember)); + .map(async (member) => { + const networkInfo = await this.fetchNetworkInfo(member); + + return Validator.createFromQuorumMember(member, networkInfo, isThisNodeMember); + })); } } diff --git a/packages/js-drive/lib/validator/fetchNetworkInfoFactory.js b/packages/js-drive/lib/validator/fetchNetworkInfoFactory.js new file mode 100644 index 00000000000..f1d8ebe8018 --- /dev/null +++ b/packages/js-drive/lib/validator/fetchNetworkInfoFactory.js @@ -0,0 +1,24 @@ +const ValidatorNetworkInfo = require('./ValidatorNetworkInfo'); + +/** + * @param {fetchProTxInfo} fetchProTxInfo + * + * @return {fetchNetworkInfo} + */ +function fetchNetworkInfoFactory(fetchProTxInfo) { + /** + * @typedef fetchNetworkInfo + * + * @param {Object} quorumMember + */ + async function fetchNetworkInfo(quorumMember) { + const quorumMemberProTxInfo = await fetchProTxInfo(quorumMember.proTxHash); + const quorumHost = quorumMemberProTxInfo.state.service.split(':')[0]; + + return new ValidatorNetworkInfo(quorumHost, 26656); + } + + return fetchNetworkInfo; +} + +module.exports = fetchNetworkInfoFactory; From 24d9024c2e1f5b5179ec4476626ac5e903be9829 Mon Sep 17 00:00:00 2001 From: Konstantin Shuplenkov Date: Mon, 20 Dec 2021 15:35:46 +0300 Subject: [PATCH 2/8] feat(drive): add tenderdash p2p port --- packages/dashmate/docker-compose.platform.yml | 1 + packages/js-drive/.env.example | 2 + packages/js-drive/lib/createDIContainer.js | 1 + .../lib/validator/fetchNetworkInfoFactory.js | 5 +- .../createValidatorSetUpdate.spec.js | 3 + .../unit/core/fetchProTxInfoFactory.spec.js | 82 +++++++++++++++++++ .../test/unit/validator/Validator.spec.js | 18 +++- .../validator/ValidatorNetworkInfo.spec.js | 24 ++++++ .../test/unit/validator/ValidatorSet.spec.js | 42 ++++++++++ .../validator/fetchNetworkInfoFactory.spec.js | 64 +++++++++++++++ 10 files changed, 238 insertions(+), 4 deletions(-) create mode 100644 packages/js-drive/test/unit/core/fetchProTxInfoFactory.spec.js create mode 100644 packages/js-drive/test/unit/validator/ValidatorNetworkInfo.spec.js create mode 100644 packages/js-drive/test/unit/validator/fetchNetworkInfoFactory.spec.js diff --git a/packages/dashmate/docker-compose.platform.yml b/packages/dashmate/docker-compose.platform.yml index 7e3a8bcc202..f13e03de097 100644 --- a/packages/dashmate/docker-compose.platform.yml +++ b/packages/dashmate/docker-compose.platform.yml @@ -46,6 +46,7 @@ services: - INITIAL_CORE_CHAINLOCKED_HEIGHT=${PLATFORM_DRIVE_TENDERDASH_GENESIS_INITIAL_CORE_CHAIN_LOCKED_HEIGHT:-1} - VALIDATOR_SET_LLMQ_TYPE=${PLATFORM_DRIVE_ABCI_VALIDATOR_SET_LLMQ_TYPE:?err} - NETWORK=${NETWORK} + - TENDERDASH_P2P_PORT=${PLATFORM_DRIVE_TENDERDASH_P2P_PORT} command: yarn workspace @dashevo/drive abci drive_tenderdash: diff --git a/packages/js-drive/.env.example b/packages/js-drive/.env.example index d215e5908d9..1ba48a12385 100644 --- a/packages/js-drive/.env.example +++ b/packages/js-drive/.env.example @@ -91,3 +91,5 @@ LOG_STATE_REPOSITORY=false LOG_MERK=false NODE_ENV=production + +TENDERDASH_P2P_PORT=26656 diff --git a/packages/js-drive/lib/createDIContainer.js b/packages/js-drive/lib/createDIContainer.js index fcdfed64541..d6c81cff9fc 100644 --- a/packages/js-drive/lib/createDIContainer.js +++ b/packages/js-drive/lib/createDIContainer.js @@ -290,6 +290,7 @@ function createDIContainer(options) { return Long.fromString(options.FEATURE_FLAGS_CONTRACT_BLOCK_HEIGHT); }), + p2pPort: asValue(options.TENDERDASH_P2P_PORT), }); /** diff --git a/packages/js-drive/lib/validator/fetchNetworkInfoFactory.js b/packages/js-drive/lib/validator/fetchNetworkInfoFactory.js index f1d8ebe8018..0b0268e3334 100644 --- a/packages/js-drive/lib/validator/fetchNetworkInfoFactory.js +++ b/packages/js-drive/lib/validator/fetchNetworkInfoFactory.js @@ -2,10 +2,11 @@ const ValidatorNetworkInfo = require('./ValidatorNetworkInfo'); /** * @param {fetchProTxInfo} fetchProTxInfo + * @param {number} p2pPort * * @return {fetchNetworkInfo} */ -function fetchNetworkInfoFactory(fetchProTxInfo) { +function fetchNetworkInfoFactory(fetchProTxInfo, p2pPort) { /** * @typedef fetchNetworkInfo * @@ -15,7 +16,7 @@ function fetchNetworkInfoFactory(fetchProTxInfo) { const quorumMemberProTxInfo = await fetchProTxInfo(quorumMember.proTxHash); const quorumHost = quorumMemberProTxInfo.state.service.split(':')[0]; - return new ValidatorNetworkInfo(quorumHost, 26656); + return new ValidatorNetworkInfo(quorumHost, p2pPort); } return fetchNetworkInfo; diff --git a/packages/js-drive/test/unit/abci/handlers/validator/createValidatorSetUpdate.spec.js b/packages/js-drive/test/unit/abci/handlers/validator/createValidatorSetUpdate.spec.js index 6e83418f48d..30c170d277f 100644 --- a/packages/js-drive/test/unit/abci/handlers/validator/createValidatorSetUpdate.spec.js +++ b/packages/js-drive/test/unit/abci/handlers/validator/createValidatorSetUpdate.spec.js @@ -8,6 +8,7 @@ const { const { expect } = require('chai'); const createValidatorSetUpdate = require('../../../../../lib/abci/handlers/validator/createValidatorSetUpdate'); +const ValidatorNetworkInfo = require('../../../../../lib/validator/ValidatorNetworkInfo'); describe('createValidatorSetUpdate', () => { let validatorSetMock; @@ -20,10 +21,12 @@ describe('createValidatorSetUpdate', () => { getPublicKeyShare: this.sinon.stub(), getVotingPower: this.sinon.stub(), getProTxHash: this.sinon.stub(), + getNetworkInfo: this.sinon.stub(), }; validatorMock.getVotingPower.returns(Buffer.alloc(2, 32)); validatorMock.getProTxHash.returns(Buffer.alloc(3, 32)); + validatorMock.getNetworkInfo.returns(new ValidatorNetworkInfo('192.168.65.2', 26656)); validatorSetMock = { getValidators: this.sinon.stub(), diff --git a/packages/js-drive/test/unit/core/fetchProTxInfoFactory.spec.js b/packages/js-drive/test/unit/core/fetchProTxInfoFactory.spec.js new file mode 100644 index 00000000000..bc20ac2bfe7 --- /dev/null +++ b/packages/js-drive/test/unit/core/fetchProTxInfoFactory.spec.js @@ -0,0 +1,82 @@ +const fetchProTxInfoFactory = require('../../../lib/core/fetchProTxInfoFactory'); + +describe('fetchProTxInfoFactory', () => { + let fetchProTxInfo; + let rpcClient; + let proTxInfo; + let proTxHash; + + beforeEach(function beforeEach() { + proTxHash = '542b5ba206d2b30a366b6f6d0cf5e877816d7a252f984a6d920134091b9b11d0'; + proTxInfo = { + proTxHash: '542b5ba206d2b30a366b6f6d0cf5e877816d7a252f984a6d920134091b9b11d0', + collateralHash: 'e6a9a31b5cf64f457ff9c15fb076f7e9a758683a516c0aeb39ab4ce40c5496cf', + collateralIndex: 0, + collateralAddress: 'ySV123ZBEv8FsRC6NnkosB6vZodWTpgaBL', + operatorReward: 0, + state: { + service: '192.168.65.2:20201', + registeredHeight: 629, + lastPaidHeight: 1106, + PoSePenalty: 0, + PoSeRevivedHeight: -1, + PoSeBanHeight: -1, + revocationReason: 0, + ownerAddress: 'yUVs18ALrCc5CSLpvC9UeJ7tHjtdt5eW6a', + votingAddress: 'yUVs18ALrCc5CSLpvC9UeJ7tHjtdt5eW6a', + payoutAddress: 'yZVHb8YQtCkw5QYiHy3uRTQy9VCKbEg4Xe', + pubKeyOperator: '120899de98537efc4d628c258a51f7f4f550360b1f93bc055c2b4238eb48be02ff00308a5ce3641e6410df9cafb89d7f', + }, + confirmations: 481, + metaInfo: { + lastDSQ: 0, + mixingTxCount: 0, + lastOutboundAttempt: 1639781982, + lastOutboundAttemptElapsed: -150, + lastOutboundSuccess: 0, + lastOutboundSuccessElapsed: 1639781832, + }, + }; + + rpcClient = { + protx: this.sinon.stub().resolves({ result: proTxInfo }), + }; + + fetchProTxInfo = fetchProTxInfoFactory(rpcClient); + }); + + it('should fetch proTxInfo', async () => { + const result = await fetchProTxInfo(proTxHash); + + expect(result).to.deep.equal(proTxInfo); + }); + + it('should throw custom error on code -8', async () => { + const error = new Error('Not found'); + error.code = -8; + + rpcClient.protx.throws(error); + + try { + await fetchProTxInfo(proTxHash); + + expect.fail('should throw an error'); + } catch (e) { + expect(e.message).to.equal(`Protx with hash ${proTxHash} was not found`); + } + }); + + it('should throw unknown error', async () => { + const error = new Error('Not found'); + + rpcClient.protx.throws(error); + + try { + await fetchProTxInfo(proTxHash); + + expect.fail('should throw an error'); + } catch (e) { + expect(e).to.equal(error); + } + }); +}); diff --git a/packages/js-drive/test/unit/validator/Validator.spec.js b/packages/js-drive/test/unit/validator/Validator.spec.js index 418add48e0f..c3544e8140b 100644 --- a/packages/js-drive/test/unit/validator/Validator.spec.js +++ b/packages/js-drive/test/unit/validator/Validator.spec.js @@ -1,15 +1,26 @@ const { expect } = require('chai'); const Validator = require('../../../lib/validator/Validator'); +const ValidatorNetworkInfo = require('../../../lib/validator/ValidatorNetworkInfo'); describe('Validator', () => { - describe('createFromQuorumMember', () => { + let networkInfo; + let host; + let port; + + beforeEach(() => { + host = '192.168.65.2'; + port = 26656; + networkInfo = new ValidatorNetworkInfo(host, port); + }); + + describe('#createFromQuorumMember', () => { it('should create an instance from quorum member info', () => { const memberInfo = { proTxHash: Buffer.alloc(0, 32).toString('hex'), pubKeyShare: Buffer.alloc(1, 32).toString('hex'), }; - const instance = Validator.createFromQuorumMember(memberInfo); + const instance = Validator.createFromQuorumMember(memberInfo, networkInfo); expect(instance).to.be.an.instanceOf(Validator); expect(instance.getProTxHash()).to.deep.equal( @@ -18,6 +29,9 @@ describe('Validator', () => { expect(instance.getPublicKeyShare()).to.deep.equal( Buffer.from(memberInfo.pubKeyShare, 'hex'), ); + expect(instance.getNetworkInfo()).to.be.an.instanceOf(ValidatorNetworkInfo); + expect(instance.getNetworkInfo().getHost()).to.equal(host); + expect(instance.getNetworkInfo().getPort()).to.equal(port); }); }); }); diff --git a/packages/js-drive/test/unit/validator/ValidatorNetworkInfo.spec.js b/packages/js-drive/test/unit/validator/ValidatorNetworkInfo.spec.js new file mode 100644 index 00000000000..92ba6b25946 --- /dev/null +++ b/packages/js-drive/test/unit/validator/ValidatorNetworkInfo.spec.js @@ -0,0 +1,24 @@ +const ValidatorNetworkInfo = require('../../../lib/validator/ValidatorNetworkInfo'); + +describe('ValidatorNetworkInfo', () => { + let validatorNetworkInfo; + let host; + let port; + + beforeEach(() => { + host = '192.168.65.2'; + port = 26656; + }); + + it('should return host', () => { + validatorNetworkInfo = new ValidatorNetworkInfo(host, port); + + expect(validatorNetworkInfo.getHost()).to.equal(host); + }); + + it('should return port', () => { + validatorNetworkInfo = new ValidatorNetworkInfo(host, port); + + expect(validatorNetworkInfo.getPort()).to.equal(port); + }); +}); diff --git a/packages/js-drive/test/unit/validator/ValidatorSet.spec.js b/packages/js-drive/test/unit/validator/ValidatorSet.spec.js index 0405fd0713d..c9cc7093636 100644 --- a/packages/js-drive/test/unit/validator/ValidatorSet.spec.js +++ b/packages/js-drive/test/unit/validator/ValidatorSet.spec.js @@ -7,6 +7,7 @@ const getSmlFixture = require('../../../lib/test/fixtures/getSmlFixture'); const ValidatorSetIsNotInitializedError = require('../../../lib/validator/errors/ValidatorSetIsNotInitializedError'); const Validator = require('../../../lib/validator/Validator'); const PublicKeyShareIsNotPresentError = require('../../../lib/validator/errors/PublicKeyShareIsNotPresentError'); +const ValidatorNetworkInfo = require('../../../lib/validator/ValidatorNetworkInfo'); describe('ValidatorSet', () => { let smlStoreMock; @@ -17,7 +18,11 @@ describe('ValidatorSet', () => { let rotationEntropy; let quorumEntry; let coreHeight; + let validatorNetworkInfo; let coreRpcClientMock; + let fetchNetworkInfoMock; + let validatorNetworkHost; + let validatorNetworkPort; let validatorSetLLMQType; @@ -86,12 +91,19 @@ describe('ValidatorSet', () => { masternode: this.sinon.stub().throws(notMasternodeError), }; + validatorNetworkHost = '192.168.65.2'; + validatorNetworkPort = 26656; + validatorNetworkInfo = new ValidatorNetworkInfo(validatorNetworkHost, validatorNetworkPort); + + fetchNetworkInfoMock = this.sinon.stub().resolves(validatorNetworkInfo); + validatorSet = new ValidatorSet( simplifiedMasternodeListMock, getRandomQuorumMock, fetchQuorumMembersMock, validatorSetLLMQType, coreRpcClientMock, + fetchNetworkInfoMock, ); }); @@ -111,6 +123,21 @@ describe('ValidatorSet', () => { validatorSetLLMQType, quorumEntry.quorumHash, ); + + expect(fetchNetworkInfoMock).to.be.calledTwice(); + + expect(fetchNetworkInfoMock.getCall(0)).to.be.calledWithExactly({ + proTxHash: 'c286807d463b06c7aba3b9a60acf64c1fc03da8c1422005cd9b4293f08cf0562', + pubKeyOperator: '06abc1c890c9da4e513d52f20da1882228bfa2db4bb29cbd064e1b2a61d9dcdadcf0784fd1371338c8ad1bf323d87ae6', + valid: true, + pubKeyShare: '00d7bb8d6753865c367824691610dcc313b661b7e024e36e82f8af33f5701caddb2668dadd1e647d8d7d5b30e37ebbcf' + }); + expect(fetchNetworkInfoMock.getCall(1)).to.be.calledWithExactly({ + proTxHash: 'a3e1edc6bd352eeaf0ae58e30781ef4b127854241a3fe7fddf36d5b7e1dc2b3f', + pubKeyOperator: '04d748ba0efeb7a8f8548e0c22b4c188c293a19837a1c5440649279ba73ead0c62ac1e840050a10a35e0ae05659d2a8d', + valid: true, + pubKeyShare: '86d0992f5c73b8f57101c34a0c4ebb17d962bb935a738c1ef1e2bb1c25034d8e4a0a2cc96e0ebc69a7bf3b8b67b2de5f' + }); }); it('should throw an error if the node is a quorum member and doesn\'t receive public key shares', async () => { @@ -157,6 +184,21 @@ describe('ValidatorSet', () => { validatorSetLLMQType, quorumEntry.quorumHash, ); + + expect(fetchNetworkInfoMock).to.be.calledTwice(); + + expect(fetchNetworkInfoMock.getCall(0)).to.be.calledWithExactly({ + proTxHash: 'c286807d463b06c7aba3b9a60acf64c1fc03da8c1422005cd9b4293f08cf0562', + pubKeyOperator: '06abc1c890c9da4e513d52f20da1882228bfa2db4bb29cbd064e1b2a61d9dcdadcf0784fd1371338c8ad1bf323d87ae6', + valid: true, + pubKeyShare: '00d7bb8d6753865c367824691610dcc313b661b7e024e36e82f8af33f5701caddb2668dadd1e647d8d7d5b30e37ebbcf' + }); + expect(fetchNetworkInfoMock.getCall(1)).to.be.calledWithExactly({ + proTxHash: 'a3e1edc6bd352eeaf0ae58e30781ef4b127854241a3fe7fddf36d5b7e1dc2b3f', + pubKeyOperator: '04d748ba0efeb7a8f8548e0c22b4c188c293a19837a1c5440649279ba73ead0c62ac1e840050a10a35e0ae05659d2a8d', + valid: true, + pubKeyShare: '86d0992f5c73b8f57101c34a0c4ebb17d962bb935a738c1ef1e2bb1c25034d8e4a0a2cc96e0ebc69a7bf3b8b67b2de5f' + }); }); it('should not rotate validator set if height not divisible by ROTATION_BLOCK_INTERVAL', async () => { diff --git a/packages/js-drive/test/unit/validator/fetchNetworkInfoFactory.spec.js b/packages/js-drive/test/unit/validator/fetchNetworkInfoFactory.spec.js new file mode 100644 index 00000000000..37b1d8ccee4 --- /dev/null +++ b/packages/js-drive/test/unit/validator/fetchNetworkInfoFactory.spec.js @@ -0,0 +1,64 @@ +const { expect } = require('chai'); +const fetchNetworkInfoFactory = require('../../../lib/validator/fetchNetworkInfoFactory'); +const ValidatorNetworkInfo = require('../../../lib/validator/ValidatorNetworkInfo'); + +describe('fetchNetworkInfoFactory', () => { + let fetchNetworkInfo; + let p2pPort; + let fetchProTxInfoMock; + let proTxInfo; + + beforeEach(function beforeEach() { + proTxInfo = { + proTxHash: '711e53894c649913b3406624c66bdb302ff1263fe692c18677309beba2f3d85d', + collateralHash: '03da4a14589265ab010b24559e2916a40ac3c50786ed15d1ec6b6ce895fb2eda', + collateralIndex: 0, + collateralAddress: 'yTAsMMJaewChEevGKpVpPNkMYgHxhJXw9c', + operatorReward: 0, + state: { + service: '192.168.65.2:20101', + registeredHeight: 626, + lastPaidHeight: 994, + PoSePenalty: 0, + PoSeRevivedHeight: -1, + PoSeBanHeight: -1, + revocationReason: 0, + ownerAddress: 'yanL8fcS9v1KbfoAprcpY6kF1FyBoiHRZK', + votingAddress: 'yanL8fcS9v1KbfoAprcpY6kF1FyBoiHRZK', + payoutAddress: 'yVy5mAY9eKYV1UUtTJHNumycBMMq2Wy5td', + pubKeyOperator: '90b63efdc888f2f3e30ffe5c971f4faaaca3fb9211e9ad05890a64905e52cc4dbd2f5fe37c7a7177f96ff1853981c5d5', + }, + confirmations: 372, + metaInfo: { + lastDSQ: 0, + mixingTxCount: 0, + lastOutboundAttempt: 1639767132, + lastOutboundAttemptElapsed: -600, + lastOutboundSuccess: 0, + lastOutboundSuccessElapsed: 1639766532, + }, + }; + + fetchProTxInfoMock = this.sinon.stub().resolves(proTxInfo); + p2pPort = 26656; + + fetchNetworkInfo = fetchNetworkInfoFactory(fetchProTxInfoMock, p2pPort); + }); + + it('should return ValidatorNetworkInfo', async () => { + const quorumMember = { + proTxHash: '542b5ba206d2b30a366b6f6d0cf5e877816d7a252f984a6d920134091b9b11d0', + pubKeyOperator: '120899de98537efc4d628c258a51f7f4f550360b1f93bc055c2b4238eb48be02ff00308a5ce3641e6410df9cafb89d7f', + valid: true, + pubKeyShare: '83e920c320fbff00e67a9c93f8678fbfc474e0ac4ea98e9b2ea9ea2df8c6fcfa058b4a73342452504c77499ba2ff318d', + }; + + const result = await fetchNetworkInfo(quorumMember); + + expect(result).to.be.an.instanceOf(ValidatorNetworkInfo); + expect(fetchProTxInfoMock).to.be.calledOnceWithExactly(quorumMember.proTxHash); + + expect(result.getPort()).to.equal(p2pPort); + expect(result.getHost()).to.equal('192.168.65.2'); + }); +}); From d1a5128535e189d7628ba5129364f29f9f94691e Mon Sep 17 00:00:00 2001 From: Konstantin Shuplenkov Date: Mon, 20 Dec 2021 15:40:29 +0300 Subject: [PATCH 3/8] fix(test-suite): eslint errors --- packages/platform-test-suite/test/e2e/contacts.spec.js | 10 +++++----- .../test/functional/platform/DataContract.spec.js | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/platform-test-suite/test/e2e/contacts.spec.js b/packages/platform-test-suite/test/e2e/contacts.spec.js index c27278b9eb4..e665ea0fb0b 100644 --- a/packages/platform-test-suite/test/e2e/contacts.spec.js +++ b/packages/platform-test-suite/test/e2e/contacts.spec.js @@ -26,10 +26,10 @@ describe('e2e', () => { type: 'object', indices: [ { - name: "ownerId", + name: 'ownerId', properties: [{ $ownerId: 'asc' }], unique: true, - }, + }, ], properties: { avatarUrl: { @@ -49,12 +49,12 @@ describe('e2e', () => { type: 'object', indices: [ { - name: "onwerIdToUserId", + name: 'onwerIdToUserId', properties: [ { $ownerId: 'asc' }, - { toUserId: 'asc' } + { toUserId: 'asc' }, ], - unique: true + unique: true, }, ], properties: { diff --git a/packages/platform-test-suite/test/functional/platform/DataContract.spec.js b/packages/platform-test-suite/test/functional/platform/DataContract.spec.js index 5aa156fa8ae..92ac99408f6 100644 --- a/packages/platform-test-suite/test/functional/platform/DataContract.spec.js +++ b/packages/platform-test-suite/test/functional/platform/DataContract.spec.js @@ -134,7 +134,7 @@ describe('Platform', () => { type: 'object', indices: [ { - name: "firstNameLastName", + name: 'firstNameLastName', properties: [ { firstName: 'asc' }, { lastName: 'asc' }, From e217fb66bf6bfbe5bc8a5bd6fac867309423dbd3 Mon Sep 17 00:00:00 2001 From: Konstantin Shuplenkov Date: Mon, 20 Dec 2021 16:33:00 +0300 Subject: [PATCH 4/8] fix(test-suite): eslint errors --- .../js-drive/test/unit/validator/ValidatorSet.spec.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/js-drive/test/unit/validator/ValidatorSet.spec.js b/packages/js-drive/test/unit/validator/ValidatorSet.spec.js index c9cc7093636..46afcd5197e 100644 --- a/packages/js-drive/test/unit/validator/ValidatorSet.spec.js +++ b/packages/js-drive/test/unit/validator/ValidatorSet.spec.js @@ -130,13 +130,13 @@ describe('ValidatorSet', () => { proTxHash: 'c286807d463b06c7aba3b9a60acf64c1fc03da8c1422005cd9b4293f08cf0562', pubKeyOperator: '06abc1c890c9da4e513d52f20da1882228bfa2db4bb29cbd064e1b2a61d9dcdadcf0784fd1371338c8ad1bf323d87ae6', valid: true, - pubKeyShare: '00d7bb8d6753865c367824691610dcc313b661b7e024e36e82f8af33f5701caddb2668dadd1e647d8d7d5b30e37ebbcf' + pubKeyShare: '00d7bb8d6753865c367824691610dcc313b661b7e024e36e82f8af33f5701caddb2668dadd1e647d8d7d5b30e37ebbcf', }); expect(fetchNetworkInfoMock.getCall(1)).to.be.calledWithExactly({ proTxHash: 'a3e1edc6bd352eeaf0ae58e30781ef4b127854241a3fe7fddf36d5b7e1dc2b3f', pubKeyOperator: '04d748ba0efeb7a8f8548e0c22b4c188c293a19837a1c5440649279ba73ead0c62ac1e840050a10a35e0ae05659d2a8d', valid: true, - pubKeyShare: '86d0992f5c73b8f57101c34a0c4ebb17d962bb935a738c1ef1e2bb1c25034d8e4a0a2cc96e0ebc69a7bf3b8b67b2de5f' + pubKeyShare: '86d0992f5c73b8f57101c34a0c4ebb17d962bb935a738c1ef1e2bb1c25034d8e4a0a2cc96e0ebc69a7bf3b8b67b2de5f', }); }); @@ -191,13 +191,13 @@ describe('ValidatorSet', () => { proTxHash: 'c286807d463b06c7aba3b9a60acf64c1fc03da8c1422005cd9b4293f08cf0562', pubKeyOperator: '06abc1c890c9da4e513d52f20da1882228bfa2db4bb29cbd064e1b2a61d9dcdadcf0784fd1371338c8ad1bf323d87ae6', valid: true, - pubKeyShare: '00d7bb8d6753865c367824691610dcc313b661b7e024e36e82f8af33f5701caddb2668dadd1e647d8d7d5b30e37ebbcf' + pubKeyShare: '00d7bb8d6753865c367824691610dcc313b661b7e024e36e82f8af33f5701caddb2668dadd1e647d8d7d5b30e37ebbcf', }); expect(fetchNetworkInfoMock.getCall(1)).to.be.calledWithExactly({ proTxHash: 'a3e1edc6bd352eeaf0ae58e30781ef4b127854241a3fe7fddf36d5b7e1dc2b3f', pubKeyOperator: '04d748ba0efeb7a8f8548e0c22b4c188c293a19837a1c5440649279ba73ead0c62ac1e840050a10a35e0ae05659d2a8d', valid: true, - pubKeyShare: '86d0992f5c73b8f57101c34a0c4ebb17d962bb935a738c1ef1e2bb1c25034d8e4a0a2cc96e0ebc69a7bf3b8b67b2de5f' + pubKeyShare: '86d0992f5c73b8f57101c34a0c4ebb17d962bb935a738c1ef1e2bb1c25034d8e4a0a2cc96e0ebc69a7bf3b8b67b2de5f', }); }); From 3126b4378a7ee99c7c438d35c0eaca6e845b5f76 Mon Sep 17 00:00:00 2001 From: Konstantin Shuplenkov Date: Tue, 21 Dec 2021 12:09:52 +0300 Subject: [PATCH 5/8] fix(drive): PR comments --- packages/js-drive/lib/createDIContainer.js | 4 +- .../js-drive/lib/validator/ValidatorSet.js | 17 +++-- .../lib/validator/fetchNetworkInfoFactory.js | 25 -------- .../test/unit/validator/ValidatorSet.spec.js | 59 +++++++---------- .../validator/fetchNetworkInfoFactory.spec.js | 64 ------------------- 5 files changed, 36 insertions(+), 133 deletions(-) delete mode 100644 packages/js-drive/lib/validator/fetchNetworkInfoFactory.js delete mode 100644 packages/js-drive/test/unit/validator/fetchNetworkInfoFactory.spec.js diff --git a/packages/js-drive/lib/createDIContainer.js b/packages/js-drive/lib/createDIContainer.js index d6c81cff9fc..865d8b938cd 100644 --- a/packages/js-drive/lib/createDIContainer.js +++ b/packages/js-drive/lib/createDIContainer.js @@ -130,7 +130,6 @@ const getRandomQuorum = require('./core/getRandomQuorum'); const createQueryResponseFactory = require('./abci/handlers/query/response/createQueryResponseFactory'); const BlockExecutionContextRepository = require('./blockExecution/BlockExecutionContextRepository'); const fetchProTxInfoFactory = require('./core/fetchProTxInfoFactory'); -const fetchNetworkInfoFactory = require('./validator/fetchNetworkInfoFactory'); /** * @@ -290,7 +289,7 @@ function createDIContainer(options) { return Long.fromString(options.FEATURE_FLAGS_CONTRACT_BLOCK_HEIGHT); }), - p2pPort: asValue(options.TENDERDASH_P2P_PORT), + tenderdashP2pPort: asValue(options.TENDERDASH_P2P_PORT), }); /** @@ -1010,7 +1009,6 @@ function createDIContainer(options) { * Register validator quorums */ container.register({ - fetchNetworkInfo: asFunction(fetchNetworkInfoFactory), validatorSet: asClass(ValidatorSet), }); diff --git a/packages/js-drive/lib/validator/ValidatorSet.js b/packages/js-drive/lib/validator/ValidatorSet.js index 1722eae3bb9..976e894522d 100644 --- a/packages/js-drive/lib/validator/ValidatorSet.js +++ b/packages/js-drive/lib/validator/ValidatorSet.js @@ -1,5 +1,6 @@ const Validator = require('./Validator'); const ValidatorSetIsNotInitializedError = require('./errors/ValidatorSetIsNotInitializedError'); +const ValidatorNetworkInfo = require('./ValidatorNetworkInfo'); class ValidatorSet { /** @@ -8,7 +9,7 @@ class ValidatorSet { * @param {fetchQuorumMembers} fetchQuorumMembers * @param {number} validatorSetLLMQType * @param {RpcClient} coreRpcClient - * @param {fetchNetworkInfo} fetchNetworkInfo + * @param {number} tenderdashP2pPort */ constructor( simplifiedMasternodeList, @@ -16,14 +17,14 @@ class ValidatorSet { fetchQuorumMembers, validatorSetLLMQType, coreRpcClient, - fetchNetworkInfo, + tenderdashP2pPort, ) { this.simplifiedMasternodeList = simplifiedMasternodeList; this.getRandomQuorum = getRandomQuorum; this.fetchQuorumMembers = fetchQuorumMembers; this.validatorSetLLMQType = validatorSetLLMQType; this.coreRpcClient = coreRpcClient; - this.fetchNetworkInfo = fetchNetworkInfo; + this.tenderdashP2pPort = tenderdashP2pPort; this.quorum = null; this.validators = []; @@ -136,10 +137,18 @@ class ValidatorSet { const isThisNodeMember = !!quorumMembers .find((member) => member.valid && member.proTxHash === proTxHash); + const validMasternodesList = this.simplifiedMasternodeList + .getStore() + .getCurrentSML() + .getValidMasternodesList(); + this.validators = await Promise.all(quorumMembers .filter((member) => member.valid) .map(async (member) => { - const networkInfo = await this.fetchNetworkInfo(member); + const masternode = validMasternodesList + .find((mnEntry) => mnEntry.proRegTxHash === member.proTxHash); + + const networkInfo = new ValidatorNetworkInfo(masternode.getIp(), this.tenderdashP2pPort); return Validator.createFromQuorumMember(member, networkInfo, isThisNodeMember); })); diff --git a/packages/js-drive/lib/validator/fetchNetworkInfoFactory.js b/packages/js-drive/lib/validator/fetchNetworkInfoFactory.js deleted file mode 100644 index 0b0268e3334..00000000000 --- a/packages/js-drive/lib/validator/fetchNetworkInfoFactory.js +++ /dev/null @@ -1,25 +0,0 @@ -const ValidatorNetworkInfo = require('./ValidatorNetworkInfo'); - -/** - * @param {fetchProTxInfo} fetchProTxInfo - * @param {number} p2pPort - * - * @return {fetchNetworkInfo} - */ -function fetchNetworkInfoFactory(fetchProTxInfo, p2pPort) { - /** - * @typedef fetchNetworkInfo - * - * @param {Object} quorumMember - */ - async function fetchNetworkInfo(quorumMember) { - const quorumMemberProTxInfo = await fetchProTxInfo(quorumMember.proTxHash); - const quorumHost = quorumMemberProTxInfo.state.service.split(':')[0]; - - return new ValidatorNetworkInfo(quorumHost, p2pPort); - } - - return fetchNetworkInfo; -} - -module.exports = fetchNetworkInfoFactory; diff --git a/packages/js-drive/test/unit/validator/ValidatorSet.spec.js b/packages/js-drive/test/unit/validator/ValidatorSet.spec.js index 46afcd5197e..82885a699d3 100644 --- a/packages/js-drive/test/unit/validator/ValidatorSet.spec.js +++ b/packages/js-drive/test/unit/validator/ValidatorSet.spec.js @@ -2,12 +2,12 @@ const Long = require('long'); const QuorumEntry = require('@dashevo/dashcore-lib/lib/deterministicmnlist/QuorumEntry'); +const SimplifiedMNListEntry = require('@dashevo/dashcore-lib/lib/deterministicmnlist/SimplifiedMNListEntry'); const ValidatorSet = require('../../../lib/validator/ValidatorSet'); const getSmlFixture = require('../../../lib/test/fixtures/getSmlFixture'); const ValidatorSetIsNotInitializedError = require('../../../lib/validator/errors/ValidatorSetIsNotInitializedError'); const Validator = require('../../../lib/validator/Validator'); const PublicKeyShareIsNotPresentError = require('../../../lib/validator/errors/PublicKeyShareIsNotPresentError'); -const ValidatorNetworkInfo = require('../../../lib/validator/ValidatorNetworkInfo'); describe('ValidatorSet', () => { let smlStoreMock; @@ -18,10 +18,7 @@ describe('ValidatorSet', () => { let rotationEntropy; let quorumEntry; let coreHeight; - let validatorNetworkInfo; let coreRpcClientMock; - let fetchNetworkInfoMock; - let validatorNetworkHost; let validatorNetworkPort; let validatorSetLLMQType; @@ -47,6 +44,24 @@ describe('ValidatorSet', () => { getQuorumsOfType: this.sinon.stub().returns( getSmlFixture()[0].newQuorums.filter((quorum) => quorum.llmqType === 1), ), + getValidMasternodesList: this.sinon.stub().returns([ + new SimplifiedMNListEntry({ + proRegTxHash: 'c286807d463b06c7aba3b9a60acf64c1fc03da8c1422005cd9b4293f08cf0562', + confirmedHash: '4eb56228c535db3b234907113fd41d57bcc7cdcb8e0e00e57590af27ee88c119', + service: '192.168.65.2:20101', + pubKeyOperator: '809519c5f6f3be1c08782ac42ae9a83b6c7205eba43f9a96a4f032ec7a73f1a7c25fa78cce0d6d9c135f7e2c28527179', + votingAddress: 'yXmprXYP51uzfMyndtWwxz96MnkCKkFc9x', + isValid: true, + }), + new SimplifiedMNListEntry({ + proRegTxHash: 'a3e1edc6bd352eeaf0ae58e30781ef4b127854241a3fe7fddf36d5b7e1dc2b3f', + confirmedHash: '27a0b637b56af038c45e2fd1f06c2401c8dadfa28ca5e0d19ca836cc984a8378', + service: '192.168.65.2:20201', + pubKeyOperator: '987a4873caba62cd45a2f7d4aa6d94519ee6753e9bef777c927cb94ade768a542b0ff34a93231d3a92b4e75ffdaa366e', + votingAddress: 'ycL7L4mhYoaZdm9TH85svvpfeKtdfo249u', + isValid: true, + }), + ]), }; smlStoreMock = { @@ -91,11 +106,7 @@ describe('ValidatorSet', () => { masternode: this.sinon.stub().throws(notMasternodeError), }; - validatorNetworkHost = '192.168.65.2'; validatorNetworkPort = 26656; - validatorNetworkInfo = new ValidatorNetworkInfo(validatorNetworkHost, validatorNetworkPort); - - fetchNetworkInfoMock = this.sinon.stub().resolves(validatorNetworkInfo); validatorSet = new ValidatorSet( simplifiedMasternodeListMock, @@ -103,7 +114,7 @@ describe('ValidatorSet', () => { fetchQuorumMembersMock, validatorSetLLMQType, coreRpcClientMock, - fetchNetworkInfoMock, + validatorNetworkPort, ); }); @@ -124,20 +135,7 @@ describe('ValidatorSet', () => { quorumEntry.quorumHash, ); - expect(fetchNetworkInfoMock).to.be.calledTwice(); - - expect(fetchNetworkInfoMock.getCall(0)).to.be.calledWithExactly({ - proTxHash: 'c286807d463b06c7aba3b9a60acf64c1fc03da8c1422005cd9b4293f08cf0562', - pubKeyOperator: '06abc1c890c9da4e513d52f20da1882228bfa2db4bb29cbd064e1b2a61d9dcdadcf0784fd1371338c8ad1bf323d87ae6', - valid: true, - pubKeyShare: '00d7bb8d6753865c367824691610dcc313b661b7e024e36e82f8af33f5701caddb2668dadd1e647d8d7d5b30e37ebbcf', - }); - expect(fetchNetworkInfoMock.getCall(1)).to.be.calledWithExactly({ - proTxHash: 'a3e1edc6bd352eeaf0ae58e30781ef4b127854241a3fe7fddf36d5b7e1dc2b3f', - pubKeyOperator: '04d748ba0efeb7a8f8548e0c22b4c188c293a19837a1c5440649279ba73ead0c62ac1e840050a10a35e0ae05659d2a8d', - valid: true, - pubKeyShare: '86d0992f5c73b8f57101c34a0c4ebb17d962bb935a738c1ef1e2bb1c25034d8e4a0a2cc96e0ebc69a7bf3b8b67b2de5f', - }); + expect(smlStoreMock.getCurrentSML().getValidMasternodesList).to.be.calledOnce(); }); it('should throw an error if the node is a quorum member and doesn\'t receive public key shares', async () => { @@ -185,20 +183,7 @@ describe('ValidatorSet', () => { quorumEntry.quorumHash, ); - expect(fetchNetworkInfoMock).to.be.calledTwice(); - - expect(fetchNetworkInfoMock.getCall(0)).to.be.calledWithExactly({ - proTxHash: 'c286807d463b06c7aba3b9a60acf64c1fc03da8c1422005cd9b4293f08cf0562', - pubKeyOperator: '06abc1c890c9da4e513d52f20da1882228bfa2db4bb29cbd064e1b2a61d9dcdadcf0784fd1371338c8ad1bf323d87ae6', - valid: true, - pubKeyShare: '00d7bb8d6753865c367824691610dcc313b661b7e024e36e82f8af33f5701caddb2668dadd1e647d8d7d5b30e37ebbcf', - }); - expect(fetchNetworkInfoMock.getCall(1)).to.be.calledWithExactly({ - proTxHash: 'a3e1edc6bd352eeaf0ae58e30781ef4b127854241a3fe7fddf36d5b7e1dc2b3f', - pubKeyOperator: '04d748ba0efeb7a8f8548e0c22b4c188c293a19837a1c5440649279ba73ead0c62ac1e840050a10a35e0ae05659d2a8d', - valid: true, - pubKeyShare: '86d0992f5c73b8f57101c34a0c4ebb17d962bb935a738c1ef1e2bb1c25034d8e4a0a2cc96e0ebc69a7bf3b8b67b2de5f', - }); + expect(smlStoreMock.getCurrentSML().getValidMasternodesList).to.be.calledOnce(); }); it('should not rotate validator set if height not divisible by ROTATION_BLOCK_INTERVAL', async () => { diff --git a/packages/js-drive/test/unit/validator/fetchNetworkInfoFactory.spec.js b/packages/js-drive/test/unit/validator/fetchNetworkInfoFactory.spec.js deleted file mode 100644 index 37b1d8ccee4..00000000000 --- a/packages/js-drive/test/unit/validator/fetchNetworkInfoFactory.spec.js +++ /dev/null @@ -1,64 +0,0 @@ -const { expect } = require('chai'); -const fetchNetworkInfoFactory = require('../../../lib/validator/fetchNetworkInfoFactory'); -const ValidatorNetworkInfo = require('../../../lib/validator/ValidatorNetworkInfo'); - -describe('fetchNetworkInfoFactory', () => { - let fetchNetworkInfo; - let p2pPort; - let fetchProTxInfoMock; - let proTxInfo; - - beforeEach(function beforeEach() { - proTxInfo = { - proTxHash: '711e53894c649913b3406624c66bdb302ff1263fe692c18677309beba2f3d85d', - collateralHash: '03da4a14589265ab010b24559e2916a40ac3c50786ed15d1ec6b6ce895fb2eda', - collateralIndex: 0, - collateralAddress: 'yTAsMMJaewChEevGKpVpPNkMYgHxhJXw9c', - operatorReward: 0, - state: { - service: '192.168.65.2:20101', - registeredHeight: 626, - lastPaidHeight: 994, - PoSePenalty: 0, - PoSeRevivedHeight: -1, - PoSeBanHeight: -1, - revocationReason: 0, - ownerAddress: 'yanL8fcS9v1KbfoAprcpY6kF1FyBoiHRZK', - votingAddress: 'yanL8fcS9v1KbfoAprcpY6kF1FyBoiHRZK', - payoutAddress: 'yVy5mAY9eKYV1UUtTJHNumycBMMq2Wy5td', - pubKeyOperator: '90b63efdc888f2f3e30ffe5c971f4faaaca3fb9211e9ad05890a64905e52cc4dbd2f5fe37c7a7177f96ff1853981c5d5', - }, - confirmations: 372, - metaInfo: { - lastDSQ: 0, - mixingTxCount: 0, - lastOutboundAttempt: 1639767132, - lastOutboundAttemptElapsed: -600, - lastOutboundSuccess: 0, - lastOutboundSuccessElapsed: 1639766532, - }, - }; - - fetchProTxInfoMock = this.sinon.stub().resolves(proTxInfo); - p2pPort = 26656; - - fetchNetworkInfo = fetchNetworkInfoFactory(fetchProTxInfoMock, p2pPort); - }); - - it('should return ValidatorNetworkInfo', async () => { - const quorumMember = { - proTxHash: '542b5ba206d2b30a366b6f6d0cf5e877816d7a252f984a6d920134091b9b11d0', - pubKeyOperator: '120899de98537efc4d628c258a51f7f4f550360b1f93bc055c2b4238eb48be02ff00308a5ce3641e6410df9cafb89d7f', - valid: true, - pubKeyShare: '83e920c320fbff00e67a9c93f8678fbfc474e0ac4ea98e9b2ea9ea2df8c6fcfa058b4a73342452504c77499ba2ff318d', - }; - - const result = await fetchNetworkInfo(quorumMember); - - expect(result).to.be.an.instanceOf(ValidatorNetworkInfo); - expect(fetchProTxInfoMock).to.be.calledOnceWithExactly(quorumMember.proTxHash); - - expect(result.getPort()).to.equal(p2pPort); - expect(result.getHost()).to.equal('192.168.65.2'); - }); -}); From d644634da7e7a2fb7135b18a3ed8a5c54d08a4d6 Mon Sep 17 00:00:00 2001 From: Konstantin Shuplenkov Date: Tue, 21 Dec 2021 13:40:30 +0300 Subject: [PATCH 6/8] chore(drive): update abci --- .pnp.cjs | 8 +- ...hevo-abci-https-6a91ed2e9b-f333af58ac.zip} | Bin 149917 -> 149852 bytes .../lib/core/fetchProTxInfoFactory.js | 36 -------- packages/js-drive/lib/createDIContainer.js | 2 - packages/js-drive/package.json | 2 +- .../unit/core/fetchProTxInfoFactory.spec.js | 82 ------------------ yarn.lock | 10 +-- 7 files changed, 10 insertions(+), 130 deletions(-) rename .yarn/cache/{@dashevo-abci-npm-0.21.0-a57b183e5f-da404cd07c.zip => @dashevo-abci-https-6a91ed2e9b-f333af58ac.zip} (85%) delete mode 100644 packages/js-drive/lib/core/fetchProTxInfoFactory.js delete mode 100644 packages/js-drive/test/unit/core/fetchProTxInfoFactory.spec.js diff --git a/.pnp.cjs b/.pnp.cjs index 20a490bf267..7c47bf30fb6 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -2236,10 +2236,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }] ]], ["@dashevo/abci", [ - ["npm:0.21.0", { - "packageLocation": "./.yarn/cache/@dashevo-abci-npm-0.21.0-a57b183e5f-da404cd07c.zip/node_modules/@dashevo/abci/", + ["https://github.com/dashevo/js-abci.git#commit=aa3dfe8e13d9ced6cf71f6ab233bd1c7e33d044c", { + "packageLocation": "./.yarn/cache/@dashevo-abci-https-6a91ed2e9b-f333af58ac.zip/node_modules/@dashevo/abci/", "packageDependencies": [ - ["@dashevo/abci", "npm:0.21.0"], + ["@dashevo/abci", "https://github.com/dashevo/js-abci.git#commit=aa3dfe8e13d9ced6cf71f6ab233bd1c7e33d044c"], ["bl", "npm:1.2.3"], ["protobufjs", "https://github.com/jawid-h/protobuf.js.git#commit=8b91c72dca68fd6c418078fd2358c4969425dcdc"], ["protocol-buffers-encodings", "npm:1.1.1"] @@ -2576,7 +2576,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "packageLocation": "./packages/js-drive/", "packageDependencies": [ ["@dashevo/drive", "workspace:packages/js-drive"], - ["@dashevo/abci", "npm:0.21.0"], + ["@dashevo/abci", "https://github.com/dashevo/js-abci.git#commit=aa3dfe8e13d9ced6cf71f6ab233bd1c7e33d044c"], ["@dashevo/dapi-grpc", "workspace:packages/dapi-grpc"], ["@dashevo/dashcore-lib", "npm:0.19.28"], ["@dashevo/dashd-rpc", "npm:2.3.1"], diff --git a/.yarn/cache/@dashevo-abci-npm-0.21.0-a57b183e5f-da404cd07c.zip b/.yarn/cache/@dashevo-abci-https-6a91ed2e9b-f333af58ac.zip similarity index 85% rename from .yarn/cache/@dashevo-abci-npm-0.21.0-a57b183e5f-da404cd07c.zip rename to .yarn/cache/@dashevo-abci-https-6a91ed2e9b-f333af58ac.zip index 56747f413cc77e6c7c3aa3abebc66aa48daaef8d..43e5deebea318a9384c26fecb95d17a625be1057 100644 GIT binary patch delta 21457 zcmV)YK&-!=lL_3C34pW#lD!QtG0!V~0ssJp1pojilkp24m%Y6K8-LyeNJ~*wQQAun z+_+VZN=9sDKR3q*+j-m$&ASt<@(5@+Vko0&H=c|Ia!VYr6H6^1^;DfX#d6(>L$(F_3KoDNKIJoY>G~iP-`Em2}HwoY|us}7J86y$$#CD-rOL#!X zGyX(_k|rh?g!CdT!++2oA!NJR3!<&)I5|zG3PI3%o+_w5u;4%-_vzr=_C_QRS2N$p zSocyjiPF`(KxiZ(B8{NhHoXEuABnJ9Ta%oY#Jz*S(%TW|ZM5aHin(QtWn{Igf-8uV z6XNQa!%NoO)y^kAk4QX!x5M zc^^CLQ**aGMsfp6k#Cwi*X!#RC!rYj_PvZiNdYG0!QcMA8|Ug$RwE?LW>c#K+N{_a zJ0G*>PV0WVOSnZVW7pSLJN_se+Et?yyFK2@rwX52@Kjhc?$ew_SVapWP)uzbY1%Mk zT;aw|dNuIy>wnDxlL|TD0!v&@a-o&-KrxAy0rJc@UyyP&A{y!Ao=(viJM*Es;BAuy z+MFZt9G7wB|+Q&FUg|^BU_^-%3NJcGkzC2t2d15!M1? zM%Q{?dp?G2zMR*FcT%#g+47JrNK-C#^A*7tugr)3&r8{Q77{&hjaMP=Cvnzq>}Dw} zm3)|!^dO@3uVw^KTsxJe7@z;0c>bP9JUAO9vg{8W>4b)L8-shV2Fu4TMi;KCsw`vL z^!b(U0+$}Z0UaeV>f7KS@6pTAKTt~p1QY-Q00;m;uGR=wyzGnF0ssKm2mk;llkp24 zmwvzj4S$tQPa821hVS_mmU1DLl6d{K>)xOOT29BcPoKBS z!~8LY6*E4Y%^tf16Ldx}$);FLaWNTNqavWKuVWqB>|KOWVTFQQR<2qi z?NwE-noH=wOX#E_OK7cZiT`b~(O$NcU@se)X5y4=y*evZnKhuYt|};_t1ATF*FGc) zPJc^PW^6zy8yDw&l+HC6VM;JE&j=LO2m-Ru)KJ3<#q<#|doM9NNqn(eqAT~*A%_RI zMZdgDMkdbg(9$MYMTEtI4}|Tb1ksFITq?yF*2GxKnWcm%B{C(gCP%RJZLnkyOaBe~ z($NO81r8|Is9e{fnU&~4Z4x}ojbb1N!hiQsgi>2zmghxQT47}&wC08tGBaFROyLNg zyahg=;XOP#gy*MvyKm~=YTxnxZERt>?H0BuT?*_ObHhlQ04}n;x`CEEn$&%6R3yqydKZ)bg6_iYM=&v6n{?> zEp3@>&ne`R=U65ks4!XpEhTjQe(cKOz@0vT%%8)&u^|TzqgC6 zb9K?CUm$o)Z((}(Dz!;dp5}egRbApQ+y9*k9ZidXrx}@2d-js-CbN zsxcXeL=GR;ROG>f+DhHz%m(JnUX>}XTxg!~`{V{n$Err_x>_c**5l}Ovoz8@%|O-O zEnTJ7=*RxHl==_1xWfT@0}Wj~kXImd005Nu2LL6L@e3Z84nzV5w`0cvm@0oAv4yeV zRXGhkxwGfb+6H++oVEk{5&%(@(F2{PIf=23sKZcj=lBe4 z1$yFG8rZ~33Ooqt)3j+qKO}$jLml*k_)dBZbhy3{5C&0w07632Y(Ie^01QDLB}Y62 zc9zJHi0XSpR3tAxqX8piV96RAb*qYD;8AOu+!4}wu_scbKBpEWv0NWTP3(YsQG|5x zEQ6#@b{#53f^woNcDL*0Rlna?j{GP&{D}dp-~B})D}Dr5NMUNlcO~ zSW+(z(O$AgYHnx|@|m|b&@Heuk44WBCGuR~H-?CCigV9l`IIV1G(^SRQ!|P6nY_sc zYnd0TVtw3~W+(!Uo>=uQN(`+OO@`uD$7*aVdKJy;0EAH0cn}P$Z`OpdNEpjvRtV)p z>0#DEJ4HaAW#J4E(o%od1ryZ*s20*`as*pg$BE3gEc}p&w}2&UG{S|CHd#Y1Zt*b; z=i)Z6nO{O5h)JcRzLW-O+yrg3m()1VD`DL#-6b|hw+@&|9&Xm(7IJ6EG=~5hL|1?~ZV_Y5wzchzBE@gR%4!V>{d#Mh(_01Fh{HYI9 z&_f?N9|U`pJaK>Hd$giA=thxd2e8BvWQ!;pwZ76w8y8XLupWKm5q2w*|Q2n|*g zW2kxLEUvq1rA;mXMe$_EJu@cESb>S$o;08Y)1oIJIcg(QfJkg>vmGm{!Z8C7xlt4N z(18#8@js0FBm*7C;12$P!`=-zuz}9~6VPDmPkm>}2xNa4I!qjZVefx>aDgvJLjw!r z_>v>2P?s1<0fl0nTn}Ka_r=-jdW{~tYSY2PR$W?u5CuJ3r9V`K9|`Gd*ez~W#ntoq zZvS+-zWgB9BPF}NN)79xJ-^fmBAi;nbz?rO)YUcU38hvDt?EC5_a#HekOCVSt53@2 z{G>Fq=`epsedOwcAMoj{3xw^pNzNxeI@z6$@N};4dX__~e6+`P+IA#5q$6p~_h`;B z8Ig1hCz3U}a}%=4Mt$U_ZOnu&v{s4_U7f(onJ2nG$ zhMW$|z#Sse5gD?784kuM%fH~qVX$!`%R?~z)SrL$=u2mXOwhvV$Hi5-UMen1YnKbB z`#WI+SN$Mn`BkXzpe-@41awo}mmH0) z^fiBkcodr{VsWP`qWrBY=w`jXSr(63&$uWP%3gN2A(Xq`&zGggmyY-vy0=+QM+7He zAjBgY4%ExTb0Cd}7&%a1*N_~j6GCr+(p}MGL}eQEsy%abl8eV8Op>enKGH2XJF1ch zPLI|lO+IE-raGRb)|nSz3eVvtRD*;nTlNbSixA=jx=CQ)|w|6 zJ1Qemz*sD6_QYeQ>TJwptZU3f#&l$iK2Ur5NisBc$aeQfV|05nG^QGSVq+;=nPwhiTy$;B>+17&=tsNXOXxpOlVqo2V=UZ_Zcq&F*%+$FNhE4q>N0Ed+nM z=?g1YCU2GND<=hqf`PF~!;DGiS5W#Vy^#&e7~s;_HE9MuV-_l_VaM{<6w zxF)q*j+&hxE2l}Nlyj>&DRyf-Bc^{kKUP+kPAw1gYqYsgX1w)!D#d(#5GtiqADT+_ zUazKgNv?>4^9Fk=)ei}dKtry4tW>INM;a<6Yt55N9hDI&R4SG=Ssad{vN1!ct}zpo z(m^TuIPK>q$(Yn3o86yD(ap_;YbkT_grwLy5AF;Z5hC#mvrmH(v8U26K{dT}+h zdiyE3TtAd6$FRC7+t`--zvXPaLH?bpgzuL3yIZ0b>bwJaSzR|pmDg29vo7oMY#gor z14mDbr(_3Tvr!Pi+v-}`uqcVTVFqExzc4e>$We~#`*d`a`eFX%6ITZ7o`!rvd>F) z&1!jAZfY++5w+9jAILTP`YBqz>N1KTB3|}bw-PQaH+{Ewu`)5Yx57bBW~-bZT;HJl>J|u z)oz2_{*m)8Ikxhh_RIE4Bki2qykysWTHGvF%@uMq^{_t}$T^t+6h*)9bDK5IO1AM& z_E(M$n!*GQF6yQ^f|Ih$WG_8kUD+t8%RbaMlD2x%w}5{(FX4MYo73uhN&7dh-%KK_ z-R0YfwZ16dP};0ye|)~!?YkI-9Z@^N-wfoLf(#UOFZn+(>s%<`#!-boboADAMse_4 zA4L*8SjbOy<5yLLq3IZlq%q-d26AIT28#CA{2!RLFO+ZN#=;*ux^X(AIJmKoA_;CR z-m3Guk`BF5ViBme>c>qEXzR}P~<-%3@Y-USdGx~AG?my$&KYQS_e=rWz?4c zhV1f{WgcAAsQ%$VPOl0=G0a@~E3;+Pnr!@D{cA_ZAHrk~t{+Cj1m{=(=-)9#q-KKEKpXecU^#zH1 zsN3wD*=Kc?U(Y_PD{W2-us}rMj3i&%=d*AA-NIKLHCBfj#Zj3#9<0`x5Vhr_v?d%X ziPoyfnK()_#x>PCY}(Gc(nn3dski$=m_&>0!%!SG z5k|Bu z;iFLTQ$P}}3Y0gFR_O@8)+z|reY7f)=`_A7)4oXlYDJS9pT*d&m)|NtG>Mi~eicW} zmi)5TGtLry6b;ZfrfCM$IILD3X@A*cwae|pX3rk-TuBK$W`izgEdP6bjor*xCqjSM zGByeIW&Ggt6x16Tn{pPVWs=Av?2U_iF`3X{$zVYVAp^6A+84$>kfA}py)eLQk~*@&0Hh4<+Xr(#jY31 z2^Ej;NJ4*H0FLC;BgsGe-&UpPik#yZ^-5cDneFE#H7HkkPGwP&WvE<)58b>2Lg#mUW@8`-6vt%Kn-oU zLMH=H7Vvf(*+N&*uymo7LZ|c6O4#GzZWN`;$Gd;eZySLK?2WNUM~e|aXJ_Wx{=2X=*w2mst2Vle=+cj+mBvh)kh0kAMk%zuFIPO~I>c`6-x|XDY53=68>BCVOlExomS(I%p z6{U@tKy;0n5QH8<&_{o4KSxOhAr9H_At(gh;2eff=H-b)uyr5Y9WnwDNSBC3BIq8b zM-l2`Ff@xm$BH~*1X}_mr4hXyGQ|<~H)5Pdw8`khJOp%mbt3V|bZMR>(GbcI~a z_j9?F>NmI;@+k=rMRav4CnelKL|0J;rzKzv!P^==Fj1yy2m*g+Yxj@{-qu(tRhqsS z!l8!2AcS0dq=O9zf@sscBIca|MAr*^V&o42KeP==c*r3jd^jQ_S{!wVNgiaCjsCd9 z#2rWn9_+A#gO{W|^e{mO?Xd?-<{;0+6LOG_K4eTB;O-Fh_=7)hP?mmCjzEltHuST8 z`eP8;vxc1kHtc^iw4p3%qg;nj{-le5f3%Y_N{cFvm z-~Bf`UAc}o3)G4pau>dfzYX8%2IZaSu5RbI6W@96;&unK7vCk_jPFA4#*f30R{QZ? z^bPs$kapy|X-iJplJ8 z@5Z$`-wm@n-(|Z!uS@%*^nI4!pLZF+4SJtgTz&QX-+%ePWMSK%sbBT`;&#R5=BwZT z>xVDp0{aB(BHu4}Uw-wwKPV-3zk7rXfI4j9L0`W>4>U?O=#?_EugtnY5%{A?I_ zqSPOOp7ag}4)9&7^G3XHf+Ilb`=#r~TNYr%2&HREB8DPs&L5&2mobSLC6-mW?XjY^ zQG=AOH4{lOkQDmh?ddD&Sjr*WJ_t;q+nlE<$}E39;S>(1z+ECIo`Q6XXh4PTWd@?6 zE)Rnv6?DkRl&P@wKw7BMn_@Vs!a)J?QH-XisBdC4?jSvhQ9pQe_Uyam{H9!qN!!Ni z2O-C=g2+#wzdHHj&u7nm{_gpc?|**#@{ccH{rTnWo7r!_&}4h^^!)VYMVaeMU9OW? zFJ6C~Rk^;><$8Jcyv*`DjFFr#ZdS$Bi`~sdbyx&$otQ^#-}%}1FHfKS{NnukpD+IF z^(o#$Ipga$r|(`}oT`lkGGZ;2v-bIZXDag5>sRMbYQ=m-hH&%k{=C@#P|uqvh~qPX zFiyxodLRX*WZG-4o8qV0yEQJ@Urh!ki*Acv5_8Ey!Tv1C(#9L_`ol4E>`3bI`yN(Cv< zom3sQY4yM=g^f850cvm5+=_3}#I6u_(Jf63u8H%pC?)1ygtz^j%I)1 zJtQZhrRgQXsg{$|8Ne32z6=hxw1Y`zfD2G_YQ`n){ql?+DuvYX3Jg=wo?X?^iP`&y z%A@62p6W+xWKdN~{P$556*0o0fDKokF+9dV6-;>~RSPW|o1&pPRiY^_r#E!s_f%R1 z3cMivp%H}gw7Ivp$D)*(S5z6VePj0y&-=G+3Cx* z2jA>>HU-k!B-hv;9i&wEX!rhYJJM|bC8hZifFm*?i33EkCEM=F8XGeW zXxlOY0WA<9kI-IDk_-zRu+amM0kW|*y zR)7r=S>l3z#!pHNIvZpP4bFc{^%XcYXp;03><_CZsa7pgR7a$3nO?@W; zzp2LsHg4*>iU!@(w?aUhdO+dWUYyI~bGo2c@F1IQx%S|j?M~M~TASp$ck4uV+H7}z z=(tmp>K^T8yKP6B&9}mXvVk_Gv#KOx`?K`Pwf(i$b-9(^-S5!v_HFT(x1}?jT`8N_=4ZlVquqJ; zVsUf3SKxY7V;^F1yYH6)q&&l{Ym z(VJC(D^=X@Qpw>G75w%Taf-sYWZ0FQa?wgwrzWg)H7BhGVSd6Lk;_CY zcdq~VFsFQYKqfDpBXHShW%CmhdLufQhgPaGH)#$!iW#lS=cXn@1zb+!Sf&5oGw3x^ zvG0$XGJ%o6 zid7E^Vg-LuZ0v1>l3X?oj?}o5xo|TILEH&_=dH`&6YMHE&L9i~mLWI_5xob;v-^@bKGZxa*URi2(3v2O+W?uvXdhc#u9tsA;xU#^r^hnL`Z6?~(GMnBLK>6EJe%+57^$h!AV5?9zW=0LgI)e4(u^N}vf&wuC=JG_Yvbt!B9;+a)a4J# zDk=#eMA0b}jc58KTsHmVPqsVyMu&rJWF z#@v2cm|3T7GFzjm-k;I#80Z0!HpMiD((i@E)m!K-FgO}yx$|wN@}wKyW*d{|KKI>v zUts&(Dh=A_*1uQUR5rThZ{V2^;azbwM4*2zH^kY{x}fd)ex;nOtbZf~L)0a_hfim| z_c`2B7rd(vAu9#9Kf?vL2bc8)_oqzIb8~7AV*G|!{u%UOn3!s%<#Q|^L`=1jwbmwD zu9)f`Y@+m^2F}zH@Nfp{OGY?Tmp>?+sU$#^T;)OOOqm9q`{&03{V3pj?2e5CSV@0% zq9%%k9|zD!&q9p{k2RB9=yAb#PK|md2^bfQWuF-ynghlKt@6)l(LK<(pw;<#h^V1v zC>Vq>3RMSZjP<|(Bvik<;;UL>C&pM0l#Z~9uXVJHv1&vb#>lJ7moXlN*+`5rhW!{v z5_C0Y##l9EVvJhGNE?9NeIprTJXC*M2Vjge8*_|Nm{}*r$ku48_h-ZyfgTWvG144L z<tkTWH1zjs{sW#%3x{${3s7c&^8vs{2{+b5B(&eBh~02|)zgqP&=3TSg2j*cKgw z3${i7^?tp*zk`S%OCg`+s!J?q&SH1FUS2)b0z4#}V_oN3sE1_pIZCE@Zg+o%td>hW zJcjr&;nY}N%fQFQDj75;2t(xfe(1{N#C{ls%8C6k3YSy*B~}Z|VLTG2>z62{g|l1M zbxwk6NOr$7c}+(g=rEp8d4yAVb*BX5cdHcl<65=cjtB1Q!OHnK7GLv-kJwesNOiW3 zSDF8zx+5_k@fckUJMssi1bu&v``}#Fl!?39L>_gsW@TQ zofsT{98T>8S)GFedP1}Va5Tp3I|TFV3yb_r`)#l3$kIH|HBz73fo{SACIvX=T>>4u>L@hz257vHul8hoAvfTr4M7q5> zlBmqc6H8?4J-9byL=%x75s4?#9ZXLY)m30SpBS!{5C{ux_7 z4@4M+^xNhi3JI)!g?G8lAEH}Nq4diJ(<)e{`V}s{0lPM=2~~ed!G=k{nhec2w4+FtoI_kPBxN0fNiyXf&#Op+ z`nx(tJ6H~gb#SRWzPUiiOP7Y6H{x=jTVSrX{xY8PVvkn_0U=Th62oRi(aJ~Q}vsBkj zHP;3wF2eO0$L+H!!DyKXYf2g-qN>i9ha8V{(A$_f2y4znL9`UaW(fE7 zmShCvu&p0}f7n=^qaVO*J+TkAD#Ut4M&twS8Iib$jic$Q2eeWQ%{(;YMwY~bUl=6i z9h@;Tr5%5@a4!n{X|z5A#5!1FjFU;M^cdGDNwS|W{)Syu{LBQRCqCaNi!7yAHN0Q1m&JT#gNa~2YbX&DhKEEt$1^e_R^6b8s279afRRoB zDV-?F-A6!YGTiU|x59ihOzut*LWtyUg;mj{T8V!wbsSW9Dc{$#wtLxvHm(~D!r9dN zgJU+PKnUeYt(u?AP^K`t`L9Ac8d4;Gs?i9uDD(G@_xdqCvq>D}+wvox${@yuX)uz5JM~$rMem7J@t7O04&zJAk`%<~D z*T1Nh$?4Rn8?ln^>7>`SasOC6ppT#RIk3HTmrkp>d4K8xxa^yy5N@Rg$_(-volx%W zeAl;b2gD|^M907+v@ipic5y1fK%E!=S?|{h*QfqE2#iB*Zk_gC7*r7G2=%M!r0T1*W1OqTqizY zz_d$us^Q`jMEv>$_t)HD<4J!=4(4%QFfJ1kXHb0Ydngu*;F&1va{^(C@0kc+m(Q|} z#tAirHyM1MA>({;SZ0&}7-{EuZ_41wiM(mT;Si$4cqlj+)wiX^^SW>OpB*)mapfnP2YgrQg{DsR!aLkQ|(dc90C{6G(w z!BRwc!%Vb2_&9=)#r+X1u}O}PnTh?z`Qa;2mJvT`CbTEjt7f1Q{U0_vS`!nWH;Za( z{WS`eT1j=n+W6IVIcr+8!j= z*8Ggr)g+2FQ4o^O4;q9di^n4*vwIj3k}SZW2uTu#Vy*W}%?;3z+#ahguKOF01xD!x zXsy=W!49A)Za)Y>!=wlf&}7dC@<>@+d5hd6Cx9l88jf@Yk!7?1O=?dXfQBg17oZ)n ziAex0vaJ~d!8Ct3W`HKOX#!|kfMz}N2XUTc0PP5zApk(Lb^;EdVRQZj(Ac6=+fyqJMV`Y9nhe|Aebt!MZxGT=q`}qr{OqSvi%!DbFdr5x|AW(W8(_WLyfjn`75bRp} z1V%=>bmEg{g12bOEK}`DWL|;Zgw*HojCK;dW4I^MVnZ65zbGg`R&*N z8OHrwT1prLC@;v9X~$(!`m<@V;R~d>(LbFwK^N-dY4*Uy(P7d)pf-UP(-UeAkOi8% zC%_^-qn3X;T)}%s%%{}CKoxN9809&&F~JmJm5}~PwX7k^Y9_`cU?4@z1+f$iS&!JH3HNAm zkL`FL#C4KUpTl>B0N}^g1sMCmd|=`~Y>}z&CmAsyZC{Cmfoxq*OM*~u3{Ha#!%CJw zNM3(YBxOR9VRFCVS&xOJY}(77+bqqME*G_Ei7x{V3muNw#CF#M$Br+-GHuU<7|? z4B|65?@WcpxPb=2ab5SIkR0QM418-6AqGf+-hkmaO-q9dF>TE44nggW8Z7WFny`W% zE7*V*3>X1kUj~N=+QB4G zgdnaT(gFm*Dw*Pg>h0O$N>2~?JP?085Yp>=L|-7sKZHc;Sr)K(;JOel$pcQH3TfDc zAPkQt3`#PR38CIW!2~Y}*&fAs&$We1XSNnw5k*V%nE&TS4888Fugunpl5_mUY+- z?EXHJjC35j4+H=mcDCm@2RM^Yn1ihy@!pXU<Wq2u5e|N^(r2ofXhi z8*RFoQ}Fh|O6$@)2eu^=wMhm65q!O#5-?_IYKEnG}@mm;eC+2&@KRci%`x1P;{JULb$K%EtVq4gAzP z?fki$8Wiu(XveSifZ!W_D~B>%#UL!-LhtOM(IB%8J*UgWJNZiUxxfBveD_!T;;-SY zU&?lzl{GKbr5=p%2s(fpDLrZnKWFRpknPah^j;9XzReHUnm6l$^)yE)JR=A=YYqDf zYs|enGaH!RCwo8B>AHWLXAbWB6z2i#?EAJPeq%MO`oO9J?yo|$MCsG-@3d6>z8p%^uu7^J8M$}}zm*pAZZMxE8nH=1k} zbCKIo{r0VK&;g6v%r8GwzA2UXsQvwsW3gAorb2P6%LjkCWR?Ow)IpQEHrq3_3OSvH zp;%V*OELd|S0ko6SJHO8>QM~kZCI8BKL(1yD^kTpDK1oN_d8hSX_@F(?XK8-+6xaZ)b#&Q{lJ z^@tRo3afuw$YNLb!)ibOq|{me>U1c}IHE#?wUGs9tk_^{Nm(0V^5LwtJXA|n^99CQ zt2F#*mTnF~kB>FUmW8r;0xOs0(7h!@nn5)06$$yeZ8EtcagM;M5p!8r!c7zCGtVpw z;7SCDuAM=!TDx?hN^l;rn?cY6hD|b1DK?MT&LDqid%Fx^cZKE=yBP$lH~vh5ALfid zqo8%ysjkOJ!C51NFF&FralidElZI)_SLX`zp+Fd~JU$FGvu}`31A<70@fE0U(h%Q) z1YHLG5_FUXxBnUx#qt=<7ZrIpUxey*P2Ys*--L)0L@+-Z=)2It`bFq3LqvZ-zYU42 zgz10l5cYKl=s4MaAA&khiZ4V&U$pr~BrRg&ekFnjo&3KOwJVVUzZ5lQ%Jf?i@jDUW z8&SaTLvm5|)S^bymOq9d<-_oKMP0?NocN&6AS}i6+sgpoT~OV=nXlB3ePSw9z3i@M zr^26DNU~lP>+5nQs9XitCxFd=)ug(%-{F4?OuL;hp)f^=sV{rv4T1X`6`3?UU z77gxddI^jMd>Zr(mhxrB{8dKxMJB-4n4G@E5Wd0$`~m}x(s<;2ndn2KhhgCOD2*zu z&*AueV}NfP`cc}b;5wIDy>YCz8Ku!L4qXPI`S4gxAEjNsa)cPIv3i9}1bs_mw&j0l zZIa?8H#*p#cFE(5N2;$K<}V$AzH)^4!Vw5|AJ~_TLB49l`J!>e2Bv(;NcI)u5Wiqd zUoR}aUU+=D7{gbK>5B#7i-phEis?&*d9gW`uM|Ukp$PnSBH5RTF@BX8_KQS@UnAI^ z9fXey%JUzmdPKo}x)5{9g?AN5xk`VtZufGbbrgA(z+EQ_jYmkC#p((wvHV#rkI(QA zRUP7sAqHJE7*rC5r=TPaDZfE5RFVYx8;1=$;C5DA)BvK0=Bq9L7!rgH1>1eM+9SJ! zuEa7~Umj=_fwRbFcfu@wmSckoA`Hpe_SD5>cJp6|e?T_@yqT9;9|Fxt{f&RNkq4(# zSUBol-f=%q*wd4|!31v%p5V2^599`ckEJ6_KT4walehJR)TS-uda5DIS!&74T;4#1u$3vqpS zi^HJoIFxOdGbuJ0l5+N3!+U==*jn6^R1sECy1AANtjN%cWG!oW^;ljVg=ATJ-oP;} z$%Zh}qGBz*qNsQa!8LNw?Hg^h2gk02j2OBSGX)`6Vv&)+l|c;+wh|@bsTD;xBxogy zkt?$jq@>v`2(eOJAu_O%3_d8Yf@MjCt3->N|3Jv^ODV*rhhSA=H}iiW5};^@N~(m$ zrUg}?o&$5L$tk=jRTA%DK_{@14+T*Pt(ypy5213LiUN3Aay)f(UK5^9IeH5h2nDZJ zCNyPUa%(zThG5Fn+0t<-N1xRPP$7%%7*NVQY>kacNo%1&kd$b^e25hBFgSE94!=2A z%?n>04CM2=I~XdjUYLI)vr@uO;L-3p z#nq|)QmzdkU3b4b%j|NqZ&{9XPLXor+Bisbm*E0pinrmC1`vP3b+~{$!`_Fh3Pc~t zd~U==M@F>7Iu${-`jHVUvQC69Wx>Wrn_&v8bPt|?M#o3nkx^IeO9!KLBcS$Vb(wBk zE~mwSIzrms?FtQo16}T_5heWXuD}pT z>!9uzfCBAvyWoEn7&V+P{KSPC?-j3Vn^N8JifhyRlGn-T#A{yTTc5w5Q=`gpU-YUr zY&t8gzvA%MWIQu`R z&*~EXRhOeK<9{MKp1ykZyiEQBl6-#g=IrJ7WrqKSWcdEgtGBN!CH)r3@Z$9R{K@yV z9x+8e{jYzE)AK6BR~k*#t@skt<>||}FRFA*mFG+oU;bX3r`G3}m_pBcN&crMM^nZh zOcBI2Gt_uzldihw^CwSFpa1;o^~Kq%mrtI<9@^x2bNZ*ZXKzlQ!Cu zr*Cbm8FV`#_OT7h3-w$upU1~Gusnxv4Rty8-x_}iN@_kffO7I58;A-tery2c8S-NT zBoNt>9v>TYp5ek;ArG-*t*{Ra!dkVWgl4Uv2dMd=Y_1>dI@szqSp@W@WVH2Ie8Zo%y7>fugMfC~ zFyVhz?!EwX_sNL261q?v-sg(s?~unRmHgAC6iU!t1|&1h5Iu}1~alH zNSGnf5+D!NgdSiC^+T+@k4+?_g*V#yp$mV_0{qbqW`Qm@2+Pt*5*lSe9;apla=B6t zsBtbF3+lR#L_;IeSSc3Jq%;sqqf1YQH5r^luo76Fztm8p$uaXQpj{Kb(%~zo86un` zC4*N-;u!wy%E=*w%P%zxg^678H(i>#PDbF$+U1+IW$%dv@lyN6VXa#>R zN0Krt$V@pSD`*3)<5p093|@hGJqZ?Err+%VXt|D{sxHfwRv8V#kQFGi%&mw9Bf%?B zwxJ0YB$S=^_^~XFM>?O%HhH8gvVuI*o?F3wHyFIqiW7rifgT4>Mo?BC*a5cH6=;Fo z*#mOugc~#3g4&giZ|Sre0CG)EV2OXbQh0-eDzrKsJNSYcH_{c@XF0{c}`4t6Yl z5(b93`G-uzw9AbNi%D3_+?_IGV}vdh50JTeD1nmc*T^wY8EI(Bm6yTmndHz6v|66z zY<6C3KQ1n**bL4MK{F($2QouSyesy%Yvs+C=2s9Nyr`4tVP8nS4nVCf$>Dz=upV!pDhr+$BQX^Yk)B?I+P zc%j*08(fHZ3`Yy8-fV=0ut-C)LQo>QAw5W;p`&7@_8F{;X5A6Rfqf49!reh*zz&O5 zFJ=3B)^;b?4vWwfl z*r+tTRI5)8LcmP9NdS#9om4YpsNU4sLI!+q_y(t+dJsR)u$E1#4!BJBE z3Sxq!YbgA*YR>8?_arxox{`wD4T~Zd? zTB@FPxAy$ZFrJ`7}|} zybzCX);H_ApvUb$zc2P>QM)J0r7X=OUFJu&hEofx;`4tSGtnv+@&~BD87qq|t#&TA zPu=Cp;{AHp%ms#urK~=+R#U&Bwl<5OUYqV6(TX+~$cyk@tFRnkIEF zoA2mBsEx}Oz-QkSH;ZzuHHUUlkM!BSX5EHVR!qt??+Q6X>{d!AyqsU&7R2oemp#ZB z@u7I24-Z}DM~*>r7{_cv<#hY+Giko zfC6CApg}pKwFj8%0J2hRQ2;Dfhmw(61LLq*(JTkGOp(Fjs^@c&Jyb-nsF#Y_sBR-7 zSX6Q`8?kTzzhV^tS%HxpaO*PbczQy2MZHIRGzOR^}>1Gm3_ zY^Rmoxrk0^)}G=x*#Z&m7#T%x;+-Q-%%+vE=_574MPumrOx@s-V^pTFLP#2m$(w&8 zg9yx3A1Gu9s^me-tODW6wzB$B%=W7&bosyY*Qck?X5Y-d{6a}OdG+k{=kv4wds-%c zr6xZ=yEy&%&D)n3XD_N8zY{Y2gMReBb_hd!shA5sY%k83Ld>(uYarfH4iEEIXEUZ!V#7SE>Vm!~fnlCi z?Finn3*#1TycZ!P%(J4x}eh{0f<6}Dksgh(*Yindr-M560E{o_d--@e<4`b+;w>pK8CX48&AQiu*T2sm#W>Qt0cQl;a*MJ!% z>I9=Rx~L&SFh($XNk$h$iQc34GQk+VlR>WDTL`YJ_uevk^cvA?h@Q{Acm3A4?)m3k z=Uw~V|Lt|&y`Qrls`WI-jjXPS4kaN2{Fhg7^*|l+rxydYO`>HHWImhFu6lBXloIxp z;P~EIvThf^Q~c%z`cJffXxtlyPh)Ugm>{MGVefx7HJ9OP_NtYUAQq&%4s>J>x=qx2 zqh;sfuy1s3n|xnX-K%n(nozeAtDT8a>xM@u%Uslybj^E2vz^`M33_J-3#0(T2oT9B zn6Mn-2V34FFWL2&J;8;6HVm&>eW!PH=5Vg5ULzmna3$} zUPMcuyv}I5+j9=AK``vdiNm}6vw%zFvPQ?2Kd`z+;j6b&VSwW|5^I?p<;{PHfH`M4 z;a)*DO|66`_NexH6=>e3uW52af^vXn@;kJnvn}r<^9`!k{Eq{aKxM~4Ab2xQgH zTA6g|VcHBM!G`_Z!}OI;{e>$~lxgIrKclm46 zCG1{nM0%iCx7~n7WOelUAC!!!iqkjQ79J%v!)F6cjkn$dZl$God7k)HyaGqwH|OBZ z>i7<$c;#8Z&hB#|r;o$MpW9O$((jCJgxW^ewTF!xgL$JdeU2W!Eh1to2eG2|XO}Z) zxjxTywXIJo5rrl)X}hJuS5tKhrUx{B-VU`so_RGf<^E@OP6D6ac(w<8bDlYuDJqZs ze#*e23U!QrjXesCPYC!hXxpEc*emBPkddq*Nn2^GK?YZJKL`4YWDuIyX|@u`suLXU z{dzI7$38zq@bcWCcsCx-%g6pRZ8wg^qgE=fBBt8D3?Wj}vj#6#$IvIsVUF2;Gf1@>Rj=J59TJoc)oFOZlkwf}l!`-7Z5)vG}` z%kyf|@563iU`z4~+v~o3=@mQSqMPPdU7)P+0|0BB&wQGR_y*y~+9Vo$pTIQ(sgd{| z>6M`0kC1e_9rYLk87iWrw@c~dP9=8+1!*Xl&o}v#<_y&DX45n1ZQ;J6I~^xu(PB1& z?b#2V(NmAMGg7zH+N<(x)9lQwsc`AS{LCm1QYK8qthL7Xo3Cftrfq&_>x1t4JANrcCtTt?9L7>FjQIpE3^FP-X=&~P4BBF+ii?3Z|-}z_Eyl_z``y<>RMa{65 zrE!$t5>JC0;{RmQO2$i`omAFKhFmro+^J(Pk-Z;yuE*5@LRWu9bgWF)B`SDzEGwkJu2eJ!&AiMXD{bLs~2OWLctH8Gi z(cLB`MGvJ(g>?)V8b3J!?ja1_03SR{VqYeoB~dwvdzUCF=lP~~Hxi8VeziuXE?7NF z5WQ!b#ZR*v9l;+)5#EEQ;}u91bg#D6G9g&nL+GjFN!2*D-4}8Who=YF3r0*+WA8y( z@p7UdJZs?@ysv*Lc=$333U~OshE5}V27Stkj=m|D^u3Yb(B9a5HWwBvORH=F@xazU zzTBh%ThnGgXF-H8lLGEp2xZ-+mouj<1*WO!PGVhxg96&!d*nV4SSSQNoD^izGTvDo zM+Bs*oq^;}hZ7l;-7QdoM*dG`iY)^5*c;osIoJaq9b(<*d>uNL{1N)(knjxQn)djB zo=#J$#lI&wKyTUlUBK#)kjVOEEHi{c$}ByB^7v$HApNMdJRk(?>N0?K2Xu;*cbDx9 zJ%zw1A}6MnqTvYz`|p+5NEIz(l*o+mU*c~h*~YVUV+O)$P?kdpIenXz=@|NPc6>R} z99p4<*j3Q6PN&TY3`_Qtkq{ydlPg(&pYY5AUn0PNLKCbUjg1)_r%xyHH8*@a-w-On z2t8b5cp=0LI{Weo3vi{bb?v@=Plk^@cNIJm+_8fQfl-ijHLF?6D^FUjo3TP4b5yhX zyr#z?vkOl%;z?{6Q;v(m-Lv%b-0=N!*U%u+$tFaDj4EKvl}XfbR7BV;cw{&b#BxBt z+QeLoOwg5Hu+GJB6Lm)AHpI+9p65KTp&{wABx98Y(G7y87|ro#E1Re2X{gyfnfqT$ z@DJ!iV!maFP~y+zaD_q)H?%I*jQDDYoypE)=~^RRXLLjZTfW8pWM$-{bB6>d9dilP z13Q*7kt0VSM>9S(V+7Y=M4c^Em2u2`&-)shQe9|m+hb0v>4wXHD$B-WlDMD5W8|y3 z?>t)+E`)qVW=JlKWaBe0g#=R>IV{Olwrs9LEB$mSvh2Q>GhL+W!6|C*x>`-_kE8fa z=&AOlF>?6{Bx@^l-!rlNG9uKx5gAF&Q48$(0adO+C<-zQM2mWiTdc}?7k7^I(Idt9 ziR!u^3hahy2o4IZ@h;W6<3c(gWqS0-T)LJ%` z0#sSADr!BGV}XA85c$bAM^$uhDUTjKrsrQx z4QVYaGKjpryPB{0tm`T~JIz^qGv)cGfN>>O`C3u~y(5wHPZH`ePTjED#)YzwtI^SV zE&N3+q3M(Tw9+FHGSZ)Fz{2sSRPqv77fIwamhS)pZsO$?6QpZr?}QTScB<=;PpI*< zsM@d%mU_6*27=b?`2?5g4*k$$5aZ8rNwM0wu()9Fv+yv+*j)Mb^^q2nudOj;+$r7Q{Bn6#S)f2y z*fr1IZZ{!U2Swnqygi`92lQ?qZlSk>XV4HIlNjFS~sX%Ou5Z?%c}IzZNa(qBUZg-Bk`Y?PG7OHet7|Z*j#ubT15#cE$h5x z2`g1RxF}0tE707y>4pMmZk}O!^ZX;dm69VQMTQ98fb9{ohVk#8wI*mTaG4yiNNBZ# zb(^Vid@H5U>EbPGC!z9?az{e-;amQ@F&8e%nkkYXu9P78!BaT59c$tRoO@ir0Iwl! zJS2uc%i=}{sPEy6sQ&g*Do|L^Mkbo|-`$r_ejS?jWcsKdJ73uj8JrLV(`>5ANVYYGaa8GSxs`B)C|T+dpv1sGLG+ zyPqi%Fiof;WIRD!fa3op;PHH|_25up=t%8mUnR%%*q+$bk>KQ-Vy@C$GK z^!Z8=H#aA{Jm2)IGhN77@VEv)UR1c&cM1QA-@(QjDQ%IY_}X|sd}#|0`nI`sV_0zt zDD84+c`_m6eBsxO_Lcbg^L=XAiVPf6MsjeMZAkP211X-b1a3x1$BjODQ^wW%j1)7n>UM#*4y z?gvauRA;WHB^N%(Ju@_Aw~l-Zxy)fo{+rW=eje$573cbAG+j1cs4pT65g@p8&xqd&e7r%I*(jQLr19^oqBKfWYU2CyITXu1~&v#{f|Hf05 z4|r;rZCAyi5OwJydtB0QE2eb}dgK3OP2KRP>@%GNZf=aipW_;V0&@I``*}^Wdn5ya z%hl1ls(i`kR_PXTK5rOBb_vKhv=$MG%V3@>1Fw}F%FokgSTwUYFWEG8z5qEWRUB^W zPL#{yLglIl>1Y0{ zDCnAE&P6!uf>qOCtj_ z92ki~D`E4`s=Q*fG|QMBXth=l2_kDsQOlB%!-;fB6xNiZdHmw?y7Ot>QP(rC*5oCa z%!Mrd{Zwm?b3^M}r01%u#c2cz#zwT$$lhMFg4yzqSt_)92#ITsg#yLNs#!?D_{hgJ z5Y%s%y$=Ou(j#Pa$Qzl0)ww%8QqnaU7#iprBK4Hy$#yjHYr+mu(S#gCRk96khA0C$ z>_KFL%mK-kf7b|*5qV1~*-;@v0M;mbO!~;rztqk+X^*KEy%a|Bb8NpMJB5##k7j#5 z5ubR#@T>r!WF=1oim-ZJKd!AKYR!guVGRICu*fhM-z{>`0mUtAZzi#t8X>efBfTKv zcYBfz!j?!t0+2i$7sGL<_82hr-P9KNAr1sF#}Yl~D#U7~LWN?D{{Foo_M7={iy9iN z44_9RKmmuO?vmY~v2n1l9;|9uuzy@qCXxUObh9LY8PO3l8M0a6()lXuD~!74`63m5Z5q-lEk1N#bCw zlx;Xa52!o$o5cz*;oUg#t9F`MD+?~}Fvk&=N8c5h{@6cE%4vG8e|~g?XL>4mTtp?O zWk<*L?JDs}oVsCD<_B{4aP@JHZ83Z@^)sUExRq(I)&xwz>#XYA@Y~D7PgGpup3!XW zvr`0%=*VyNG3i%hkcLzO=IyyMy~3oD*jQyv;;#;3iSi6)2lVD{=^m6<+wvtUNPFQ+ zC!dtGgC2{PXQr0aFnwv2g+ABONwaU zS~^vkyYA8}cX0squl{b(EZFNtMp=ElM6H96cmA(GXDw1)s-cJx-4pHbH@N3)jm$k* zYlekVPno;@Lt6#J+DyX}9{>4Kw8v|F_iAzYCZ77+4qb^>EVVg@6Ww||Dtq}cT+|a4 zsS^~m8hB0%^vW=kpW{5bC)QBI2K+T64FEjC`$u$%UQz^TfdAthe5jfKt%6|14(#OM z$BgI)2O$Z%M+rcN?(4=TBmMsX%kF;znw0=_jQSrClrLmw*vG;xHX05rBF z0F17H0*KL$$^c%R!hc^qLANUdxN)}r-Ep9SDgb)$f2#OD356v8N%T(~(cfiFi4Io* Nkl`mvKe!XI{trpE2-yGt delta 21511 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 SN6_=XzVeiVYL~=Mvi}3G30|H6 diff --git a/packages/js-drive/lib/core/fetchProTxInfoFactory.js b/packages/js-drive/lib/core/fetchProTxInfoFactory.js deleted file mode 100644 index ce200e2e2aa..00000000000 --- a/packages/js-drive/lib/core/fetchProTxInfoFactory.js +++ /dev/null @@ -1,36 +0,0 @@ -/** - * @param {RpcClient} - * @return {fetchProTxInfo} - */ -function fetchProTxInfoFactory(coreRpcClient) { - /** - * @typedef fetchProTxInfo - * - * @param {string} proTxHash - * - * @returns {Promise<{{ state: { service: string } }}>} - */ - async function fetchProTxInfo(proTxHash) { - try { - const { - result, - } = await coreRpcClient.protx( - 'info', - proTxHash, - ); - - return result; - } catch (e) { - // RPC_INVALID_PARAMETER: protx not found - if (e.code === -8) { - throw new Error(`Protx with hash ${proTxHash} was not found`); - } - - throw e; - } - } - - return fetchProTxInfo; -} - -module.exports = fetchProTxInfoFactory; diff --git a/packages/js-drive/lib/createDIContainer.js b/packages/js-drive/lib/createDIContainer.js index 865d8b938cd..1ab1845acad 100644 --- a/packages/js-drive/lib/createDIContainer.js +++ b/packages/js-drive/lib/createDIContainer.js @@ -129,7 +129,6 @@ const fetchQuorumMembersFactory = require('./core/fetchQuorumMembersFactory'); const getRandomQuorum = require('./core/getRandomQuorum'); const createQueryResponseFactory = require('./abci/handlers/query/response/createQueryResponseFactory'); const BlockExecutionContextRepository = require('./blockExecution/BlockExecutionContextRepository'); -const fetchProTxInfoFactory = require('./core/fetchProTxInfoFactory'); /** * @@ -308,7 +307,6 @@ function createDIContainer(options) { decodeChainLock: asValue(decodeChainLock), fetchQuorumMembers: asFunction(fetchQuorumMembersFactory), getRandomQuorum: asValue(getRandomQuorum), - fetchProTxInfo: asFunction(fetchProTxInfoFactory), }); /** diff --git a/packages/js-drive/package.json b/packages/js-drive/package.json index a4ef3f7f9b8..f2ba5cc69f4 100644 --- a/packages/js-drive/package.json +++ b/packages/js-drive/package.json @@ -63,7 +63,7 @@ "sinon-chai": "^3.7.0" }, "dependencies": { - "@dashevo/abci": "~0.21.0", + "@dashevo/abci": "github:dashevo/js-abci#v0.22-dev", "@dashevo/dapi-grpc": "workspace:~", "@dashevo/dashcore-lib": "~0.19.28", "@dashevo/dashd-rpc": "^2.3.1", diff --git a/packages/js-drive/test/unit/core/fetchProTxInfoFactory.spec.js b/packages/js-drive/test/unit/core/fetchProTxInfoFactory.spec.js deleted file mode 100644 index bc20ac2bfe7..00000000000 --- a/packages/js-drive/test/unit/core/fetchProTxInfoFactory.spec.js +++ /dev/null @@ -1,82 +0,0 @@ -const fetchProTxInfoFactory = require('../../../lib/core/fetchProTxInfoFactory'); - -describe('fetchProTxInfoFactory', () => { - let fetchProTxInfo; - let rpcClient; - let proTxInfo; - let proTxHash; - - beforeEach(function beforeEach() { - proTxHash = '542b5ba206d2b30a366b6f6d0cf5e877816d7a252f984a6d920134091b9b11d0'; - proTxInfo = { - proTxHash: '542b5ba206d2b30a366b6f6d0cf5e877816d7a252f984a6d920134091b9b11d0', - collateralHash: 'e6a9a31b5cf64f457ff9c15fb076f7e9a758683a516c0aeb39ab4ce40c5496cf', - collateralIndex: 0, - collateralAddress: 'ySV123ZBEv8FsRC6NnkosB6vZodWTpgaBL', - operatorReward: 0, - state: { - service: '192.168.65.2:20201', - registeredHeight: 629, - lastPaidHeight: 1106, - PoSePenalty: 0, - PoSeRevivedHeight: -1, - PoSeBanHeight: -1, - revocationReason: 0, - ownerAddress: 'yUVs18ALrCc5CSLpvC9UeJ7tHjtdt5eW6a', - votingAddress: 'yUVs18ALrCc5CSLpvC9UeJ7tHjtdt5eW6a', - payoutAddress: 'yZVHb8YQtCkw5QYiHy3uRTQy9VCKbEg4Xe', - pubKeyOperator: '120899de98537efc4d628c258a51f7f4f550360b1f93bc055c2b4238eb48be02ff00308a5ce3641e6410df9cafb89d7f', - }, - confirmations: 481, - metaInfo: { - lastDSQ: 0, - mixingTxCount: 0, - lastOutboundAttempt: 1639781982, - lastOutboundAttemptElapsed: -150, - lastOutboundSuccess: 0, - lastOutboundSuccessElapsed: 1639781832, - }, - }; - - rpcClient = { - protx: this.sinon.stub().resolves({ result: proTxInfo }), - }; - - fetchProTxInfo = fetchProTxInfoFactory(rpcClient); - }); - - it('should fetch proTxInfo', async () => { - const result = await fetchProTxInfo(proTxHash); - - expect(result).to.deep.equal(proTxInfo); - }); - - it('should throw custom error on code -8', async () => { - const error = new Error('Not found'); - error.code = -8; - - rpcClient.protx.throws(error); - - try { - await fetchProTxInfo(proTxHash); - - expect.fail('should throw an error'); - } catch (e) { - expect(e.message).to.equal(`Protx with hash ${proTxHash} was not found`); - } - }); - - it('should throw unknown error', async () => { - const error = new Error('Not found'); - - rpcClient.protx.throws(error); - - try { - await fetchProTxInfo(proTxHash); - - expect.fail('should throw an error'); - } catch (e) { - expect(e).to.equal(error); - } - }); -}); diff --git a/yarn.lock b/yarn.lock index 64219ef4450..6632b4396d8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1264,14 +1264,14 @@ __metadata: languageName: node linkType: hard -"@dashevo/abci@npm:~0.21.0": - version: 0.21.0 - resolution: "@dashevo/abci@npm:0.21.0" +"@dashevo/abci@github:dashevo/js-abci#v0.22-dev": + version: 0.20.0 + resolution: "@dashevo/abci@https://github.com/dashevo/js-abci.git#commit=aa3dfe8e13d9ced6cf71f6ab233bd1c7e33d044c" dependencies: bl: ^1.2.3 protobufjs: "github:jawid-h/protobuf.js#fix/buffer-conversion" protocol-buffers-encodings: ^1.1.0 - checksum: da404cd07c70018676bf45de091a9cda63ac37e85d76ebf9935b33635018ac941574c5bee5182f6d3a52d17879a6d33979f143ea327e9b7eaadf3efbd35bc935 + checksum: f333af58acc2129c0199db8b1174d84ae770030a66a3d9dae23130faf3c0aa159f814ae0c91a465548ccac9dd41e469ea8e36b02e292bc4cf2e78abadbea6e52 languageName: node linkType: hard @@ -1588,7 +1588,7 @@ __metadata: version: 0.0.0-use.local resolution: "@dashevo/drive@workspace:packages/js-drive" dependencies: - "@dashevo/abci": ~0.21.0 + "@dashevo/abci": "github:dashevo/js-abci#v0.22-dev" "@dashevo/dapi-grpc": "workspace:~" "@dashevo/dashcore-lib": ~0.19.28 "@dashevo/dashd-rpc": ^2.3.1 From f1d7d5acb15204a5da068c47146f5af2fcf83cf2 Mon Sep 17 00:00:00 2001 From: Konstantin Shuplenkov Date: Tue, 21 Dec 2021 15:33:11 +0300 Subject: [PATCH 7/8] chore(drive): fix PR comments --- packages/js-drive/lib/validator/Validator.js | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/packages/js-drive/lib/validator/Validator.js b/packages/js-drive/lib/validator/Validator.js index 579d4eb93b5..daefdb63af4 100644 --- a/packages/js-drive/lib/validator/Validator.js +++ b/packages/js-drive/lib/validator/Validator.js @@ -3,12 +3,13 @@ const PublicKeyShareIsNotPresentError = require('./errors/PublicKeyShareIsNotPre class Validator { /** * @param {Buffer} proTxHash + * @param {ValidatorNetworkInfo} networkInfo * @param {Buffer} [pubKeyShare] */ - constructor(proTxHash, pubKeyShare = undefined) { + constructor(proTxHash, networkInfo, pubKeyShare = undefined) { this.proTxHash = proTxHash; + this.networkInfo = networkInfo; this.pubKeyShare = pubKeyShare; - this.networkInfo = null; } /** @@ -46,15 +47,6 @@ class Validator { return this.networkInfo; } - /** - * Set network info - * - * @param {ValidatorNetworkInfo} networkInfo - */ - setNetworkInfo(networkInfo) { - this.networkInfo = networkInfo; - } - /** * @param {Object} member * @param {ValidatorNetworkInfo} networkInfo @@ -71,11 +63,7 @@ class Validator { throw new PublicKeyShareIsNotPresentError(member); } - const validator = new Validator(proTxHash, pubKeyShare); - - validator.setNetworkInfo(networkInfo); - - return validator; + return new Validator(proTxHash, networkInfo, pubKeyShare); } } From ce8771144c2aa6a9910a50437bfec2bdd14170b7 Mon Sep 17 00:00:00 2001 From: Konstantin Shuplenkov Date: Tue, 21 Dec 2021 15:59:38 +0300 Subject: [PATCH 8/8] chore: fix PR comments --- packages/dashmate/configs/system/base.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dashmate/configs/system/base.js b/packages/dashmate/configs/system/base.js index d54a972f54a..e5bad839f02 100644 --- a/packages/dashmate/configs/system/base.js +++ b/packages/dashmate/configs/system/base.js @@ -105,7 +105,7 @@ module.exports = { }, tenderdash: { docker: { - image: 'dashpay/tenderdash:0.6.0', + image: 'dashpay/tenderdash:0.7.0-dev.4', }, p2p: { port: 26656,