Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(dapi): implement getIdentityBalance #2105

Merged
merged 16 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ const {
GetTotalCreditsInPlatformResponse: PBJSGetTotalCreditsInPlatformResponse,
GetStatusRequest: PBJSGetStatusRequest,
GetStatusResponse: PBJSGetStatusResponse,
GetIdentityBalanceRequest: PBJSGetIdentityBalanceRequest,
GetIdentityBalanceResponse: PBJSGetIdentityBalanceResponse,
},
},
},
Expand All @@ -88,6 +90,7 @@ const {
GetIdentityKeysResponse: ProtocGetIdentityKeysResponse,
GetTotalCreditsInPlatformResponse: ProtocGetTotalCreditsInPlatformResponse,
GetStatusResponse: ProtocGetStatusResponse,
GetIdentityBalanceResponse: ProtocGetIdentityBalanceResponse,
} = require('./platform_protoc');

const getPlatformDefinition = require('../../../../lib/getPlatformDefinition');
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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),
};
});

Expand Down Expand Up @@ -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);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
/**
Expand All @@ -40,6 +41,7 @@ class PlatformMethodsFacade {
this.getIdentityKeys = getIdentityKeysFactory(grpcTransport);
this.getTotalCreditsInPlatform = getTotalCreditsInPlatformFactory(grpcTransport);
this.getStatus = getStatusFactory(grpcTransport);
this.getIdentityBalance = getIdentityBalanceFactory(grpcTransport);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -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<GetIdentityBalanceResponse>}
*/
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;
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const {
GetDataContractResponse,
GetDocumentsResponse,
GetIdentityResponse,
GetIdentityBalanceResponse,
GetIdentityByPublicKeyHashResponse,
GetIdentitiesContractKeysResponse,
GetEpochsInfoResponse,
Expand All @@ -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;
Expand Down Expand Up @@ -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();
});
});
});
Original file line number Diff line number Diff line change
@@ -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);
}
});
});
Loading
Loading