Skip to content
This repository has been archived by the owner on Apr 21, 2023. It is now read-only.

Commit

Permalink
[node-rewards-program] task: add service to communicate with API #679
Browse files Browse the repository at this point in the history
* [node-rewards-program] task: add service

add methods: prepareEnrollTransaction, checkEnrollmentAddress, checkEnrollmentStatus and getEnrollmentHash

* [node-rewards-program] fix: typo in error message

* [node-rewards-program] fix: typo in class name

* [node-rewards-program] task: add tests

* [node-rewards-program] fix: fetch mock, improve prepareEnrollTransaction test, register service, cleanup

* [node-rewards-program] feat: add getNodePayouts() and getNodeInfo() methods, add unit tests

* [node-rewards-program] fix: typo in jsdoc

* [node-rewards-program] fix: test comments

* [node-rewards-program] fix: remove whitespaces

* [node-rewards-program] task: refactor fetch requests

* [node-rewards-program] task: refactor the enrollment and error tests, remove unused variable

* [node-rewards-program] task: update codeword test, fix runPromiseErrorTest helper
  • Loading branch information
OlegMakarenko authored Jun 28, 2022
1 parent 35b428f commit a63f24b
Show file tree
Hide file tree
Showing 4 changed files with 517 additions and 0 deletions.
4 changes: 4 additions & 0 deletions src/app/services/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,8 @@ servicesModule.service('Voting', VotingService);
import CatapultOptinService from './catapultOptin.service'
servicesModule.service('CatapultOptin', CatapultOptinService);

// Set Node Rewards Program service
import NodeRewardsProgramService from './nodeRewardsProgram.service'
servicesModule.service('NodeRewardsProgram', NodeRewardsProgramService);

export default servicesModule;
168 changes: 168 additions & 0 deletions src/app/services/nodeRewardsProgram.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import nem from 'nem-sdk';

const NODE_REWARDS_PROGRAM_API_BASE_URL = 'https://supernodesapi.nem.io';

/** Service to enroll in the Node Rewards Program and to get the status related information */
class NodeRewardsProgram {

/**
* Initialize dependencies and properties.
*
* @params {services} - Angular services to inject
*/
constructor($localStorage, $filter, Wallet) {
'ngInject';

// Service dependencies region //

this._storage = $localStorage;
this._$filter = $filter;
this._Wallet = Wallet;

// End dependencies region //

// Service properties region //

this.apiBaseUrl = NODE_REWARDS_PROGRAM_API_BASE_URL;
this.common = nem.model.objects.get('common');

// End properties region //
}

// Service methods region //

/**
* Make get request.
*
* @param {string} url - URL of the resource to fetch.
* @param {string} errorMessage - Description of the error that will be thrown in case of an unsuccessful request.
*
* @return {Promise<object>} - Response.
*/
async _get(url, errorMessage) {
const response = await fetch(url);

if (!response.ok) {
throw Error(errorMessage);
}

return response;
}

/**
* Prepare the enroll transaction.
*
* @param {string} mainPublicKey - Delegated harvesting public key.
* @param {string} host - IP or domain of the node.
* @param {string} enrollmentAddress - Node Rewards Program enrollment address.
* @param {string} [multisigAccountAddress] - Multisig account address. For multisig enrollment only.
*
* @return {Promise<object>} - Prepared enroll transaction.
*/
async prepareEnrollTransaction(mainPublicKey, host, enrollmentAddress, multisigAccountAddress) {
// Create a new transaction object
const transferTransaction = nem.model.objects.get('transferTransaction');

// Set enrollment address as recipient
transferTransaction.recipient = enrollmentAddress.toUpperCase().replace(/-/g, '');

// Set multisig, if selected
if (multisigAccountAddress) {
transferTransaction.isMultisig = true;
transferTransaction.multisigAccount = multisigAccountAddress.toUpperCase().replace(/-/g, '');
}

// Set the message
const hash = await this.getCodewordHash(mainPublicKey);
transferTransaction.message = `enroll ${host} ${hash}`;

// Set no mosaics
transferTransaction.mosaics = null;

return nem.model.transactions.prepare('transferTransaction')(this.common, transferTransaction, this._Wallet.network);
}

/**
* Checks if an address is the current enrollment address.
*
* @param {string} address - Address to check.
*
* @return {Promise<boolean>} - If the address to check matches the current enrollment address.
*/
async checkEnrollmentAddress(address) {
const response = await this._get(`${this.apiBaseUrl}/enrollment/check/address/${address}`, 'failed_to_validate_enroll_address');
const status = await response.text();

return status === 'true';
}

/**
* Checks if a signer public key is enrolled in the current period.
*
* @param {string} publicKey - Delegated harvesting public key to check.
*
* @return {Promise<boolean>} - If the public key is enrolled in the current period.
*/
async checkEnrollmentStatus(publicKey) {
const successEnrollmentResponse = await this._get(`${this.apiBaseUrl}/enrollment/successes/${publicKey}?count=1`, 'failed_to_get_success_enrollments');
const successEnrollments = await successEnrollmentResponse.json();

if (successEnrollments.length === 0) {
return false;
}

const latestSuccessEnrollment = successEnrollments[0];
const enrollAddress = latestSuccessEnrollment.recipientAddress;

return this.checkEnrollmentAddress(enrollAddress);
}

/**
* Gets codeword dependent hash for the current period given a public key.
*
* @param {string} publicKey - Signer public key to use in hash calculation.
*
* @return {Promise<string>} - The codeword hex string, which should be used in enrollment messages.
*/
async getCodewordHash(publicKey) {
const response = await this._get(`${this.apiBaseUrl}/codeword/${publicKey}`, 'failed_to_get_codeword_hash');

return response.text();
}

/**
* Gets payout information for a single node.
*
* @param {string} nodeId - Id of the node to search.
* @param {number} pageNumber - Page number to return.
*
* @return {Promise<Array<object>>} - List of payouts.
*/
async getNodePayouts(nodeId, pageNumber) {
const count = 15;
const offset = count * pageNumber;
const response = await this._get(`${this.apiBaseUrl}/node/${nodeId}/payouts?count=${count}&offset=${offset}`, 'failed_to_get_payouts_page');

return response.json();
}

/**
* Gets detailed information about a single node.
*
* @param {string} nodeId - Id of the node to search. Can be one of:
* - Database node id
* - Hex encoded node main public key
* - Node IP (or host)
*
* @return {Promise<object>} - Node info.
*/
async getNodeInfo(nodeId) {
const response = await this._get(`${this.apiBaseUrl}/node/${nodeId}`, 'failed_to_get_node_info');

return response.json();
}

// End methods region //
}

export default NodeRewardsProgram;
22 changes: 22 additions & 0 deletions tests/helper/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Tests promise to throw given error.
*
* @param {Promise} promiseToTest - Promise to be tested.
* @param {any} expectedError - Error to be thrown.
*/
export const runPromiseErrorTest = async (promiseToTest, expectedError) => {
// Arrange:
let thrownError;

// Act:
try {
await promiseToTest;
}
catch (error) {
thrownError = error;
}

// Assert:
expect(thrownError instanceof expectedError.constructor).toBe(true);
expect(thrownError.message).toBe(expectedError.message);
};
Loading

0 comments on commit a63f24b

Please sign in to comment.