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(js-dapi-client): add contested resources query methods #2446

Open
wants to merge 2 commits into
base: v2.0-dev
Choose a base branch
from
Open
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 @@ -16,6 +16,8 @@ const getIdentityKeysFactory = require('./getIdentityKeys/getIdentityKeysFactory
const getTotalCreditsInPlatformFactory = require('./getTotalCreditsInPlatform/getTotalCreditsInPlatformFactory');
const getStatusFactory = require('./getStatus/getStatusFactory');
const getIdentityBalanceFactory = require('./getIdentityBalance/getIdentityBalanceFactory');
const getContestedResourceVoteStateFactory = require('./getContestedResourceVoteState/getContestedResourceVoteStateFactory');
const getContestedResourcesFactory = require('./getContestedResources/getContestedResourcesFactory');

class PlatformMethodsFacade {
/**
Expand All @@ -42,6 +44,8 @@ class PlatformMethodsFacade {
this.getTotalCreditsInPlatform = getTotalCreditsInPlatformFactory(grpcTransport);
this.getStatus = getStatusFactory(grpcTransport);
this.getIdentityBalance = getIdentityBalanceFactory(grpcTransport);
this.getContestedResourceVoteState = getContestedResourceVoteStateFactory(grpcTransport);
this.getContestedResources = getContestedResourcesFactory(grpcTransport);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
const AbstractResponse = require('../response/AbstractResponse');
const InvalidResponseError = require('../response/errors/InvalidResponseError');

class GetContestedResourceVoteStateResponse extends AbstractResponse {
/**
* @param {object} contestedResourceContenders
* @param {Metadata} metadata
* @param {Proof} [proof]
*/
constructor(contestedResourceContenders, metadata, proof = undefined) {
super(metadata, proof);

this.contestedResourceContenders = contestedResourceContenders;
}

/**
* @returns {object}
*/
getContestedResourceContenders() {
return this.contestedResourceContenders;
}

/**
* @param proto
* @returns {GetContestedResourceVoteStateResponse}
*/
static createFromProto(proto) {
// eslint-disable-next-line
const contestedResourceContenders = proto.getV0().getContestedResourceContenders();

const { metadata, proof } = AbstractResponse.createMetadataAndProofFromProto(
proto,
);

if ((typeof contestedResourceContenders === 'undefined' || contestedResourceContenders === null) && !proof) {
throw new InvalidResponseError('Contested Resource Contenders data is not defined');
}

return new GetContestedResourceVoteStateResponse(
contestedResourceContenders.toObject(),
metadata,
proof,
);
}
}

module.exports = GetContestedResourceVoteStateResponse;
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
const {
v0: {
PlatformPromiseClient,
GetContestedResourceVoteStateRequest,
},
} = require('@dashevo/dapi-grpc');

const GetContestedResourceVoteStateResponse = require('./GetContestedResourceVoteStateResponse');
const InvalidResponseError = require('../response/errors/InvalidResponseError');

/**
* @param {GrpcTransport} grpcTransport
* @returns {getContestedResourceVoteStateRequest}
*/
function getContestedResourceVoteStateFactory(grpcTransport) {
/**
* Fetch the version upgrade votes status
* @typedef {getContestedResourceVoteState}
* @param contractId
* @param documentTypeName
* @param indexName
* @param resultType
* @param indexValuesList
* @param startAtIdentifierInfo
* @param allowIncludeLockedAndAbstainingVoteTally
* @param count
* @param {DAPIClientOptions & {prove: boolean}} [options]
* @returns {Promise<gрetContestedResourceVoteStateResponse>}
*/
async function getContestedResourceVoteState(
contractId,
documentTypeName,
indexName,
resultType,
indexValuesList,
startAtIdentifierInfo,
allowIncludeLockedAndAbstainingVoteTally,
count,
options = {},
) {
const { GetContestedResourceVoteStateRequestV0 } = GetContestedResourceVoteStateRequest;

// eslint-disable-next-line max-len
const getContestedResourceVoteStateRequest = new GetContestedResourceVoteStateRequest();

if (Buffer.isBuffer(contractId)) {
// eslint-disable-next-line no-param-reassign
contractId = Buffer.from(contractId);
}
Comment on lines +46 to +49
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix incorrect Buffer conversion logic

The Buffer conversion logic has the same issue as in getContestedResourcesFactory.

-    if (Buffer.isBuffer(contractId)) {
+    if (!Buffer.isBuffer(contractId)) {
       // eslint-disable-next-line no-param-reassign
       contractId = Buffer.from(contractId);
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (Buffer.isBuffer(contractId)) {
// eslint-disable-next-line no-param-reassign
contractId = Buffer.from(contractId);
}
if (!Buffer.isBuffer(contractId)) {
// eslint-disable-next-line no-param-reassign
contractId = Buffer.from(contractId);
}


getContestedResourceVoteStateRequest.setV0(
new GetContestedResourceVoteStateRequestV0()
.setContractId(contractId)
.setDocumentTypeName(documentTypeName)
.setIndexName(indexName)
.setResultType(resultType)
.setIndexValuesList(indexValuesList)
.setStartAtIdentifierInfo(startAtIdentifierInfo)
.setAllowIncludeLockedAndAbstainingVoteTally(allowIncludeLockedAndAbstainingVoteTally)
.setCount(count)
.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 getContestedResourceVoteStateResponse = await grpcTransport.request(
PlatformPromiseClient,
'getContestedResourceVoteState',
getContestedResourceVoteStateRequest,
options,
);

return GetContestedResourceVoteStateResponse
.createFromProto(getContestedResourceVoteStateResponse);
} 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 getContestedResourceVoteState;
}

module.exports = getContestedResourceVoteStateFactory;
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
const {
v0: {
PlatformPromiseClient,
GetContestedResourcesRequest,
},
} = require('@dashevo/dapi-grpc');

const GetContestedResourcesResponse = require('./GetContestedResourcesResponse');
const InvalidResponseError = require('../response/errors/InvalidResponseError');

/**
* @param {GrpcTransport} grpcTransport
* @returns {getContestedResourcesRequest}
*/
function getContestedResourcesFactory(grpcTransport) {
/**
* Fetch the contested resources for specific contract
* @typedef {getContestedResources}
* @param contractId
* @param documentTypeName
* @param indexName
* @param startIndexValues
* @param endIndexValues
* @param startAtValueInfo
* @param count
* @param orderAscending
* @param {DAPIClientOptions & {prove: boolean}} [options]
* @returns {Promise<getContestedResourcesResponse>}
*/
async function getContestedResources(
contractId,
documentTypeName,
indexName,
startIndexValues,
endIndexValues,
startAtValueInfo,
count,
orderAscending,
options = {},
) {
const { GetContestedResourcesRequestV0 } = GetContestedResourcesRequest;

// eslint-disable-next-line max-len
const getContestedResourcesRequest = new GetContestedResourcesRequest();

if (Buffer.isBuffer(contractId)) {
// eslint-disable-next-line no-param-reassign
contractId = Buffer.from(contractId);
}
Comment on lines +46 to +49
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix incorrect Buffer conversion logic

The current implementation creates a new Buffer from an existing Buffer unnecessarily, and the condition is reversed.

-    if (Buffer.isBuffer(contractId)) {
+    if (!Buffer.isBuffer(contractId)) {
       // eslint-disable-next-line no-param-reassign
       contractId = Buffer.from(contractId);
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (Buffer.isBuffer(contractId)) {
// eslint-disable-next-line no-param-reassign
contractId = Buffer.from(contractId);
}
if (!Buffer.isBuffer(contractId)) {
// eslint-disable-next-line no-param-reassign
contractId = Buffer.from(contractId);
}


getContestedResourcesRequest.setV0(
new GetContestedResourcesRequestV0()
.setContractId(contractId)
.setDocumentTypeName(documentTypeName)
.setIndexName(indexName)
.setStartIndexValuesList(startIndexValues)
.setEndIndexValuesList(endIndexValues)
.setStartAtValueInfo(startAtValueInfo)
.setCount(count)
.setOrderAscending(orderAscending)
.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 getContestedResourcesResponse = await grpcTransport.request(
PlatformPromiseClient,
'getContestedResources',
getContestedResourcesRequest,
options,
);

return GetContestedResourcesResponse
.createFromProto(getContestedResourcesResponse);
} catch (e) {
if (e instanceof InvalidResponseError) {
lastError = e;
} else {
throw e;
}
}
}
Comment on lines +66 to +86
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve retry mechanism

The retry implementation has several issues:

  1. Magic number for retry count
  2. No exponential backoff
  3. No delay between retries

Consider implementing a more robust retry mechanism:

+    const MAX_RETRIES = 3;
+    const BASE_DELAY = 1000; // 1 second
+
     // TODO: simple retry before the dapi versioning is properly implemented
-    for (let i = 0; i < 3; i += 1) {
+    for (let i = 0; i < MAX_RETRIES; i += 1) {
       try {
         // eslint-disable-next-line no-await-in-loop
         const getContestedResourcesResponse = await grpcTransport.request(
           PlatformPromiseClient,
           'getContestedResources',
           getContestedResourcesRequest,
           options,
         );
 
         return GetContestedResourcesResponse
           .createFromProto(getContestedResourcesResponse);
       } catch (e) {
         if (e instanceof InvalidResponseError) {
           lastError = e;
+          // Exponential backoff with jitter
+          const delay = Math.min(BASE_DELAY * Math.pow(2, i), 5000) + Math.random() * 1000;
+          await new Promise(resolve => setTimeout(resolve, delay));
         } else {
           throw e;
         }
       }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 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 getContestedResourcesResponse = await grpcTransport.request(
PlatformPromiseClient,
'getContestedResources',
getContestedResourcesRequest,
options,
);
return GetContestedResourcesResponse
.createFromProto(getContestedResourcesResponse);
} catch (e) {
if (e instanceof InvalidResponseError) {
lastError = e;
} else {
throw e;
}
}
}
const MAX_RETRIES = 3;
const BASE_DELAY = 1000; // 1 second
// TODO: simple retry before the dapi versioning is properly implemented
for (let i = 0; i < MAX_RETRIES; i += 1) {
try {
// eslint-disable-next-line no-await-in-loop
const getContestedResourcesResponse = await grpcTransport.request(
PlatformPromiseClient,
'getContestedResources',
getContestedResourcesRequest,
options,
);
return GetContestedResourcesResponse
.createFromProto(getContestedResourcesResponse);
} catch (e) {
if (e instanceof InvalidResponseError) {
lastError = e;
// Exponential backoff with jitter
const delay = Math.min(BASE_DELAY * Math.pow(2, i), 5000) + Math.random() * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
} 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 getContestedResources;
}

module.exports = getContestedResourcesFactory;
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
const AbstractResponse = require('../response/AbstractResponse');
const InvalidResponseError = require('../response/errors/InvalidResponseError');

class GetContestedResourcesResponse extends AbstractResponse {
/**
* @param {object} contestedResourceContenders
* @param contestedResources
* @param resultCase
* @param {Metadata} metadata
* @param {Proof} [proof]
*/
constructor(contestedResources, resultCase, metadata, proof = undefined) {
super(metadata, proof);

this.contestedResources = contestedResources;
this.resultCase = resultCase;
}

/**
* @returns {object}
*/
getContestedResources() {
return this.contestedResources;
}

/**
* @param proto
* @returns {GetContestedResourcesResponse}
*/
static createFromProto(proto) {
// eslint-disable-next-line
const contestedResourceContenders = proto.getV0().getContestedResourceValues();
const resultCase = proto.getV0().getResultCase();

const { metadata, proof } = AbstractResponse.createMetadataAndProofFromProto(
proto,
);

if ((typeof contestedResourceContenders === 'undefined' || contestedResourceContenders === null) && !proof) {
throw new InvalidResponseError('Contested Resource Contenders data is not defined');
}

return new GetContestedResourcesResponse(
contestedResourceContenders !== undefined ? contestedResourceContenders.getContestedResourceValuesList() : '',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add null check before accessing getContestedResourceValuesList

The code may throw if contestedResourceContenders is null but undefined check passes.

-      contestedResourceContenders !== undefined ? contestedResourceContenders.getContestedResourceValuesList() : '',
+      contestedResourceContenders != null ? contestedResourceContenders.getContestedResourceValuesList() : '',
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
contestedResourceContenders !== undefined ? contestedResourceContenders.getContestedResourceValuesList() : '',
contestedResourceContenders != null ? contestedResourceContenders.getContestedResourceValuesList() : '',

resultCase,
metadata,
proof,
);
}
}

module.exports = GetContestedResourcesResponse;
Loading
Loading