From 2954e29d26989c3d806f9cbddbfacf30f4c5d571 Mon Sep 17 00:00:00 2001 From: pshenmic Date: Wed, 18 Sep 2024 02:44:52 +0700 Subject: [PATCH] feat(dapi): implement getIdentityBalance (#2105) Co-authored-by: owl352 Co-authored-by: owl352 <64574305+owl352@users.noreply.github.com> --- .../v0/nodejs/PlatformPromiseClient.js | 36 ++++ .../v0/nodejs/PlatformPromiseClient.spec.js | 11 ++ .../methods/platform/PlatformMethodsFacade.js | 2 + .../getEpochsInfo/getEpochsInfoFactory.js | 2 +- .../GetIdentityBalanceResponse.js | 43 +++++ .../getIdentityBalanceFactory.js | 71 ++++++++ .../platform/PlatformMethodsFacade.spec.js | 20 +++ .../GetIdentityBalanceResponse.spec.js | 126 ++++++++++++++ .../getIdentityBalanceFactory.spec.js | 156 ++++++++++++++++++ 9 files changed, 466 insertions(+), 1 deletion(-) create mode 100644 packages/js-dapi-client/lib/methods/platform/getIdentityBalance/GetIdentityBalanceResponse.js create mode 100644 packages/js-dapi-client/lib/methods/platform/getIdentityBalance/getIdentityBalanceFactory.js create mode 100644 packages/js-dapi-client/test/unit/methods/platform/getIdentityBalance/GetIdentityBalanceResponse.spec.js create mode 100644 packages/js-dapi-client/test/unit/methods/platform/getIdentityBalance/getIdentityBalanceFactory.spec.js diff --git a/packages/dapi-grpc/clients/platform/v0/nodejs/PlatformPromiseClient.js b/packages/dapi-grpc/clients/platform/v0/nodejs/PlatformPromiseClient.js index b2a6d25bb26..a6190b4ac77 100644 --- a/packages/dapi-grpc/clients/platform/v0/nodejs/PlatformPromiseClient.js +++ b/packages/dapi-grpc/clients/platform/v0/nodejs/PlatformPromiseClient.js @@ -62,6 +62,8 @@ const { GetTotalCreditsInPlatformResponse: PBJSGetTotalCreditsInPlatformResponse, GetStatusRequest: PBJSGetStatusRequest, GetStatusResponse: PBJSGetStatusResponse, + GetIdentityBalanceRequest: PBJSGetIdentityBalanceRequest, + GetIdentityBalanceResponse: PBJSGetIdentityBalanceResponse, }, }, }, @@ -88,6 +90,7 @@ const { GetIdentityKeysResponse: ProtocGetIdentityKeysResponse, GetTotalCreditsInPlatformResponse: ProtocGetTotalCreditsInPlatformResponse, GetStatusResponse: ProtocGetStatusResponse, + GetIdentityBalanceResponse: ProtocGetIdentityBalanceResponse, } = require('./platform_protoc'); const getPlatformDefinition = require('../../../../lib/getPlatformDefinition'); @@ -186,6 +189,10 @@ class PlatformPromiseClient { this.client.getStatus.bind(this.client), ); + this.client.getIdentityBalance = promisify( + this.client.getIdentityBalance.bind(this.client), + ); + this.protocolVersion = undefined; } @@ -762,6 +769,35 @@ class PlatformPromiseClient { ); } + getIdentityBalance( + getIdentityBalanceRequest, + metadata = {}, + options = {}, + ) { + if (!isObject(metadata)) { + throw new Error('metadata must be an object'); + } + + return this.client.getIdentityBalance( + getIdentityBalanceRequest, + convertObjectToMetadata(metadata), + { + interceptors: [ + jsonToProtobufInterceptorFactory( + jsonToProtobufFactory( + ProtocGetIdentityBalanceResponse, + PBJSGetIdentityBalanceResponse, + ), + protobufToJsonFactory( + PBJSGetIdentityBalanceRequest, + ), + ), + ], + ...options, + }, + ); + } + /** * @param {string} protocolVersion */ diff --git a/packages/dapi-grpc/test/unit/clients/platform/v0/nodejs/PlatformPromiseClient.spec.js b/packages/dapi-grpc/test/unit/clients/platform/v0/nodejs/PlatformPromiseClient.spec.js index 5987cbfd0f4..fc3eb575fad 100644 --- a/packages/dapi-grpc/test/unit/clients/platform/v0/nodejs/PlatformPromiseClient.spec.js +++ b/packages/dapi-grpc/test/unit/clients/platform/v0/nodejs/PlatformPromiseClient.spec.js @@ -22,6 +22,7 @@ describe('PlatformPromiseClient', () => { getIdentityContractNonce: this.sinon.stub().resolves(response), getIdentityNonce: this.sinon.stub().resolves(response), getIdentityKeys: this.sinon.stub().resolves(response), + getIdentityBalance: this.sinon.stub().resolves(response), }; }); @@ -170,4 +171,14 @@ describe('PlatformPromiseClient', () => { .to.be.calledOnceWith(request); }); }); + + describe('#getIdentityBalance', () => { + it('should get identity balance', async () => { + const result = await platformPromiseClient.getIdentityBalance(request); + + expect(result).to.equal(response); + expect(platformPromiseClient.client.getIdentityBalance) + .to.be.calledOnceWith(request); + }); + }); }); diff --git a/packages/js-dapi-client/lib/methods/platform/PlatformMethodsFacade.js b/packages/js-dapi-client/lib/methods/platform/PlatformMethodsFacade.js index 035b7afbfc7..8de255abdec 100644 --- a/packages/js-dapi-client/lib/methods/platform/PlatformMethodsFacade.js +++ b/packages/js-dapi-client/lib/methods/platform/PlatformMethodsFacade.js @@ -15,6 +15,7 @@ const getIdentityNonceFactory = require('./getIdentityNonce/getIdentityNonceFact const getIdentityKeysFactory = require('./getIdentityKeys/getIdentityKeysFactory'); const getTotalCreditsInPlatformFactory = require('./getTotalCreditsInPlatform/getTotalCreditsInPlatformFactory'); const getStatusFactory = require('./getStatus/getStatusFactory'); +const getIdentityBalanceFactory = require('./getIdentityBalance/getIdentityBalanceFactory'); class PlatformMethodsFacade { /** @@ -40,6 +41,7 @@ class PlatformMethodsFacade { this.getIdentityKeys = getIdentityKeysFactory(grpcTransport); this.getTotalCreditsInPlatform = getTotalCreditsInPlatformFactory(grpcTransport); this.getStatus = getStatusFactory(grpcTransport); + this.getIdentityBalance = getIdentityBalanceFactory(grpcTransport); } } diff --git a/packages/js-dapi-client/lib/methods/platform/getEpochsInfo/getEpochsInfoFactory.js b/packages/js-dapi-client/lib/methods/platform/getEpochsInfo/getEpochsInfoFactory.js index 9ae984dbc9a..9982c6f9e6a 100644 --- a/packages/js-dapi-client/lib/methods/platform/getEpochsInfo/getEpochsInfoFactory.js +++ b/packages/js-dapi-client/lib/methods/platform/getEpochsInfo/getEpochsInfoFactory.js @@ -28,7 +28,7 @@ function getEpochsInfoFactory(grpcTransport) { const getEpochInfosRequest = new GetEpochsInfoRequest(); getEpochInfosRequest.setV0( new GetEpochsInfoRequestV0() - .setStartEpoch(new UInt32Value([startEpoch])) + .setStartEpoch(typeof startEpoch === 'number' ? new UInt32Value([startEpoch]) : undefined) .setCount(count) .setAscending(!!options.ascending) .setProve(!!options.prove), diff --git a/packages/js-dapi-client/lib/methods/platform/getIdentityBalance/GetIdentityBalanceResponse.js b/packages/js-dapi-client/lib/methods/platform/getIdentityBalance/GetIdentityBalanceResponse.js new file mode 100644 index 00000000000..b5b4a59b310 --- /dev/null +++ b/packages/js-dapi-client/lib/methods/platform/getIdentityBalance/GetIdentityBalanceResponse.js @@ -0,0 +1,43 @@ +const AbstractResponse = require('../response/AbstractResponse'); +const InvalidResponseError = require('../response/errors/InvalidResponseError'); + +class GetIdentityBalanceResponse extends AbstractResponse { + /** + * @param {number} balance + * @param {Metadata} metadata + * @param {Proof} [proof] + */ + constructor(balance, metadata, proof = undefined) { + super(metadata, proof); + + this.balance = balance; + } + + /** + * @returns {number} + */ + getBalance() { + return this.balance; + } + + /** + * @param proto + * @returns {GetIdentityBalanceResponse} + */ + static createFromProto(proto) { + const balance = proto.getV0().getBalance(); + const { metadata, proof } = AbstractResponse.createMetadataAndProofFromProto(proto); + + if ((balance === null || balance === undefined) && !proof) { + throw new InvalidResponseError('Balance is not defined'); + } + + return new GetIdentityBalanceResponse( + balance, + metadata, + proof, + ); + } +} + +module.exports = GetIdentityBalanceResponse; diff --git a/packages/js-dapi-client/lib/methods/platform/getIdentityBalance/getIdentityBalanceFactory.js b/packages/js-dapi-client/lib/methods/platform/getIdentityBalance/getIdentityBalanceFactory.js new file mode 100644 index 00000000000..93862ccff9a --- /dev/null +++ b/packages/js-dapi-client/lib/methods/platform/getIdentityBalance/getIdentityBalanceFactory.js @@ -0,0 +1,71 @@ +const { + v0: { + PlatformPromiseClient, + GetIdentityBalanceRequest, + }, +} = require('@dashevo/dapi-grpc'); + +const GetIdentityBalanceResponse = require('./GetIdentityBalanceResponse'); +const InvalidResponseError = require('../response/errors/InvalidResponseError'); + +/** + * @param {GrpcTransport} grpcTransport + * @returns {getIdentityBalance} + */ +function getIdentityBalanceFactory(grpcTransport) { + /** + * Fetch the identity balance by id + * @typedef {getIdentityBalance} + * @param {Buffer} id + * @param {DAPIClientOptions & {prove: boolean}} [options] + * @returns {Promise} + */ + async function getIdentityBalance(id, options = {}) { + const { GetIdentityBalanceRequestV0 } = GetIdentityBalanceRequest; + const getIdentityBalanceRequest = new GetIdentityBalanceRequest(); + // need to convert objects inherited from Buffer to pure buffer as google protobuf + // doesn't support extended buffers + // https://github.com/protocolbuffers/protobuf/blob/master/js/binary/utils.js#L1049 + if (Buffer.isBuffer(id)) { + // eslint-disable-next-line no-param-reassign + id = Buffer.from(id); + } + + getIdentityBalanceRequest.setV0( + new GetIdentityBalanceRequestV0() + .setId(id) + .setProve(!!options.prove), + ); + + let lastError; + + // TODO: simple retry before the dapi versioning is properly implemented + for (let i = 0; i < 3; i += 1) { + try { + // eslint-disable-next-line no-await-in-loop + const getIdentityBalanceResponse = await grpcTransport.request( + PlatformPromiseClient, + 'getIdentityBalance', + getIdentityBalanceRequest, + options, + ); + + return GetIdentityBalanceResponse.createFromProto(getIdentityBalanceResponse); + } catch (e) { + if (e instanceof InvalidResponseError) { + lastError = e; + } else { + throw e; + } + } + } + + // If we made it past the cycle it means that the retry didn't work, + // and we're throwing the last error encountered + throw lastError; + } + + return getIdentityBalance; +} + +module.exports = getIdentityBalanceFactory; diff --git a/packages/js-dapi-client/test/integration/methods/platform/PlatformMethodsFacade.spec.js b/packages/js-dapi-client/test/integration/methods/platform/PlatformMethodsFacade.spec.js index cb2c2dd0fae..fbb1f32ed5c 100644 --- a/packages/js-dapi-client/test/integration/methods/platform/PlatformMethodsFacade.spec.js +++ b/packages/js-dapi-client/test/integration/methods/platform/PlatformMethodsFacade.spec.js @@ -4,6 +4,7 @@ const { GetDataContractResponse, GetDocumentsResponse, GetIdentityResponse, + GetIdentityBalanceResponse, GetIdentityByPublicKeyHashResponse, GetIdentitiesContractKeysResponse, GetEpochsInfoResponse, @@ -27,6 +28,7 @@ const PlatformMethodsFacade = require('../../../../lib/methods/platform/Platform const { WaitForStateTransitionResultResponseV0 } = WaitForStateTransitionResultResponse; const { GetIdentityResponseV0 } = GetIdentityResponse; +const { GetIdentityBalanceResponseV0 } = GetIdentityBalanceResponse; const { GetIdentityByPublicKeyHashResponseV0 } = GetIdentityByPublicKeyHashResponse; const { GetIdentitiesContractKeysResponseV0 } = GetIdentitiesContractKeysResponse; const { GetDocumentsResponseV0 } = GetDocumentsResponse; @@ -318,4 +320,22 @@ describe('PlatformMethodsFacade', () => { expect(grpcTransportMock.request).to.be.calledOnce(); }); }); + + describe('#getIdentityBalance', () => { + it('should get identity balance', async () => { + const response = new GetIdentityBalanceResponse(); + + response.setV0( + new GetIdentityBalanceResponseV0() + .setMetadata(new ResponseMetadata()) + .setBalance(1337), + ); + + grpcTransportMock.request.resolves(response); + + await platformMethods.getIdentityBalance('41nthkqvHBLnqiMkSbsdTNANzYu9bgdv4etKoRUunY1M'); + + expect(grpcTransportMock.request).to.be.calledOnce(); + }); + }); }); diff --git a/packages/js-dapi-client/test/unit/methods/platform/getIdentityBalance/GetIdentityBalanceResponse.spec.js b/packages/js-dapi-client/test/unit/methods/platform/getIdentityBalance/GetIdentityBalanceResponse.spec.js new file mode 100644 index 00000000000..3df6adb6be5 --- /dev/null +++ b/packages/js-dapi-client/test/unit/methods/platform/getIdentityBalance/GetIdentityBalanceResponse.spec.js @@ -0,0 +1,126 @@ +const { + v0: { + GetIdentityBalanceResponse, + ResponseMetadata, + Proof: ProofResponse, + }, +} = require('@dashevo/dapi-grpc'); + +const GetIdentityBalanceResponseClass = require('../../../../../lib/methods/platform/getIdentityBalance/GetIdentityBalanceResponse'); +const getMetadataFixture = require('../../../../../lib/test/fixtures/getMetadataFixture'); +const InvalidResponseError = require('../../../../../lib/methods/platform/response/errors/InvalidResponseError'); +const getProofFixture = require('../../../../../lib/test/fixtures/getProofFixture'); +const Proof = require('../../../../../lib/methods/platform/response/Proof'); +const Metadata = require('../../../../../lib/methods/platform/response/Metadata'); + +describe('GetIdentityBalanceResponse', () => { + let getIdentityBalanceResponse; + let metadataFixture; + let balance; + let proto; + let proofFixture; + + beforeEach(async () => { + metadataFixture = getMetadataFixture(); + proofFixture = getProofFixture(); + balance = 1337; + + const { GetIdentityBalanceResponseV0 } = GetIdentityBalanceResponse; + proto = new GetIdentityBalanceResponse(); + + const metadata = new ResponseMetadata(); + metadata.setHeight(metadataFixture.height); + metadata.setCoreChainLockedHeight(metadataFixture.coreChainLockedHeight); + metadata.setTimeMs(metadataFixture.timeMs); + metadata.setProtocolVersion(metadataFixture.protocolVersion); + + proto.setV0( + new GetIdentityBalanceResponseV0() + .setBalance(balance) + .setMetadata(metadata), + ); + + getIdentityBalanceResponse = new GetIdentityBalanceResponseClass( + balance, + new Metadata(metadataFixture), + ); + }); + + it('should return Identity balance', () => { + const identityBalance = getIdentityBalanceResponse.getBalance(); + const identityProof = getIdentityBalanceResponse.getProof(); + + expect(identityBalance).to.equal(balance); + expect(identityProof).to.equal(undefined); + }); + + it('should return proof', () => { + getIdentityBalanceResponse = new GetIdentityBalanceResponseClass( + balance, + new Metadata(metadataFixture), + new Proof(proofFixture), + ); + + const identityBalance = getIdentityBalanceResponse.getBalance(); + const proof = getIdentityBalanceResponse.getProof(); + + expect(identityBalance).to.equal(balance); + expect(proof).to.be.an.instanceOf(Proof); + expect(proof.getGrovedbProof()).to.deep.equal(proofFixture.merkleProof); + expect(proof.getQuorumHash()).to.deep.equal(proofFixture.quorumHash); + expect(proof.getSignature()).to.deep.equal(proofFixture.signature); + expect(proof.getRound()).to.deep.equal(proofFixture.round); + }); + + it('should create an instance from proto', () => { + getIdentityBalanceResponse = GetIdentityBalanceResponseClass.createFromProto(proto); + expect(getIdentityBalanceResponse).to.be + .an.instanceOf(GetIdentityBalanceResponseClass); + expect(getIdentityBalanceResponse.getBalance()).to.equal(balance); + + expect(getIdentityBalanceResponse.getMetadata()) + .to.be.an.instanceOf(Metadata); + expect(getIdentityBalanceResponse.getMetadata().getHeight()) + .to.equal(metadataFixture.height); + expect(getIdentityBalanceResponse.getMetadata().getCoreChainLockedHeight()) + .to.equal(metadataFixture.coreChainLockedHeight); + + expect(getIdentityBalanceResponse.getProof()).to.equal(undefined); + }); + + it('should create an instance with proof from proto', () => { + const proofProto = new ProofResponse(); + + proofProto.setQuorumHash(proofFixture.quorumHash); + proofProto.setSignature(proofFixture.signature); + proofProto.setGrovedbProof(proofFixture.merkleProof); + proofProto.setRound(proofFixture.round); + + proto.getV0().setBalance(undefined); + proto.getV0().setProof(proofProto); + + getIdentityBalanceResponse = GetIdentityBalanceResponseClass.createFromProto(proto); + + expect(getIdentityBalanceResponse.getBalance()).to.equal(0); + expect(getIdentityBalanceResponse.getMetadata()).to.deep.equal(metadataFixture); + + const proof = getIdentityBalanceResponse.getProof(); + expect(proof).to.be.an.instanceOf(Proof); + expect(proof.getGrovedbProof()).to.deep.equal(proofFixture.merkleProof); + expect(proof.getQuorumHash()).to.deep.equal(proofFixture.quorumHash); + expect(proof.getSignature()).to.deep.equal(proofFixture.signature); + expect(proof.getRound()).to.deep.equal(proofFixture.round); + }); + + it('should throw InvalidResponseError if Metadata is not defined', () => { + proto.getV0().setMetadata(undefined); + + try { + getIdentityBalanceResponse = GetIdentityBalanceResponseClass.createFromProto(proto); + + expect.fail('should throw InvalidResponseError'); + } catch (e) { + expect(e).to.be.an.instanceOf(InvalidResponseError); + } + }); +}); diff --git a/packages/js-dapi-client/test/unit/methods/platform/getIdentityBalance/getIdentityBalanceFactory.spec.js b/packages/js-dapi-client/test/unit/methods/platform/getIdentityBalance/getIdentityBalanceFactory.spec.js new file mode 100644 index 00000000000..210d2a8a9f9 --- /dev/null +++ b/packages/js-dapi-client/test/unit/methods/platform/getIdentityBalance/getIdentityBalanceFactory.spec.js @@ -0,0 +1,156 @@ +const { + v0: { + PlatformPromiseClient, + GetIdentityBalanceRequest, + GetIdentityBalanceResponse, + ResponseMetadata, + Proof: ProofResponse, + }, +} = require('@dashevo/dapi-grpc'); + +const { GetIdentityBalanceResponseV0 } = GetIdentityBalanceResponse; + +const getIdentityBalanceFactory = require('../../../../../lib/methods/platform/getIdentityBalance/getIdentityBalanceFactory'); +const getMetadataFixture = require('../../../../../lib/test/fixtures/getMetadataFixture'); +const getProofFixture = require('../../../../../lib/test/fixtures/getProofFixture'); +const Proof = require('../../../../../lib/methods/platform/response/Proof'); + +describe('getIdentityBalanceFactory', () => { + let grpcTransportMock; + let getIdentityBalance; + let options; + let response; + let balance; + let identityId; + let metadataFixture; + let proofFixture; + let proofResponse; + + beforeEach(async function beforeEach() { + balance = 1337; + + identityId = Buffer.alloc(32).fill(0); + + metadataFixture = getMetadataFixture(); + proofFixture = getProofFixture(); + + const metadata = new ResponseMetadata(); + metadata.setHeight(metadataFixture.height); + metadata.setCoreChainLockedHeight(metadataFixture.coreChainLockedHeight); + metadata.setTimeMs(metadataFixture.timeMs); + metadata.setProtocolVersion(metadataFixture.protocolVersion); + + response = new GetIdentityBalanceResponse(); + + response.setV0( + new GetIdentityBalanceResponseV0() + .setBalance(balance) + .setMetadata(metadata), + ); + + proofResponse = new ProofResponse(); + + proofResponse.setQuorumHash(proofFixture.quorumHash); + proofResponse.setSignature(proofFixture.signature); + proofResponse.setGrovedbProof(proofFixture.merkleProof); + proofResponse.setRound(proofFixture.round); + + grpcTransportMock = { + request: this.sinon.stub().resolves(response), + }; + + getIdentityBalance = getIdentityBalanceFactory(grpcTransportMock); + + options = { + timeout: 1000, + }; + }); + + it('should return identity balance', async () => { + const result = await getIdentityBalance(identityId, options); + + const { GetIdentityBalanceRequestV0 } = GetIdentityBalanceRequest; + const request = new GetIdentityBalanceRequest(); + request.setV0( + new GetIdentityBalanceRequestV0() + .setId(identityId) + .setProve(false), + ); + + expect(grpcTransportMock.request).to.be.calledOnceWithExactly( + PlatformPromiseClient, + 'getIdentityBalance', + request, + options, + ); + expect(result.getBalance()).to.deep.equal(balance); + expect(result.getMetadata()).to.deep.equal(metadataFixture); + expect(result.getProof()).to.equal(undefined); + }); + + it('should return proof', async () => { + options.prove = true; + response.getV0().setBalance(undefined); + response.getV0().setProof(proofResponse); + + const result = await getIdentityBalance(identityId, options); + + const { GetIdentityBalanceRequestV0 } = GetIdentityBalanceRequest; + const request = new GetIdentityBalanceRequest(); + request.setV0( + new GetIdentityBalanceRequestV0() + .setId(identityId) + .setProve(true), + ); + + expect(grpcTransportMock.request).to.be.calledOnceWithExactly( + PlatformPromiseClient, + 'getIdentityBalance', + request, + options, + ); + + expect(result.getBalance()).to.deep.equal(0); + + expect(result.getMetadata()).to.deep.equal(metadataFixture); + + expect(result.getProof()).to.be.an.instanceOf(Proof); + expect(result.getProof().getGrovedbProof()).to.deep.equal(proofFixture.merkleProof); + expect(result.getProof().getQuorumHash()).to.deep.equal(proofFixture.quorumHash); + expect(result.getProof().getSignature()).to.deep.equal(proofFixture.signature); + expect(result.getProof().getRound()).to.deep.equal(proofFixture.round); + expect(result.getMetadata()).to.deep.equal(metadataFixture); + expect(result.getMetadata().getHeight()).to.equal(metadataFixture.height); + expect(result.getMetadata().getCoreChainLockedHeight()).to.equal( + metadataFixture.coreChainLockedHeight, + ); + }); + + it('should throw unknown error', async () => { + const error = new Error('Unknown found'); + + grpcTransportMock.request.throws(error); + + const { GetIdentityBalanceRequestV0 } = GetIdentityBalanceRequest; + const request = new GetIdentityBalanceRequest(); + request.setV0( + new GetIdentityBalanceRequestV0() + .setId(identityId) + .setProve(false), + ); + + try { + await getIdentityBalance(identityId, options); + + expect.fail('should throw unknown error'); + } catch (e) { + expect(e).to.deep.equal(error); + expect(grpcTransportMock.request).to.be.calledOnceWithExactly( + PlatformPromiseClient, + 'getIdentityBalance', + request, + options, + ); + } + }); +});