diff --git a/.gitignore b/.gitignore index 496ee2c..01198e3 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ -.DS_Store \ No newline at end of file +.DS_Store +node_modules +package-lock.json +yarn.lock diff --git a/Lesson 1/EZ-testing-task/_koiiNode/koiiNode.js b/Lesson 1/EZ-testing-task/_koiiNode/koiiNode.js deleted file mode 100644 index 70edc11..0000000 --- a/Lesson 1/EZ-testing-task/_koiiNode/koiiNode.js +++ /dev/null @@ -1,1133 +0,0 @@ -const { default: axios } = require('axios'); -const { createHash } = require('crypto'); - -const { Connection, PublicKey, Keypair } = require('@_koi/web3.js'); - -const Datastore = require('nedb-promises'); -const fsPromises = require('fs/promises'); -const bs58 = require('bs58'); -const nacl = require('tweetnacl'); -const semver = require('semver'); - -/** **************************************** init.js ********************************** */ - -const express = require('express'); -// Only used for testing purposes, in production the env will be injected by tasknode -require('dotenv').config(); -const bodyParser = require('body-parser'); -/** - * This will be the name of the current task as coming from the task node running this task. - */ -const TASK_NAME = process.argv[2] || 'Local'; -/** - * This will be the id of the current task as coming from the task node running this task. - */ -const TASK_ID = process.argv[3]; -/** - * This will be the PORT on which the this task is expected to run the express server coming from the task node running this task. - * As all communication via the task node and this task will be done on this port. - */ -const EXPRESS_PORT = process.argv[4] || 10000; - -const LogLevel = { - Log: 'log', - Warn: 'warn', - Error: 'error', -}; - -// Not used anymore -// const NODE_MODE = process.argv[5]; - -/** - * This will be the main account public key in string format of the task node running this task. - */ -const MAIN_ACCOUNT_PUBKEY = process.argv[6]; -/** - * This will be the secret used by the task to authenticate with task node running this task. - */ -const SECRET_KEY = process.argv[7]; -/** - * This will be K2 url being used by the task node, possible values are 'https://k2-testnet.koii.live' | 'https://k2-devnet.koii.live' | 'http://localhost:8899' - */ -const K2_NODE_URL = process.argv[8] || "https://testnet.koii.network"; -/** - * This will be public task node endpoint (Or local if it doesn't have any) of the task node running this task. - */ -const SERVICE_URL = process.argv[9]; -/** - * This will be stake of the task node running this task, can be double checked with the task state and staking public key. - */ -const STAKE = Number(process.argv[10]); -/** - * This will be the port used by task node as the express server port, so it can be used by the task for the communication with the task node - */ -const TASK_NODE_PORT = Number(process.argv[11]); - -const app = express(); - -console.log('SETTING UP EXPRESS'); - -app.use(bodyParser.urlencoded({ extended: false })); - -app.use(bodyParser.json()); - -app.use((req, res, next) => { - res.setHeader('Access-Control-Allow-Origin', '*'); - res.setHeader( - 'Access-Control-Allow-Methods', - 'GET, POST, PUT, PATCH, DELETE', - ); - res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); - res.setHeader('Access-Control-Allow-Credentials', false); - if (req.method === 'OPTIONS') - // if is preflight(OPTIONS) then response status 204(NO CONTENT) - { return res.send(204); } - next(); -}); - -app.get('/', (req, res) => { - res.send('Hello World!'); -}); - -const _server = app.listen(EXPRESS_PORT, () => { - console.log(`${TASK_NAME} listening on port ${EXPRESS_PORT}`); -}); - -/** **************************************** NamespaceWrapper.js ********************************** */ - -const taskNodeAdministered = !!TASK_ID; -const BASE_ROOT_URL = `http://localhost:${TASK_NODE_PORT}/namespace-wrapper`; -let connection; - -class NamespaceWrapper { - #db; - - #testingMainSystemAccount; - - #testingStakingSystemAccount; - - #testingTaskState; - - #testingDistributionList; - - constructor() { - if (taskNodeAdministered) { - this.initializeDB(); - } else { - this.#db = Datastore.create('./localKOIIDB.db'); - this.defaultTaskSetup(); - } - } - - async initializeDB() { - if (this.#db) return; - try { - if (taskNodeAdministered) { - const path = await this.getTaskLevelDBPath(); - this.#db = Datastore.create(path); - } else { - this.#db = Datastore.create('./localKOIIDB.db'); - } - } catch (e) { - this.#db = Datastore.create(`../namespace/${TASK_ID}/KOIILevelDB.db`); - } - } - - async getDb() { - if (this.#db) return this.#db; - await this.initializeDB(); - return this.#db; - } - - /** - * Namespace wrapper of storeGetAsync - * @param {string} key // Path to get - */ - async storeGet(key) { - try { - await this.initializeDB(); - const resp = await this.#db.findOne({ key }); - if (resp) { - return resp[key]; - } - return null; - } catch (e) { - console.error(e); - return null; - } - } - - /** - * Namespace wrapper over storeSetAsync - * @param {string} key Path to set - * @param {*} value Data to set - */ - async storeSet(key, value) { - try { - await this.initializeDB(); - await this.#db.update( - { key }, - { [key]: value, key }, - { upsert: true }, - ); - } catch (e) { - console.error(e); - return undefined; - } - } - - /** - * Namespace wrapper over fsPromises methods - * @param {*} method The fsPromise method to call - * @param {*} path Path for the express call - * @param {...any} args Remaining parameters for the FS call - */ - async fs(method, path, ...args) { - if (taskNodeAdministered) { - return await genericHandler('fs', method, path, ...args); - } - return fsPromises[method](`${path}`, ...args); - } - - async fsStaking(method, path, ...args) { - if (taskNodeAdministered) { - return await genericHandler('fsStaking', method, path, ...args); - } - return fsPromises[method](`${path}`, ...args); - } - - async fsWriteStream(imagepath) { - if (taskNodeAdministered) { - return await genericHandler('fsWriteStream', imagepath); - } - const writer = fsPromises.createWriteStream(imagepath); - return writer; - } - - async fsReadStream(imagepath) { - if (taskNodeAdministered) { - return await genericHandler('fsReadStream', imagepath); - } - const file = fsPromises.readFileSync(imagepath); - return file; - } - - /** - * Namespace wrapper for getting current slots - */ - async getSlot() { - if (taskNodeAdministered) { - return await genericHandler('getCurrentSlot'); - } - return 100; - } - - async payloadSigning(body) { - if (taskNodeAdministered) { - return await genericHandler('signData', body); - } - const msg = new TextEncoder().encode(JSON.stringify(body)); - const signedMessage = nacl.sign( - msg, - this.#testingMainSystemAccount.secretKey, - ); - return await this.bs58Encode(signedMessage); - } - - async bs58Encode(data) { - return bs58.encode( - Buffer.from(data.buffer, data.byteOffset, data.byteLength), - ); - } - - async bs58Decode(data) { - return new Uint8Array(bs58.decode(data)); - } - - decodePayload(payload) { - return new TextDecoder().decode(payload); - } - - /** - * Namespace wrapper of storeGetAsync - * @param {string} signedMessage r // Path to get - */ - - async verifySignature(signedMessage, pubKey) { - if (taskNodeAdministered) { - return await genericHandler('verifySignedData', signedMessage, pubKey); - } - try { - const payload = nacl.sign.open( - await this.bs58Decode(signedMessage), - await this.bs58Decode(pubKey), - ); - if (!payload) return { error: 'Invalid signature' }; - return { data: this.decodePayload(payload) }; - } catch (e) { - console.error(e); - return { error: `Verification failed: ${e}` }; - } - } - - // async submissionOnChain(submitterKeypair, submission) { - // return await genericHandler( - // 'submissionOnChain', - // submitterKeypair, - // submission, - // ); - // } - - async stakeOnChain( - taskStateInfoPublicKey, - stakingAccKeypair, - stakePotAccount, - stakeAmount, - ) { - if (taskNodeAdministered) { - return await genericHandler( - 'stakeOnChain', - taskStateInfoPublicKey, - stakingAccKeypair, - stakePotAccount, - stakeAmount, - ); - } - this.#testingTaskState.stake_list[ - this.#testingStakingSystemAccount.publicKey.toBase58() - ] = stakeAmount; - } - - async claimReward(stakePotAccount, beneficiaryAccount, claimerKeypair) { - if (!taskNodeAdministered) { - console.log('Cannot call sendTransaction in testing mode'); - return; - } - return await genericHandler( - 'claimReward', - stakePotAccount, - beneficiaryAccount, - claimerKeypair, - ); - } - - async sendTransaction(serviceNodeAccount, beneficiaryAccount, amount) { - if (!taskNodeAdministered) { - console.log('Cannot call sendTransaction in testing mode'); - return; - } - return await genericHandler( - 'sendTransaction', - serviceNodeAccount, - beneficiaryAccount, - amount, - ); - } - - async getSubmitterAccount() { - if (taskNodeAdministered) { - const submitterAccountResp = await genericHandler('getSubmitterAccount'); - return Keypair.fromSecretKey( - Uint8Array.from(Object.values(submitterAccountResp._keypair.secretKey)), - ); - } - return this.#testingStakingSystemAccount; - } - - /** - * sendAndConfirmTransaction wrapper that injects mainSystemWallet as the first signer for paying the tx fees - * @param {connection} method // Receive method ["get", "post", "put", "delete"] - * @param {transaction} path // Endpoint path appended to namespace - * @param {Function} callback // Callback function on traffic receive - */ - async sendAndConfirmTransactionWrapper(transaction, signers) { - if (!taskNodeAdministered) { - console.log('Cannot call sendTransaction in testing mode'); - return; - } - const { blockhash } = await connection.getRecentBlockhash('finalized'); - transaction.recentBlockhash = blockhash; - transaction.feePayer = new PublicKey(MAIN_ACCOUNT_PUBKEY); - return await genericHandler( - 'sendAndConfirmTransactionWrapper', - transaction.serialize({ - requireAllSignatures: false, - verifySignatures: false, - }), - signers, - ); - } - - // async signArweave(transaction) { - // let tx = await genericHandler('signArweave', transaction.toJSON()); - // return arweave.transactions.fromRaw(tx); - // } - // async signEth(transaction) { - // return await genericHandler('signEth', transaction); - // } - async getTaskState(options) { - if (taskNodeAdministered) { - const response = await genericHandler('getTaskState', options); - if (response.error) { - console.log('Error in getting task state', response.error); - return null; - } - return response; - } - return this.#testingTaskState; - } - - async logMessage(level, message, action) { - switch (level) { - case LogLevel.Log: - console.log(message, action); - break; - case LogLevel.Warn: - console.warn(message, action); - break; - case LogLevel.Error: - console.error(message, action); - break; - default: - console.log( - `Invalid log level: ${level}. The log levels can be log, warn or error`, - ); - return false; - } - return true; - } - - /** - * This logger function is used to log the task erros , warnings and logs on desktop-node - * @param {level} enum // Receive method ["Log", "Warn", "Error"] - enum LogLevel { - Log = 'log', - Warn = 'warn', - Error = 'error', - } - * @param {message} string // log, error or warning message - * @returns {boolean} // true if the message is logged successfully otherwise false - */ - - async logger(level, message, action) { - if (taskNodeAdministered) { - return await genericHandler('logger', level, message, action); - } - return await this.logMessage(level, message, action); - } - - async auditSubmission(candidatePubkey, isValid, voterKeypair, round) { - if (taskNodeAdministered) { - return await genericHandler( - 'auditSubmission', - candidatePubkey, - isValid, - round, - ); - } - if ( - this.#testingTaskState.submissions_audit_trigger[round] - && this.#testingTaskState.submissions_audit_trigger[round][candidatePubkey] - ) { - this.#testingTaskState.submissions_audit_trigger[round][ - candidatePubkey - ].votes.push({ - is_valid: isValid, - voter: voterKeypair.pubKey.toBase58(), - slot: 100, - }); - } else { - this.#testingTaskState.submissions_audit_trigger[round] = { - [candidatePubkey]: { - trigger_by: this.#testingStakingSystemAccount.publicKey.toBase58(), - slot: 100, - votes: [], - }, - }; - } - } - - async distributionListAuditSubmission( - candidatePubkey, - isValid, - voterKeypair, - round, - ) { - if (taskNodeAdministered) { - return await genericHandler( - 'distributionListAuditSubmission', - candidatePubkey, - isValid, - round, - ); - } - if ( - this.#testingTaskState.distributions_audit_trigger[round] - && this.#testingTaskState.distributions_audit_trigger[round][ - candidatePubkey - ] - ) { - this.#testingTaskState.distributions_audit_trigger[round][ - candidatePubkey - ].votes.push({ - is_valid: isValid, - voter: voterKeypair.pubKey.toBase58(), - slot: 100, - }); - } else { - this.#testingTaskState.distributions_audit_trigger[round] = { - [candidatePubkey]: { - trigger_by: this.#testingStakingSystemAccount.publicKey.toBase58(), - slot: 100, - votes: [], - }, - }; - } - } - - async getRound() { - if (taskNodeAdministered) { - return await genericHandler('getRound'); - } - return 1; - } - - async payoutTrigger(round) { - if (taskNodeAdministered) { - return await genericHandler('payloadTrigger', round); - } else { - console.log( - 'Payout Trigger only handles possitive flows (Without audits)', - ); - const round = 1; - const submissionValAcc = this.#testingDistributionList[round][ - this.#testingStakingSystemAccount.toBase58() - ].submission_value; - this.#testingTaskState.available_balances = this.#testingDistributionList[round][submissionValAcc]; - } - } - - async uploadDistributionList(distributionList, round) { - if (taskNodeAdministered) { - return await genericHandler( - 'uploadDistributionList', - distributionList, - round, - ); - } - if (!this.#testingDistributionList[round]) { this.#testingDistributionList[round] = {}; } - - this.#testingDistributionList[round][ - this.#testingStakingSystemAccount.publicKey.toBase58() - ] = Buffer.from(JSON.stringify(distributionList)); - return true; - } - - async distributionListSubmissionOnChain(round) { - if (taskNodeAdministered) { - return await genericHandler('distributionListSubmissionOnChain', round); - } - if (!this.#testingTaskState.distribution_rewards_submission[round]) { this.#testingTaskState.distribution_rewards_submission[round] = {}; } - - this.#testingTaskState.distribution_rewards_submission[round][ - this.#testingStakingSystemAccount.publicKey.toBase58() - ] = { - submission_value: - this.#testingStakingSystemAccount.publicKey.toBase58(), - slot: 200, - round: 1, - }; - } - - async checkSubmissionAndUpdateRound(submissionValue = 'default', round) { - if (taskNodeAdministered) { - return await genericHandler( - 'checkSubmissionAndUpdateRound', - submissionValue, - round, - ); - } - if (!this.#testingTaskState.submissions[round]) { this.#testingTaskState.submissions[round] = {}; } - this.#testingTaskState.submissions[round][ - this.#testingStakingSystemAccount.publicKey.toBase58() - ] = { - submission_value: submissionValue, - slot: 100, - round, - }; - } - - async getProgramAccounts() { - if (taskNodeAdministered) { - return await genericHandler('getProgramAccounts'); - } - console.log('Cannot call getProgramAccounts in testing mode'); - } - - async defaultTaskSetup() { - if (taskNodeAdministered) { - return await genericHandler('defaultTaskSetup'); - } - if (this.#testingTaskState) return; - this.#testingMainSystemAccount = new Keypair(); - this.#testingStakingSystemAccount = new Keypair(); - this.#testingDistributionList = {}; - this.#testingTaskState = { - task_name: 'DummyTestState', - task_description: 'Dummy Task state for testing flow', - submissions: {}, - submissions_audit_trigger: {}, - total_bounty_amount: 10000000000, - bounty_amount_per_round: 1000000000, - total_stake_amount: 50000000000, - minimum_stake_amount: 5000000000, - available_balances: {}, - stake_list: {}, - round_time: 600, - starting_slot: 0, - audit_window: 200, - submission_window: 200, - distribution_rewards_submission: {}, - distributions_audit_trigger: {}, - }; - } - - async getRpcUrl() { - if (taskNodeAdministered) { - return await genericHandler('getRpcUrl'); - } - console.log('Cannot call getNodes in testing mode'); - } - - async getNodes(url) { - if (taskNodeAdministered) { - return await genericHandler('getNodes', url); - } - console.log('Cannot call getNodes in testing mode'); - } - - async getDistributionList(publicKey, round) { - if (taskNodeAdministered) { - const response = await genericHandler( - 'getDistributionList', - publicKey, - round, - ); - if (response.error) { - return null; - } - return response; - } - const submissionValAcc = this.#testingTaskState.distribution_rewards_submission[round][ - this.#testingStakingSystemAccount.publicKey.toBase58() - ].submission_value; - return this.#testingDistributionList[round][submissionValAcc]; - } - - async getTaskSubmissionInfo(round, forcefetch = false) { - if (taskNodeAdministered) { - const taskSubmissionInfo = await genericHandler( - 'getTaskSubmissionInfo', - round, - forcefetch - ); - if (!taskSubmissionInfo || taskSubmissionInfo.error) { - return null; - } - return taskSubmissionInfo; - } - // console.log(this.#testingTaskState) - return this.#testingTaskState; - } - - async validateAndVoteOnNodes(validate, round) { - console.log('******/ IN VOTING /******'); - let taskAccountDataJSON = null; - try { - taskAccountDataJSON = await this.getTaskSubmissionInfo(round); - } catch (error) { - console.error('Error in getting submissions for the round', error); - } - if (taskAccountDataJSON == null) { - console.log('No submissions found for the round', round); - return; - } - console.log( - `Fetching the submissions of round ${round}`, - taskAccountDataJSON.submissions[round], - ); - const submissions = taskAccountDataJSON.submissions[round]; - if (submissions == null) { - console.log(`No submisssions found in round ${round}`); - return `No submisssions found in round ${round}`; - } - const keys = Object.keys(submissions); - const values = Object.values(submissions); - const size = values.length; - console.log('Submissions from last round: ', keys, values, size); - let isValid; - const submitterAccountKeyPair = await this.getSubmitterAccount(); - const submitterPubkey = submitterAccountKeyPair.publicKey.toBase58(); - for (let i = 0; i < size; i++) { - const candidatePublicKey = keys[i]; - console.log('FOR CANDIDATE KEY', candidatePublicKey); - const candidateKeyPairPublicKey = new PublicKey(keys[i]); - if (candidatePublicKey == submitterPubkey && taskNodeAdministered) { - console.log('YOU CANNOT VOTE ON YOUR OWN SUBMISSIONS'); - } else { - try { - console.log( - 'SUBMISSION VALUE TO CHECK', - values[i].submission_value, - ); - isValid = await validate(values[i].submission_value, round); - console.log(`Voting ${isValid} to ${candidatePublicKey}`); - - if (isValid) { - // check for the submissions_audit_trigger , if it exists then vote true on that otherwise do nothing - const submissions_audit_trigger = taskAccountDataJSON.submissions_audit_trigger[round]; - console.log('SUBMIT AUDIT TRIGGER', submissions_audit_trigger); - // console.log( - // "CANDIDATE PUBKEY CHECK IN AUDIT TRIGGER", - // submissions_audit_trigger[candidatePublicKey] - // ); - if ( - submissions_audit_trigger - && submissions_audit_trigger[candidatePublicKey] - ) { - console.log('VOTING TRUE ON AUDIT'); - const response = await this.auditSubmission( - candidateKeyPairPublicKey, - isValid, - submitterAccountKeyPair, - round, - ); - console.log('RESPONSE FROM AUDIT FUNCTION', response); - } - } else if (isValid == false) { - // Call auditSubmission function and isValid is passed as false - console.log('RAISING AUDIT / VOTING FALSE'); - const response = await this.auditSubmission( - candidateKeyPairPublicKey, - isValid, - submitterAccountKeyPair, - round, - ); - console.log('RESPONSE FROM AUDIT FUNCTION', response); - } - } catch (err) { - console.log('ERROR IN ELSE CONDITION', err); - } - } - } - } - - async getTaskDistributionInfo(round) { - if (taskNodeAdministered) { - const taskDistributionInfo = await genericHandler( - 'getTaskDistributionInfo', - round, - ); - if (!taskDistributionInfo || taskDistributionInfo.error) { - return null; - } - return taskDistributionInfo; - } - return this.#testingTaskState; - } - async validateAndVoteOnDistributionList( - validateDistribution, - round, - isPreviousRoundFailed = false - ) { - console.log("******/ IN VOTING OF DISTRIBUTION LIST /******"); - let tasknodeVersionSatisfied = false; - const taskNodeVersion = await this.getTaskNodeVersion(); - if (semver.gte(taskNodeVersion, "1.11.19")) { - tasknodeVersionSatisfied = true; - } - let taskAccountDataJSON = null; - try { - taskAccountDataJSON = await this.getTaskDistributionInfo(round); - } catch (error) { - console.error("Error in getting distributions for the round", error); - } - if (taskAccountDataJSON == null) { - console.log("No distribution submissions found for the round", round); - return; - } - const submissions = - taskAccountDataJSON?.distribution_rewards_submission[round]; - if ( - submissions == null || - submissions == undefined || - submissions.length == 0 - ) { - console.log(`No submisssions found in round ${round}`); - return `No submisssions found in round ${round}`; - } else { - const keys = Object.keys(submissions); - const values = Object.values(submissions); - const size = values.length; - let isValid; - const submitterAccountKeyPair = await this.getSubmitterAccount(); - const submitterPubkey = submitterAccountKeyPair.publicKey.toBase58(); - const selectedNode = await this.nodeSelectionDistributionList( - round, - isPreviousRoundFailed - ); - console.log("SELECTED NODE FOR AUDIT", selectedNode); - if (selectedNode == submitterPubkey) { - console.log("YOU CANNOT VOTE ON YOUR OWN DISTRIBUTION SUBMISSIONS"); - return; - } - for (let i = 0; i < size; i++) { - let candidatePublicKey = keys[i]; - let candidateKeyPairPublicKey = new PublicKey(keys[i]); - try { - console.log("VOTING ON DISTRIBUTION LIST"); - isValid = await validateDistribution( - values[i].submission_value, - round - ); - - if (isValid) { - // check for the submissions_audit_trigger , if it exists then vote true on that otherwise do nothing - const distributions_audit_trigger = - taskAccountDataJSON.distributions_audit_trigger[round]; - if ( - distributions_audit_trigger && - distributions_audit_trigger[candidatePublicKey] - ) { - console.log("VOTING TRUE ON DISTRIBUTION AUDIT"); - const response = await this.distributionListAuditSubmission( - candidateKeyPairPublicKey, - isValid, - submitterAccountKeyPair, - round - ); - console.log( - "RESPONSE FROM DISTRIBUTION AUDIT FUNCTION", - response - ); - } - } else if (isValid == false && tasknodeVersionSatisfied) { - // Call auditSubmission function and isValid is passed as false - console.log("RAISING AUDIT / VOTING FALSE ON DISTRIBUTION"); - const response = await this.distributionListAuditSubmission( - candidateKeyPairPublicKey, - isValid, - submitterAccountKeyPair, - round - ); - console.log("RESPONSE FROM DISTRIBUTION AUDIT FUNCTION", response); - } - } catch (err) { - console.log("ERROR IN ELSE CONDITION FOR DISTRIBUTION", err); - } - } - } - } - - async getTaskNodeVersion() { - if (taskNodeAdministered) { - try { - return await genericHandler("getTaskNodeVersion"); - } catch (error) { - console.error("Error getting task node version", error); - return; - } - } else { - return "1.11.19"; - } - } - - async getTaskLevelDBPath() { - if (taskNodeAdministered) { - return await genericHandler('getTaskLevelDBPath'); - } - return './KOIIDB'; - } - - async getBasePath() { - if (taskNodeAdministered) { - const basePath = (await namespaceWrapper.getTaskLevelDBPath()).replace( - '/KOIIDB', - '', - ); - return basePath; - } - return './'; - } - - async getAverageSlotTime() { - if (taskNodeAdministered) { - try { - return await genericHandler('getAverageSlotTime'); - } catch (error) { - console.error('Error getting average slot time', error); - return 400; - } - } else { - return 400; - } - } - - async nodeSelectionDistributionList(round, isPreviousFailed) { - let taskAccountDataJSON = null; - try { - taskAccountDataJSON = await this.getTaskSubmissionInfo(round,true); - } catch (error) { - console.error('Task submission not found', error); - return; - } - - if (taskAccountDataJSON == null) { - console.error('Task state not found'); - return; - } - console.log('EXPECTED ROUND', round); - - const submissions = taskAccountDataJSON.submissions[round]; - if (submissions == null) { - console.log('No submisssions found in N-1 round'); - return 'No submisssions found in N-1 round'; - } - // getting last 3 submissions for the rounds - let keys; - const latestRounds = [round, round - 1, round - 2].filter((r) => r >= 0); - - const promises = latestRounds.map(async (r) => { - if (r == round) { - return new Set(Object.keys(submissions)); - } - let roundSubmissions = null; - try { - roundSubmissions = await this.getTaskSubmissionInfo(r, true); - if (roundSubmissions && roundSubmissions.submissions[r]) { - return new Set(Object.keys(roundSubmissions.submissions[r])); - } - } catch (error) { - console.error('Error in getting submissions for the round', error); - } - return new Set(); - }); - - const keySets = await Promise.all(promises); - - // Find the keys present in all the rounds - keys = keySets.length > 0 - ? [...keySets[0]].filter((key) => keySets.every((set) => set.has(key))) - : []; - if (keys.length == 0) { - console.log('No common keys found in last 3 rounds'); - keys = Object.keys(submissions); - } - console.log('KEYS', keys.length); - const values = keys.map((key) => submissions[key]); - - let size = keys.length; - console.log('Submissions from N-2 round: ', size); - - // Check the keys i.e if the submitter shall be excluded or not - try { - const distributionData = await this.getTaskDistributionInfo(round); - const audit_record = distributionData?.distributions_audit_record; - if (audit_record && audit_record[round] == 'PayoutFailed') { - console.log('ROUND DATA', audit_record[round]); - console.log( - 'SUBMITTER LIST', - distributionData.distribution_rewards_submission[round], - ); - const submitterList = distributionData.distribution_rewards_submission[round]; - const submitterKeys = Object.keys(submitterList); - console.log('SUBMITTER KEYS', submitterKeys); - const submitterSize = submitterKeys.length; - console.log('SUBMITTER SIZE', submitterSize); - - for (let j = 0; j < submitterSize; j++) { - console.log('SUBMITTER KEY CANDIDATE', submitterKeys[j]); - const id = keys.indexOf(submitterKeys[j]); - console.log('ID', id); - if (id != -1) { - keys.splice(id, 1); - values.splice(id, 1); - size--; - } - } - - console.log('KEYS FOR HASH CALC', keys.length); - } - } catch (error) { - console.log('Error in getting distribution data', error); - } - - // calculating the digest - - const ValuesString = JSON.stringify(values); - - const hashDigest = createHash('sha256') - .update(ValuesString) - .digest('hex'); - - console.log('HASH DIGEST', hashDigest); - - // function to calculate the score - const calculateScore = (str = '') => str.split('').reduce((acc, val) => acc + val.charCodeAt(0), 0); - - // function to compare the ASCII values - - const compareASCII = (str1, str2) => { - const firstScore = calculateScore(str1); - const secondScore = calculateScore(str2); - return Math.abs(firstScore - secondScore); - }; - - // loop through the keys and select the one with higest score - - const selectedNode = { - score: 0, - pubkey: '', - }; - let score = 0; - if (isPreviousFailed) { - let leastScore = -Infinity; - let secondLeastScore = -Infinity; - for (let i = 0; i < size; i++) { - const candidateSubmissionJson = {}; - candidateSubmissionJson[keys[i]] = values[i]; - const candidateSubmissionString = JSON.stringify( - candidateSubmissionJson, - ); - const candidateSubmissionHash = createHash('sha256') - .update(candidateSubmissionString) - .digest('hex'); - const candidateScore = compareASCII( - hashDigest, - candidateSubmissionHash, - ); - if (candidateScore > leastScore) { - secondLeastScore = leastScore; - leastScore = candidateScore; - } else if (candidateScore > secondLeastScore) { - secondLeastScore = candidateScore; - selectedNode.score = candidateScore; - selectedNode.pubkey = keys[i]; - } - } - } else { - for (let i = 0; i < size; i++) { - const candidateSubmissionJson = {}; - candidateSubmissionJson[keys[i]] = values[i]; - const candidateSubmissionString = JSON.stringify( - candidateSubmissionJson, - ); - const candidateSubmissionHash = createHash('sha256') - .update(candidateSubmissionString) - .digest('hex'); - const candidateScore = compareASCII( - hashDigest, - candidateSubmissionHash, - ); - // console.log('CANDIDATE SCORE', candidateScore); - if (candidateScore > score) { - score = candidateScore; - selectedNode.score = candidateScore; - selectedNode.pubkey = keys[i]; - } - } - } - - console.log('SELECTED NODE OBJECT', selectedNode); - return selectedNode.pubkey; - } - - async selectAndGenerateDistributionList( - submitDistributionList, - round, - isPreviousRoundFailed, - ) { - console.log('SelectAndGenerateDistributionList called'); - const selectedNode = await this.nodeSelectionDistributionList( - round, - isPreviousRoundFailed, - ); - console.log('Selected Node', selectedNode); - const submitPubKey = await this.getSubmitterAccount(); - if ( - selectedNode == undefined - || selectedNode == '' - || submitPubKey == undefined - ) { return; } - if (selectedNode == submitPubKey?.publicKey.toBase58()) { - await submitDistributionList(round); - const taskState = await this.getTaskState({}); - if (taskState == null) { - console.error('Task state not found'); - return; - } - const avgSlotTime = await this.getAverageSlotTime(); - if (avgSlotTime == null) { - console.error('Avg slot time not found'); - return; - } - setTimeout(async () => { - await this.payoutTrigger(round); - }, (taskState.audit_window + taskState.submission_window) * avgSlotTime); - } - } - - getMainAccountPubkey() { - if (taskNodeAdministered) { - return MAIN_ACCOUNT_PUBKEY; - } - return this.#testingMainSystemAccount.publicKey.toBase58(); - } -} - -async function genericHandler(...args) { - try { - let response = await axios.post(BASE_ROOT_URL, { - args, - taskId: TASK_ID, - secret: SECRET_KEY, - }); - if (response.status == 200) return response.data.response; - else { - console.error(response.status, response.data); - return null; - } - } catch (err) { - const responseData = err?.response?.data?.message; - if ((args[0] === 'getTaskSubmissionInfo' || args[0] === 'getTaskDistributionInfo') && - responseData && typeof responseData === 'string' && responseData.includes('Task does not have any')) { - console.log(`Error in genericHandler: "${args[0]}"`, err.message); - console.log(err?.response?.data); - }else{ - console.error(`Error in genericHandler: "${args[0]}"`, err.message); - console.error(err?.response?.data); - return { error: err }; - } - } -} - -const namespaceWrapper = new NamespaceWrapper(); -if (taskNodeAdministered) { - namespaceWrapper.getRpcUrl().then((rpcUrl) => { - console.log(rpcUrl, 'RPC URL'); - connection = new Connection(rpcUrl, 'confirmed'); - }); -} -module.exports = { - namespaceWrapper, - taskNodeAdministered, // Boolean flag indicating that the task is being ran in active mode (Task node supervised), or development (testing) mode - app, // The initialized express app to be used to register endpoints - TASK_ID, // This will be the PORT on which the this task is expected to run the express server coming from the task node running this task. As all communication via the task node and this task will be done on this port. - MAIN_ACCOUNT_PUBKEY, // This will be the secret used to authenticate with task node running this task. - SECRET_KEY, // This will be the secret used by the task to authenticate with task node running this task. - K2_NODE_URL, // This will be K2 url being used by the task node, possible values are 'https://k2-testnet.koii.live' | 'https://k2-devnet.koii.live' | 'http://localhost:8899' - SERVICE_URL, // This will be public task node endpoint (Or local if it doesn't have any) of the task node running this task. - STAKE, // This will be stake of the task node running this task, can be double checked with the task state and staking public key. - TASK_NODE_PORT, // This will be the port used by task node as the express server port, so it can be used by the task for the communication with the task node - _server, // Express server object -}; diff --git a/Lesson 1/EZ-testing-task/coreLogic.js b/Lesson 1/EZ-testing-task/coreLogic.js index a7d7165..64b3e7f 100644 --- a/Lesson 1/EZ-testing-task/coreLogic.js +++ b/Lesson 1/EZ-testing-task/coreLogic.js @@ -1,4 +1,4 @@ -const { namespaceWrapper } = require('./_koiiNode/koiiNode'); +const { namespaceWrapper } = require('@_koii/namespace-wrapper'); const task = require('./task'); class CoreLogic { diff --git a/Lesson 1/EZ-testing-task/index.js b/Lesson 1/EZ-testing-task/index.js index 8f18299..caf6e98 100644 --- a/Lesson 1/EZ-testing-task/index.js +++ b/Lesson 1/EZ-testing-task/index.js @@ -3,7 +3,7 @@ const { namespaceWrapper, taskNodeAdministered, app, -} = require('./_koiiNode/koiiNode'); +} = require('@_koii/namespace-wrapper'); if (app) { // Write your Express Endpoints here. @@ -59,13 +59,13 @@ async function setup() { /* GUIDE TO CALLS K2 FUNCTIONS MANUALLY - If you wish to do the development by avoiding the timers then you can do the intended calls to K2 - directly using these function calls. + If you wish to do the development by avoiding the timers then you can do the intended calls to K2 + directly using these function calls. To disable timers please set the TIMERS flag in task-node ENV to disable NOTE : K2 will still have the windows to accept the submission value, audit, so you are expected - to make calls in the intended slots of your round time. + to make calls in the intended slots of your round time. */ diff --git a/Lesson 1/EZ-testing-task/package.json b/Lesson 1/EZ-testing-task/package.json index 71ddb4e..4622ac5 100644 --- a/Lesson 1/EZ-testing-task/package.json +++ b/Lesson 1/EZ-testing-task/package.json @@ -16,6 +16,8 @@ "license": "ISC", "dependencies": { "@_koi/web3.js": "^0.0.6", + "@_koii/namespace-wrapper": "^1.0.1", + "@_koii/storage-task-sdk": "^1.2.4", "axios": "^0.27.2", "body-parser": "^1.20.2", "cross-spawn": "^7.0.3", diff --git a/Lesson 1/EZ-testing-task/task/audit.js b/Lesson 1/EZ-testing-task/task/audit.js index 9f20d12..809cbfb 100644 --- a/Lesson 1/EZ-testing-task/task/audit.js +++ b/Lesson 1/EZ-testing-task/task/audit.js @@ -1,4 +1,4 @@ -const { namespaceWrapper } = require('../_koiiNode/koiiNode'); +const { namespaceWrapper } = require('@_koii/namespace-wrapper'); class Audit { /** diff --git a/Lesson 1/EZ-testing-task/task/distribution.js b/Lesson 1/EZ-testing-task/task/distribution.js index e5b6426..ebb40de 100644 --- a/Lesson 1/EZ-testing-task/task/distribution.js +++ b/Lesson 1/EZ-testing-task/task/distribution.js @@ -1,4 +1,4 @@ -const { namespaceWrapper } = require('../_koiiNode/koiiNode'); +const { namespaceWrapper } = require('@_koii/namespace-wrapper'); class Distribution { /** diff --git a/Lesson 1/EZ-testing-task/task/submission.js b/Lesson 1/EZ-testing-task/task/submission.js index 586ac90..88ac8f4 100644 --- a/Lesson 1/EZ-testing-task/task/submission.js +++ b/Lesson 1/EZ-testing-task/task/submission.js @@ -1,4 +1,4 @@ -const { namespaceWrapper } = require('../_koiiNode/koiiNode'); +const { namespaceWrapper } = require('@_koii/namespace-wrapper'); class Submission { /** * Executes your task, optionally storing the result. diff --git a/Lesson 1/EZ-testing-task/tests/main.test.js b/Lesson 1/EZ-testing-task/tests/main.test.js index ba05a7f..1756f01 100644 --- a/Lesson 1/EZ-testing-task/tests/main.test.js +++ b/Lesson 1/EZ-testing-task/tests/main.test.js @@ -1,5 +1,5 @@ const { coreLogic } = require('../coreLogic'); -const { namespaceWrapper, _server } = require('../_koiiNode/koiiNode'); +const { namespaceWrapper, _server } = require('@_koii/namespace-wrapper'); const Joi = require('joi'); const axios = require('axios'); beforeAll(async () => { diff --git a/Lesson 2/file-sharing/after/_koiiNode/koiiNode.js b/Lesson 2/file-sharing/after/_koiiNode/koiiNode.js deleted file mode 100644 index 852bdb6..0000000 --- a/Lesson 2/file-sharing/after/_koiiNode/koiiNode.js +++ /dev/null @@ -1,1156 +0,0 @@ -const { default: axios } = require('axios'); -const { createHash } = require('crypto'); - -const { Connection, PublicKey, Keypair } = require('@_koi/web3.js'); - -const Datastore = require('nedb-promises'); -const fsPromises = require('fs/promises'); -const bs58 = require('bs58'); -const nacl = require('tweetnacl'); - -/****************************************** init.js ***********************************/ - -const express = require('express'); -// Only used for testing purposes, in production the env will be injected by tasknode -require('dotenv').config(); -const bodyParser = require('body-parser'); -/** - * This will be the name of the current task as coming from the task node running this task. - */ -const TASK_NAME = process.argv[2] || 'Local'; -/** - * This will be the id of the current task as coming from the task node running this task. - */ -const TASK_ID = process.argv[3]; -/** - * This will be the PORT on which the this task is expected to run the express server coming from the task node running this task. - * As all communication via the task node and this task will be done on this port. - */ -const EXPRESS_PORT = process.argv[4] || 10000; - -const LogLevel = { - Log: 'log', - Warn: 'warn', - Error: 'error', -}; - -// Not used anymore -// const NODE_MODE = process.argv[5]; - -/** - * This will be the main account public key in string format of the task node running this task. - */ -const MAIN_ACCOUNT_PUBKEY = process.argv[6]; -/** - * This will be the secret used by the task to authenticate with task node running this task. - */ -const SECRET_KEY = process.argv[7]; -/** - * This will be K2 url being used by the task node, possible values are 'https://k2-testnet.koii.live' | 'https://k2-devnet.koii.live' | 'http://localhost:8899' - */ -const K2_NODE_URL = process.argv[8] || 'https://k2-testnet.koii.live'; -/** - * This will be public task node endpoint (Or local if it doesn't have any) of the task node running this task. - */ -const SERVICE_URL = process.argv[9]; -/** - * This will be stake of the task node running this task, can be double checked with the task state and staking public key. - */ -const STAKE = Number(process.argv[10]); -/** - * This will be the port used by task node as the express server port, so it can be used by the task for the communication with the task node - */ -const TASK_NODE_PORT = Number(process.argv[11]); - -const app = express(); - -console.log('SETTING UP EXPRESS'); - -app.use(bodyParser.urlencoded({ extended: false })); - -app.use(bodyParser.json()); - -app.use((req, res, next) => { - res.setHeader('Access-Control-Allow-Origin', '*'); - res.setHeader( - 'Access-Control-Allow-Methods', - 'GET, POST, PUT, PATCH, DELETE', - ); - res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); - res.setHeader('Access-Control-Allow-Credentials', false); - if (req.method === 'OPTIONS') - // if is preflight(OPTIONS) then response status 204(NO CONTENT) - return res.send(204); - next(); -}); - -app.get('/', (req, res) => { - res.send('Hello World!'); -}); - -const _server = app.listen(EXPRESS_PORT, () => { - console.log(`${TASK_NAME} listening on port ${EXPRESS_PORT}`); -}); - -/****************************************** NamespaceWrapper.js ***********************************/ - -const taskNodeAdministered = !!TASK_ID; -const BASE_ROOT_URL = `http://localhost:${TASK_NODE_PORT}/namespace-wrapper`; -let connection; - -class NamespaceWrapper { - #db; - #testingMainSystemAccount; - #testingStakingSystemAccount; - #testingTaskState; - #testingDistributionList; - - constructor() { - if (taskNodeAdministered) { - this.initializeDB(); - } else { - this.#db = Datastore.create('./localKOIIDB.db'); - this.defaultTaskSetup(); - } - } - - async initializeDB() { - if (this.#db) return; - try { - if (taskNodeAdministered) { - const path = await this.getTaskLevelDBPath(); - this.#db = Datastore.create(path); - } else { - this.#db = Datastore.create('./localKOIIDB.db'); - } - } catch (e) { - this.#db = Datastore.create(`../namespace/${TASK_ID}/KOIILevelDB.db`); - } - } - - async getDb() { - if (this.#db) return this.#db; - await this.initializeDB(); - return this.#db; - } - /** - * Namespace wrapper of storeGetAsync - * @param {string} key // Path to get - */ - async storeGet(key) { - try { - await this.initializeDB(); - const resp = await this.#db.findOne({ key: key }); - if (resp) { - return resp[key]; - } else { - return null; - } - } catch (e) { - console.error(e); - return null; - } - } - /** - * Namespace wrapper over storeSetAsync - * @param {string} key Path to set - * @param {*} value Data to set - */ - async storeSet(key, value) { - try { - await this.initializeDB(); - await this.#db.update( - { key: key }, - { [key]: value, key }, - { upsert: true }, - ); - } catch (e) { - console.error(e); - return undefined; - } - } - - /** - * Namespace wrapper over fsPromises methods - * @param {*} method The fsPromise method to call - * @param {*} path Path for the express call - * @param {...any} args Remaining parameters for the FS call - */ - async fs(method, path, ...args) { - if (taskNodeAdministered) { - return await genericHandler('fs', method, path, ...args); - } else { - return fsPromises[method](`${path}`, ...args); - } - } - async fsStaking(method, path, ...args) { - if (taskNodeAdministered) { - return await genericHandler('fsStaking', method, path, ...args); - } else { - return fsPromises[method](`${path}`, ...args); - } - } - - async fsWriteStream(imagepath) { - if (taskNodeAdministered) { - return await genericHandler('fsWriteStream', imagepath); - } else { - const writer = createWriteStream(imagepath); - return writer; - } - } - async fsReadStream(imagepath) { - if (taskNodeAdministered) { - return await genericHandler('fsReadStream', imagepath); - } else { - const file = readFileSync(imagepath); - return file; - } - } - - /** - * Namespace wrapper for getting current slots - */ - async getSlot() { - if (taskNodeAdministered) { - return await genericHandler('getCurrentSlot'); - } else { - return 100; - } - } - - async payloadSigning(body) { - if (taskNodeAdministered) { - return await genericHandler('signData', body); - } else { - const msg = new TextEncoder().encode(JSON.stringify(body)); - const signedMessage = nacl.sign( - msg, - this.#testingMainSystemAccount.secretKey, - ); - return await this.bs58Encode(signedMessage); - } - } - - async bs58Encode(data) { - return bs58.encode( - Buffer.from(data.buffer, data.byteOffset, data.byteLength), - ); - } - - async bs58Decode(data) { - return new Uint8Array(bs58.decode(data)); - } - - decodePayload(payload) { - return new TextDecoder().decode(payload); - } - - /** - * Namespace wrapper of storeGetAsync - * @param {string} signedMessage r // Path to get - */ - - async verifySignature(signedMessage, pubKey) { - if (taskNodeAdministered) { - return await genericHandler('verifySignedData', signedMessage, pubKey); - } else { - try { - const payload = nacl.sign.open( - await this.bs58Decode(signedMessage), - await this.bs58Decode(pubKey), - ); - if (!payload) return { error: 'Invalid signature' }; - return { data: this.decodePayload(payload) }; - } catch (e) { - console.error(e); - return { error: `Verification failed: ${e}` }; - } - } - } - - // async submissionOnChain(submitterKeypair, submission) { - // return await genericHandler( - // 'submissionOnChain', - // submitterKeypair, - // submission, - // ); - // } - - async stakeOnChain( - taskStateInfoPublicKey, - stakingAccKeypair, - stakePotAccount, - stakeAmount, - ) { - if (taskNodeAdministered) { - return await genericHandler( - 'stakeOnChain', - taskStateInfoPublicKey, - stakingAccKeypair, - stakePotAccount, - stakeAmount, - ); - } else { - this.#testingTaskState.stake_list[ - this.#testingStakingSystemAccount.publicKey.toBase58() - ] = stakeAmount; - } - } - async claimReward(stakePotAccount, beneficiaryAccount, claimerKeypair) { - if (!taskNodeAdministered) { - console.log('Cannot call sendTransaction in testing mode'); - return; - } - return await genericHandler( - 'claimReward', - stakePotAccount, - beneficiaryAccount, - claimerKeypair, - ); - } - async sendTransaction(serviceNodeAccount, beneficiaryAccount, amount) { - if (!taskNodeAdministered) { - console.log('Cannot call sendTransaction in testing mode'); - return; - } - return await genericHandler( - 'sendTransaction', - serviceNodeAccount, - beneficiaryAccount, - amount, - ); - } - - async getSubmitterAccount() { - if (taskNodeAdministered) { - const submitterAccountResp = await genericHandler('getSubmitterAccount'); - return Keypair.fromSecretKey( - Uint8Array.from(Object.values(submitterAccountResp._keypair.secretKey)), - ); - } else { - return this.#testingStakingSystemAccount; - } - } - - /** - * sendAndConfirmTransaction wrapper that injects mainSystemWallet as the first signer for paying the tx fees - * @param {connection} method // Receive method ["get", "post", "put", "delete"] - * @param {transaction} path // Endpoint path appended to namespace - * @param {Function} callback // Callback function on traffic receive - */ - async sendAndConfirmTransactionWrapper(transaction, signers) { - if (!taskNodeAdministered) { - console.log('Cannot call sendTransaction in testing mode'); - return; - } - const blockhash = (await connection.getRecentBlockhash('finalized')) - .blockhash; - transaction.recentBlockhash = blockhash; - transaction.feePayer = new PublicKey(MAIN_ACCOUNT_PUBKEY); - return await genericHandler( - 'sendAndConfirmTransactionWrapper', - transaction.serialize({ - requireAllSignatures: false, - verifySignatures: false, - }), - signers, - ); - } - - // async signArweave(transaction) { - // let tx = await genericHandler('signArweave', transaction.toJSON()); - // return arweave.transactions.fromRaw(tx); - // } - // async signEth(transaction) { - // return await genericHandler('signEth', transaction); - // } - async getTaskState(options) { - if (taskNodeAdministered) { - const response = await genericHandler('getTaskState', options); - if (response.error) { - console.log('Error in getting task state', response.error); - return null; - } - return response; - } else { - return this.#testingTaskState; - } - } - - async logMessage(level, message, action) { - switch (level) { - case LogLevel.Log: - console.log(message, action); - break; - case LogLevel.Warn: - console.warn(message, action); - break; - case LogLevel.Error: - console.error(message, action); - break; - default: - console.log( - `Invalid log level: ${level}. The log levels can be log, warn or error`, - ); - return false; - } - return true; - } - - /** - * This logger function is used to log the task erros , warnings and logs on desktop-node - * @param {level} enum // Receive method ["Log", "Warn", "Error"] - enum LogLevel { - Log = 'log', - Warn = 'warn', - Error = 'error', - } - * @param {message} string // log, error or warning message - * @returns {boolean} // true if the message is logged successfully otherwise false - */ - - async logger(level, message, action) { - if (taskNodeAdministered) { - return await genericHandler('logger', level, message, action); - } else { - return await this.logMessage(level, message, action); - } - } - - async auditSubmission(candidatePubkey, isValid, voterKeypair, round) { - if (taskNodeAdministered) { - return await genericHandler( - 'auditSubmission', - candidatePubkey, - isValid, - round, - ); - } else { - if ( - this.#testingTaskState.submissions_audit_trigger[round] && - this.#testingTaskState.submissions_audit_trigger[round][candidatePubkey] - ) { - this.#testingTaskState.submissions_audit_trigger[round][ - candidatePubkey - ].votes.push({ - is_valid: isValid, - voter: voterKeypair.pubKey.toBase58(), - slot: 100, - }); - } else { - this.#testingTaskState.submissions_audit_trigger[round] = { - [candidatePubkey]: { - trigger_by: this.#testingStakingSystemAccount.publicKey.toBase58(), - slot: 100, - votes: [], - }, - }; - } - } - } - - async distributionListAuditSubmission( - candidatePubkey, - isValid, - voterKeypair, - round, - ) { - if (taskNodeAdministered) { - return await genericHandler( - 'distributionListAuditSubmission', - candidatePubkey, - isValid, - round, - ); - } else { - if ( - this.#testingTaskState.distributions_audit_trigger[round] && - this.#testingTaskState.distributions_audit_trigger[round][ - candidatePubkey - ] - ) { - this.#testingTaskState.distributions_audit_trigger[round][ - candidatePubkey - ].votes.push({ - is_valid: isValid, - voter: voterKeypair.pubKey.toBase58(), - slot: 100, - }); - } else { - this.#testingTaskState.distributions_audit_trigger[round] = { - [candidatePubkey]: { - trigger_by: this.#testingStakingSystemAccount.publicKey.toBase58(), - slot: 100, - votes: [], - }, - }; - } - } - } - - async getRound() { - if (taskNodeAdministered) { - return await genericHandler('getRound'); - } else { - return 1; - } - } - - async payoutTrigger(round) { - if (taskNodeAdministered) { - return await genericHandler('payloadTrigger', round); - } else { - console.log( - 'Payout Trigger only handles possitive flows (Without audits)', - ); - let round = 1; - const submissionValAcc = - this.#testingDistributionList[round][ - this.#testingStakingSystemAccount.toBase58() - ].submission_value; - this.#testingTaskState.available_balances = - this.#testingDistributionList[round][submissionValAcc]; - } - } - - async uploadDistributionList(distributionList, round) { - if (taskNodeAdministered) { - return await genericHandler( - 'uploadDistributionList', - distributionList, - round, - ); - } else { - if (!this.#testingDistributionList[round]) - this.#testingDistributionList[round] = {}; - - this.#testingDistributionList[round][ - this.#testingStakingSystemAccount.publicKey.toBase58() - ] = Buffer.from(JSON.stringify(distributionList)); - return true; - } - } - - async distributionListSubmissionOnChain(round) { - if (taskNodeAdministered) { - return await genericHandler('distributionListSubmissionOnChain', round); - } else { - if (!this.#testingTaskState.distribution_rewards_submission[round]) - this.#testingTaskState.distribution_rewards_submission[round] = {}; - - this.#testingTaskState.distribution_rewards_submission[round][ - this.#testingStakingSystemAccount.publicKey.toBase58() - ] = { - submission_value: - this.#testingStakingSystemAccount.publicKey.toBase58(), - slot: 200, - round: 1, - }; - } - } - - async checkSubmissionAndUpdateRound(submissionValue = 'default', round) { - if (taskNodeAdministered) { - return await genericHandler( - 'checkSubmissionAndUpdateRound', - submissionValue, - round, - ); - } else { - if (!this.#testingTaskState.submissions[round]) - this.#testingTaskState.submissions[round] = {}; - this.#testingTaskState.submissions[round][ - this.#testingStakingSystemAccount.publicKey.toBase58() - ] = { - submission_value: submissionValue, - slot: 100, - round, - }; - } - } - async getProgramAccounts() { - if (taskNodeAdministered) { - return await genericHandler('getProgramAccounts'); - } else { - console.log('Cannot call getProgramAccounts in testing mode'); - } - } - async defaultTaskSetup() { - if (taskNodeAdministered) { - return await genericHandler('defaultTaskSetup'); - } else { - if (this.#testingTaskState) return; - this.#testingMainSystemAccount = new Keypair(); - this.#testingStakingSystemAccount = new Keypair(); - this.#testingDistributionList = {}; - this.#testingTaskState = { - task_name: 'DummyTestState', - task_description: 'Dummy Task state for testing flow', - submissions: {}, - submissions_audit_trigger: {}, - total_bounty_amount: 10000000000, - bounty_amount_per_round: 1000000000, - total_stake_amount: 50000000000, - minimum_stake_amount: 5000000000, - available_balances: {}, - stake_list: {}, - round_time: 600, - starting_slot: 0, - audit_window: 200, - submission_window: 200, - distribution_rewards_submission: {}, - distributions_audit_trigger: {}, - }; - } - } - async getRpcUrl() { - if (taskNodeAdministered) { - return await genericHandler('getRpcUrl'); - } else { - console.log('Cannot call getNodes in testing mode'); - } - } - async getNodes(url) { - if (taskNodeAdministered) { - return await genericHandler('getNodes', url); - } else { - console.log('Cannot call getNodes in testing mode'); - } - } - - async getDistributionList(publicKey, round) { - if (taskNodeAdministered) { - const response = await genericHandler( - 'getDistributionList', - publicKey, - round, - ); - if (response.error) { - return null; - } - return response; - } else { - const submissionValAcc = - this.#testingTaskState.distribution_rewards_submission[round][ - this.#testingStakingSystemAccount.publicKey.toBase58() - ].submission_value; - return this.#testingDistributionList[round][submissionValAcc]; - } - } - - async getTaskSubmissionInfo(round) { - if (taskNodeAdministered) { - const taskSubmissionInfo = await genericHandler( - 'getTaskSubmissionInfo', - round, - ); - if (taskSubmissionInfo.error) { - return null; - } - return taskSubmissionInfo; - } else { - // console.log(this.#testingTaskState) - return this.#testingTaskState; - } - } - - async validateAndVoteOnNodes(validate, round) { - console.log('******/ IN VOTING /******'); - let taskAccountDataJSON = null; - try { - taskAccountDataJSON = await this.getTaskSubmissionInfo(round); - } catch (error) { - console.error('Error in getting submissions for the round', error); - } - if (taskAccountDataJSON == null) { - console.log('No submissions found for the round', round); - return; - } - console.log( - `Fetching the submissions of round ${round}`, - taskAccountDataJSON.submissions[round], - ); - const submissions = taskAccountDataJSON.submissions[round]; - if (submissions == null) { - console.log(`No submisssions found in round ${round}`); - return `No submisssions found in round ${round}`; - } else { - const keys = Object.keys(submissions); - const values = Object.values(submissions); - const size = values.length; - console.log('Submissions from last round: ', keys, values, size); - let isValid; - const submitterAccountKeyPair = await this.getSubmitterAccount(); - const submitterPubkey = submitterAccountKeyPair.publicKey.toBase58(); - for (let i = 0; i < size; i++) { - let candidatePublicKey = keys[i]; - console.log('FOR CANDIDATE KEY', candidatePublicKey); - let candidateKeyPairPublicKey = new PublicKey(keys[i]); - if (candidatePublicKey == submitterPubkey && taskNodeAdministered) { - console.log('YOU CANNOT VOTE ON YOUR OWN SUBMISSIONS'); - } else { - try { - console.log( - 'SUBMISSION VALUE TO CHECK', - values[i].submission_value, - ); - isValid = await validate(values[i].submission_value, round); - console.log(`Voting ${isValid} to ${candidatePublicKey}`); - - if (isValid) { - // check for the submissions_audit_trigger , if it exists then vote true on that otherwise do nothing - const submissions_audit_trigger = - taskAccountDataJSON.submissions_audit_trigger[round]; - console.log('SUBMIT AUDIT TRIGGER', submissions_audit_trigger); - // console.log( - // "CANDIDATE PUBKEY CHECK IN AUDIT TRIGGER", - // submissions_audit_trigger[candidatePublicKey] - // ); - if ( - submissions_audit_trigger && - submissions_audit_trigger[candidatePublicKey] - ) { - console.log('VOTING TRUE ON AUDIT'); - const response = await this.auditSubmission( - candidateKeyPairPublicKey, - isValid, - submitterAccountKeyPair, - round, - ); - console.log('RESPONSE FROM AUDIT FUNCTION', response); - } - } else if (isValid == false) { - // Call auditSubmission function and isValid is passed as false - console.log('RAISING AUDIT / VOTING FALSE'); - const response = await this.auditSubmission( - candidateKeyPairPublicKey, - isValid, - submitterAccountKeyPair, - round, - ); - console.log('RESPONSE FROM AUDIT FUNCTION', response); - } - } catch (err) { - console.log('ERROR IN ELSE CONDITION', err); - } - } - } - } - } - - async getTaskDistributionInfo(round) { - if (taskNodeAdministered) { - const taskDistributionInfo = await genericHandler( - 'getTaskDistributionInfo', - round, - ); - if (taskDistributionInfo.error) { - return null; - } - return taskDistributionInfo; - } else { - return this.#testingTaskState; - } - } - - async validateAndVoteOnDistributionList(validateDistribution, round) { - // await this.checkVoteStatus(); - console.log('******/ IN VOTING OF DISTRIBUTION LIST /******'); - let taskAccountDataJSON = null; - try { - taskAccountDataJSON = await this.getTaskDistributionInfo(round); - } catch (error) { - console.error('Error in getting distributions for the round', error); - } - if (taskAccountDataJSON == null) { - console.log('No distribution submissions found for the round', round); - return; - } - console.log( - `Fetching the Distribution submissions of round ${round}`, - taskAccountDataJSON.distribution_rewards_submission[round], - ); - const submissions = - taskAccountDataJSON?.distribution_rewards_submission[round]; - if ( - submissions == null || - submissions == undefined || - submissions.length == 0 - ) { - console.log(`No submisssions found in round ${round}`); - return `No submisssions found in round ${round}`; - } else { - const keys = Object.keys(submissions); - const values = Object.values(submissions); - const size = values.length; - console.log( - 'Distribution Submissions from last round: ', - keys, - values, - size, - ); - let isValid; - const submitterAccountKeyPair = await this.getSubmitterAccount(); - const submitterPubkey = submitterAccountKeyPair.publicKey.toBase58(); - - for (let i = 0; i < size; i++) { - let candidatePublicKey = keys[i]; - console.log('FOR CANDIDATE KEY', candidatePublicKey); - let candidateKeyPairPublicKey = new PublicKey(keys[i]); - if (candidatePublicKey == submitterPubkey) { - console.log('YOU CANNOT VOTE ON YOUR OWN DISTRIBUTION SUBMISSIONS'); - } else { - try { - console.log( - 'DISTRIBUTION SUBMISSION VALUE TO CHECK', - values[i].submission_value, - ); - isValid = await validateDistribution( - values[i].submission_value, - round, - ); - console.log(`Voting ${isValid} to ${candidatePublicKey}`); - - if (isValid) { - // check for the submissions_audit_trigger , if it exists then vote true on that otherwise do nothing - const distributions_audit_trigger = - taskAccountDataJSON.distributions_audit_trigger[round]; - console.log( - 'SUBMIT DISTRIBUTION AUDIT TRIGGER', - distributions_audit_trigger, - ); - // console.log( - // "CANDIDATE PUBKEY CHECK IN AUDIT TRIGGER", - // distributions_audit_trigger[candidatePublicKey] - // ); - if ( - distributions_audit_trigger && - distributions_audit_trigger[candidatePublicKey] - ) { - console.log('VOTING TRUE ON DISTRIBUTION AUDIT'); - const response = await this.distributionListAuditSubmission( - candidateKeyPairPublicKey, - isValid, - submitterAccountKeyPair, - round, - ); - console.log( - 'RESPONSE FROM DISTRIBUTION AUDIT FUNCTION', - response, - ); - } - } else if (isValid == false) { - // Call auditSubmission function and isValid is passed as false - console.log('RAISING AUDIT / VOTING FALSE ON DISTRIBUTION'); - const response = await this.distributionListAuditSubmission( - candidateKeyPairPublicKey, - isValid, - submitterAccountKeyPair, - round, - ); - console.log( - 'RESPONSE FROM DISTRIBUTION AUDIT FUNCTION', - response, - ); - } - } catch (err) { - console.log('ERROR IN ELSE CONDITION FOR DISTRIBUTION', err); - } - } - } - } - } - async getTaskLevelDBPath() { - if (taskNodeAdministered) { - return await genericHandler('getTaskLevelDBPath'); - } else { - return './KOIIDB'; - } - } - async getBasePath() { - if (taskNodeAdministered) { - const basePath = (await namespaceWrapper.getTaskLevelDBPath()).replace( - '/KOIIDB', - '', - ); - return basePath; - } else { - return './'; - } - } - - async getAverageSlotTime() { - if (taskNodeAdministered) { - try { - return await genericHandler('getAverageSlotTime'); - } catch (error) { - console.error('Error getting average slot time', error); - return 400; - } - } else { - return 400; - } - } - - async nodeSelectionDistributionList(round, isPreviousFailed) { - let taskAccountDataJSON = null; - try { - taskAccountDataJSON = await this.getTaskSubmissionInfo(round); - } catch (error) { - console.error('Task submission not found', error); - return; - } - - if (taskAccountDataJSON == null) { - console.error('Task state not found'); - return; - } - console.log('EXPECTED ROUND', round); - - const submissions = taskAccountDataJSON.submissions[round]; - if (submissions == null) { - console.log('No submisssions found in N-1 round'); - return 'No submisssions found in N-1 round'; - } else { - // getting last 3 submissions for the rounds - let keys; - const latestRounds = [round, round - 1, round - 2].filter(r => r >= 0); - - const promises = latestRounds.map(async r => { - if (r == round) { - return new Set(Object.keys(submissions)); - } else { - let roundSubmissions = null; - try { - roundSubmissions = await this.getTaskSubmissionInfo(r); - if (roundSubmissions && roundSubmissions.submissions[r]) { - return new Set(Object.keys(roundSubmissions.submissions[r])); - } - } catch (error) { - console.error('Error in getting submissions for the round', error); - } - return new Set(); - } - }); - - const keySets = await Promise.all(promises); - - // Find the keys present in all the rounds - keys = - keySets.length > 0 - ? [...keySets[0]].filter(key => keySets.every(set => set.has(key))) - : []; - if (keys.length == 0) { - console.log('No common keys found in last 3 rounds'); - keys = Object.keys(submissions); - } - console.log('KEYS', keys.length); - const values = keys.map(key => submissions[key]); - - let size = keys.length; - console.log('Submissions from N-2 round: ', size); - - // Check the keys i.e if the submitter shall be excluded or not - try { - const distributionData = await this.getTaskDistributionInfo(round); - const audit_record = distributionData?.distributions_audit_record; - if (audit_record && audit_record[round] == 'PayoutFailed') { - console.log('ROUND DATA', audit_record[round]); - console.log( - 'SUBMITTER LIST', - distributionData.distribution_rewards_submission[round], - ); - const submitterList = - distributionData.distribution_rewards_submission[round]; - const submitterKeys = Object.keys(submitterList); - console.log('SUBMITTER KEYS', submitterKeys); - const submitterSize = submitterKeys.length; - console.log('SUBMITTER SIZE', submitterSize); - - for (let j = 0; j < submitterSize; j++) { - console.log('SUBMITTER KEY CANDIDATE', submitterKeys[j]); - const id = keys.indexOf(submitterKeys[j]); - console.log('ID', id); - if (id != -1) { - keys.splice(id, 1); - values.splice(id, 1); - size--; - } - } - - console.log('KEYS FOR HASH CALC', keys.length); - } - } catch (error) { - console.log('Error in getting distribution data', error); - } - - // calculating the digest - - const ValuesString = JSON.stringify(values); - - const hashDigest = createHash('sha256') - .update(ValuesString) - .digest('hex'); - - console.log('HASH DIGEST', hashDigest); - - // function to calculate the score - const calculateScore = (str = '') => { - return str.split('').reduce((acc, val) => { - return acc + val.charCodeAt(0); - }, 0); - }; - - // function to compare the ASCII values - - const compareASCII = (str1, str2) => { - const firstScore = calculateScore(str1); - const secondScore = calculateScore(str2); - return Math.abs(firstScore - secondScore); - }; - - // loop through the keys and select the one with higest score - - const selectedNode = { - score: 0, - pubkey: '', - }; - let score = 0; - if (isPreviousFailed) { - let leastScore = -Infinity; - let secondLeastScore = -Infinity; - for (let i = 0; i < size; i++) { - const candidateSubmissionJson = {}; - candidateSubmissionJson[keys[i]] = values[i]; - const candidateSubmissionString = JSON.stringify( - candidateSubmissionJson, - ); - const candidateSubmissionHash = createHash('sha256') - .update(candidateSubmissionString) - .digest('hex'); - const candidateScore = compareASCII( - hashDigest, - candidateSubmissionHash, - ); - if (candidateScore > leastScore) { - secondLeastScore = leastScore; - leastScore = candidateScore; - } else if (candidateScore > secondLeastScore) { - secondLeastScore = candidateScore; - selectedNode.score = candidateScore; - selectedNode.pubkey = keys[i]; - } - } - } else { - for (let i = 0; i < size; i++) { - const candidateSubmissionJson = {}; - candidateSubmissionJson[keys[i]] = values[i]; - const candidateSubmissionString = JSON.stringify( - candidateSubmissionJson, - ); - const candidateSubmissionHash = createHash('sha256') - .update(candidateSubmissionString) - .digest('hex'); - const candidateScore = compareASCII( - hashDigest, - candidateSubmissionHash, - ); - // console.log('CANDIDATE SCORE', candidateScore); - if (candidateScore > score) { - score = candidateScore; - selectedNode.score = candidateScore; - selectedNode.pubkey = keys[i]; - } - } - } - - console.log('SELECTED NODE OBJECT', selectedNode); - return selectedNode.pubkey; - } - } - - async selectAndGenerateDistributionList( - submitDistributionList, - round, - isPreviousRoundFailed, - ) { - console.log('SelectAndGenerateDistributionList called'); - const selectedNode = await this.nodeSelectionDistributionList( - round, - isPreviousRoundFailed, - ); - console.log('Selected Node', selectedNode); - const submitPubKey = await this.getSubmitterAccount(); - if ( - selectedNode == undefined || - selectedNode == '' || - submitPubKey == undefined - ) - return; - if (selectedNode == submitPubKey?.publicKey.toBase58()) { - await submitDistributionList(round); - const taskState = await this.getTaskState({}); - if (taskState == null) { - console.error('Task state not found'); - return; - } - const avgSlotTime = await this.getAverageSlotTime(); - if (avgSlotTime == null) { - console.error('Avg slot time not found'); - return; - } - setTimeout(async () => { - await this.payoutTrigger(round); - }, (taskState.audit_window + taskState.submission_window) * avgSlotTime); - } - } - - getMainAccountPubkey() { - if (taskNodeAdministered) { - return MAIN_ACCOUNT_PUBKEY; - } else { - return this.#testingMainSystemAccount.publicKey.toBase58(); - } - } -} - -async function genericHandler(...args) { - try { - let response = await axios.post(BASE_ROOT_URL, { - args, - taskId: TASK_ID, - secret: SECRET_KEY, - }); - if (response.status == 200) return response.data.response; - else { - console.error(response.status, response.data); - return null; - } - } catch (err) { - console.error(`Error in genericHandler: "${args[0]}"`, err.message); - console.error(err?.response?.data); - return { error: err }; - } -} - -const namespaceWrapper = new NamespaceWrapper(); -if (taskNodeAdministered) { - namespaceWrapper.getRpcUrl().then(rpcUrl => { - console.log(rpcUrl, 'RPC URL'); - connection = new Connection(rpcUrl, 'confirmed'); - }); -} -module.exports = { - namespaceWrapper, - taskNodeAdministered, // Boolean flag indicating that the task is being ran in active mode (Task node supervised), or development (testing) mode - app, // The initialized express app to be used to register endpoints - TASK_ID, // This will be the PORT on which the this task is expected to run the express server coming from the task node running this task. As all communication via the task node and this task will be done on this port. - MAIN_ACCOUNT_PUBKEY, // This will be the secret used to authenticate with task node running this task. - SECRET_KEY, // This will be the secret used by the task to authenticate with task node running this task. - K2_NODE_URL, // This will be K2 url being used by the task node, possible values are 'https://k2-testnet.koii.live' | 'https://k2-devnet.koii.live' | 'http://localhost:8899' - SERVICE_URL, // This will be public task node endpoint (Or local if it doesn't have any) of the task node running this task. - STAKE, // This will be stake of the task node running this task, can be double checked with the task state and staking public key. - TASK_NODE_PORT, // This will be the port used by task node as the express server port, so it can be used by the task for the communication with the task node - _server, // Express server object -}; diff --git a/Lesson 2/file-sharing/after/coreLogic.js b/Lesson 2/file-sharing/after/coreLogic.js index 7b09d08..b210551 100644 --- a/Lesson 2/file-sharing/after/coreLogic.js +++ b/Lesson 2/file-sharing/after/coreLogic.js @@ -1,4 +1,4 @@ -const { namespaceWrapper } = require('./_koiiNode/koiiNode'); +const { namespaceWrapper } = require('@_koii/namespace-wrapper'); const task = require('./task'); class CoreLogic { diff --git a/Lesson 2/file-sharing/after/index.js b/Lesson 2/file-sharing/after/index.js index c7d556a..8d05e4a 100644 --- a/Lesson 2/file-sharing/after/index.js +++ b/Lesson 2/file-sharing/after/index.js @@ -3,7 +3,7 @@ const { namespaceWrapper, taskNodeAdministered, app, -} = require('./_koiiNode/koiiNode'); +} = require('@_koii/namespace-wrapper'); const setupRoutes = require('./routes'); diff --git a/Lesson 2/file-sharing/after/package.json b/Lesson 2/file-sharing/after/package.json index 55858d7..28d8f6f 100644 --- a/Lesson 2/file-sharing/after/package.json +++ b/Lesson 2/file-sharing/after/package.json @@ -17,6 +17,7 @@ "dependencies": { "@_koi/web3.js": "^0.0.6", "@_koii/storage-task-sdk": "^1.2.4", + "@_koii/namespace-wrapper": "^1.0.1", "axios": "^0.27.2", "body-parser": "^1.20.2", "cross-spawn": "^7.0.3", diff --git a/Lesson 2/file-sharing/after/task/audit.js b/Lesson 2/file-sharing/after/task/audit.js index 88b0594..21688dd 100644 --- a/Lesson 2/file-sharing/after/task/audit.js +++ b/Lesson 2/file-sharing/after/task/audit.js @@ -1,4 +1,4 @@ -const { namespaceWrapper } = require('../_koiiNode/koiiNode'); +const { namespaceWrapper } = require('@_koii/namespace-wrapper'); const isValidFile = require('./fileUtils/isValidFile'); class Audit { diff --git a/Lesson 2/file-sharing/after/task/distribution.js b/Lesson 2/file-sharing/after/task/distribution.js index 6b7c83b..f07071c 100644 --- a/Lesson 2/file-sharing/after/task/distribution.js +++ b/Lesson 2/file-sharing/after/task/distribution.js @@ -1,4 +1,4 @@ -const { namespaceWrapper } = require('../_koiiNode/koiiNode'); +const { namespaceWrapper } = require('@_koii/namespace-wrapper'); class Distribution { /** diff --git a/Lesson 2/file-sharing/after/task/submission.js b/Lesson 2/file-sharing/after/task/submission.js index a2ec3fa..a72964c 100644 --- a/Lesson 2/file-sharing/after/task/submission.js +++ b/Lesson 2/file-sharing/after/task/submission.js @@ -1,4 +1,4 @@ -const { namespaceWrapper } = require('../_koiiNode/koiiNode'); +const { namespaceWrapper } = require('@_koii/namespace-wrapper'); const storeFile = require('./fileUtils/storeFile'); class Submission { diff --git a/Lesson 2/file-sharing/after/tests/main.test.js b/Lesson 2/file-sharing/after/tests/main.test.js index 1eb8a52..05f5352 100644 --- a/Lesson 2/file-sharing/after/tests/main.test.js +++ b/Lesson 2/file-sharing/after/tests/main.test.js @@ -1,5 +1,5 @@ const { coreLogic } = require('../coreLogic'); -const { namespaceWrapper, _server } = require('../_koiiNode/koiiNode'); +const { namespaceWrapper, _server } = require('@_koii/namespace-wrapper'); const Joi = require('joi'); const axios = require('axios'); beforeAll(async () => { diff --git a/Lesson 2/file-sharing/before/_koiiNode/koiiNode.js b/Lesson 2/file-sharing/before/_koiiNode/koiiNode.js deleted file mode 100644 index 852bdb6..0000000 --- a/Lesson 2/file-sharing/before/_koiiNode/koiiNode.js +++ /dev/null @@ -1,1156 +0,0 @@ -const { default: axios } = require('axios'); -const { createHash } = require('crypto'); - -const { Connection, PublicKey, Keypair } = require('@_koi/web3.js'); - -const Datastore = require('nedb-promises'); -const fsPromises = require('fs/promises'); -const bs58 = require('bs58'); -const nacl = require('tweetnacl'); - -/****************************************** init.js ***********************************/ - -const express = require('express'); -// Only used for testing purposes, in production the env will be injected by tasknode -require('dotenv').config(); -const bodyParser = require('body-parser'); -/** - * This will be the name of the current task as coming from the task node running this task. - */ -const TASK_NAME = process.argv[2] || 'Local'; -/** - * This will be the id of the current task as coming from the task node running this task. - */ -const TASK_ID = process.argv[3]; -/** - * This will be the PORT on which the this task is expected to run the express server coming from the task node running this task. - * As all communication via the task node and this task will be done on this port. - */ -const EXPRESS_PORT = process.argv[4] || 10000; - -const LogLevel = { - Log: 'log', - Warn: 'warn', - Error: 'error', -}; - -// Not used anymore -// const NODE_MODE = process.argv[5]; - -/** - * This will be the main account public key in string format of the task node running this task. - */ -const MAIN_ACCOUNT_PUBKEY = process.argv[6]; -/** - * This will be the secret used by the task to authenticate with task node running this task. - */ -const SECRET_KEY = process.argv[7]; -/** - * This will be K2 url being used by the task node, possible values are 'https://k2-testnet.koii.live' | 'https://k2-devnet.koii.live' | 'http://localhost:8899' - */ -const K2_NODE_URL = process.argv[8] || 'https://k2-testnet.koii.live'; -/** - * This will be public task node endpoint (Or local if it doesn't have any) of the task node running this task. - */ -const SERVICE_URL = process.argv[9]; -/** - * This will be stake of the task node running this task, can be double checked with the task state and staking public key. - */ -const STAKE = Number(process.argv[10]); -/** - * This will be the port used by task node as the express server port, so it can be used by the task for the communication with the task node - */ -const TASK_NODE_PORT = Number(process.argv[11]); - -const app = express(); - -console.log('SETTING UP EXPRESS'); - -app.use(bodyParser.urlencoded({ extended: false })); - -app.use(bodyParser.json()); - -app.use((req, res, next) => { - res.setHeader('Access-Control-Allow-Origin', '*'); - res.setHeader( - 'Access-Control-Allow-Methods', - 'GET, POST, PUT, PATCH, DELETE', - ); - res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); - res.setHeader('Access-Control-Allow-Credentials', false); - if (req.method === 'OPTIONS') - // if is preflight(OPTIONS) then response status 204(NO CONTENT) - return res.send(204); - next(); -}); - -app.get('/', (req, res) => { - res.send('Hello World!'); -}); - -const _server = app.listen(EXPRESS_PORT, () => { - console.log(`${TASK_NAME} listening on port ${EXPRESS_PORT}`); -}); - -/****************************************** NamespaceWrapper.js ***********************************/ - -const taskNodeAdministered = !!TASK_ID; -const BASE_ROOT_URL = `http://localhost:${TASK_NODE_PORT}/namespace-wrapper`; -let connection; - -class NamespaceWrapper { - #db; - #testingMainSystemAccount; - #testingStakingSystemAccount; - #testingTaskState; - #testingDistributionList; - - constructor() { - if (taskNodeAdministered) { - this.initializeDB(); - } else { - this.#db = Datastore.create('./localKOIIDB.db'); - this.defaultTaskSetup(); - } - } - - async initializeDB() { - if (this.#db) return; - try { - if (taskNodeAdministered) { - const path = await this.getTaskLevelDBPath(); - this.#db = Datastore.create(path); - } else { - this.#db = Datastore.create('./localKOIIDB.db'); - } - } catch (e) { - this.#db = Datastore.create(`../namespace/${TASK_ID}/KOIILevelDB.db`); - } - } - - async getDb() { - if (this.#db) return this.#db; - await this.initializeDB(); - return this.#db; - } - /** - * Namespace wrapper of storeGetAsync - * @param {string} key // Path to get - */ - async storeGet(key) { - try { - await this.initializeDB(); - const resp = await this.#db.findOne({ key: key }); - if (resp) { - return resp[key]; - } else { - return null; - } - } catch (e) { - console.error(e); - return null; - } - } - /** - * Namespace wrapper over storeSetAsync - * @param {string} key Path to set - * @param {*} value Data to set - */ - async storeSet(key, value) { - try { - await this.initializeDB(); - await this.#db.update( - { key: key }, - { [key]: value, key }, - { upsert: true }, - ); - } catch (e) { - console.error(e); - return undefined; - } - } - - /** - * Namespace wrapper over fsPromises methods - * @param {*} method The fsPromise method to call - * @param {*} path Path for the express call - * @param {...any} args Remaining parameters for the FS call - */ - async fs(method, path, ...args) { - if (taskNodeAdministered) { - return await genericHandler('fs', method, path, ...args); - } else { - return fsPromises[method](`${path}`, ...args); - } - } - async fsStaking(method, path, ...args) { - if (taskNodeAdministered) { - return await genericHandler('fsStaking', method, path, ...args); - } else { - return fsPromises[method](`${path}`, ...args); - } - } - - async fsWriteStream(imagepath) { - if (taskNodeAdministered) { - return await genericHandler('fsWriteStream', imagepath); - } else { - const writer = createWriteStream(imagepath); - return writer; - } - } - async fsReadStream(imagepath) { - if (taskNodeAdministered) { - return await genericHandler('fsReadStream', imagepath); - } else { - const file = readFileSync(imagepath); - return file; - } - } - - /** - * Namespace wrapper for getting current slots - */ - async getSlot() { - if (taskNodeAdministered) { - return await genericHandler('getCurrentSlot'); - } else { - return 100; - } - } - - async payloadSigning(body) { - if (taskNodeAdministered) { - return await genericHandler('signData', body); - } else { - const msg = new TextEncoder().encode(JSON.stringify(body)); - const signedMessage = nacl.sign( - msg, - this.#testingMainSystemAccount.secretKey, - ); - return await this.bs58Encode(signedMessage); - } - } - - async bs58Encode(data) { - return bs58.encode( - Buffer.from(data.buffer, data.byteOffset, data.byteLength), - ); - } - - async bs58Decode(data) { - return new Uint8Array(bs58.decode(data)); - } - - decodePayload(payload) { - return new TextDecoder().decode(payload); - } - - /** - * Namespace wrapper of storeGetAsync - * @param {string} signedMessage r // Path to get - */ - - async verifySignature(signedMessage, pubKey) { - if (taskNodeAdministered) { - return await genericHandler('verifySignedData', signedMessage, pubKey); - } else { - try { - const payload = nacl.sign.open( - await this.bs58Decode(signedMessage), - await this.bs58Decode(pubKey), - ); - if (!payload) return { error: 'Invalid signature' }; - return { data: this.decodePayload(payload) }; - } catch (e) { - console.error(e); - return { error: `Verification failed: ${e}` }; - } - } - } - - // async submissionOnChain(submitterKeypair, submission) { - // return await genericHandler( - // 'submissionOnChain', - // submitterKeypair, - // submission, - // ); - // } - - async stakeOnChain( - taskStateInfoPublicKey, - stakingAccKeypair, - stakePotAccount, - stakeAmount, - ) { - if (taskNodeAdministered) { - return await genericHandler( - 'stakeOnChain', - taskStateInfoPublicKey, - stakingAccKeypair, - stakePotAccount, - stakeAmount, - ); - } else { - this.#testingTaskState.stake_list[ - this.#testingStakingSystemAccount.publicKey.toBase58() - ] = stakeAmount; - } - } - async claimReward(stakePotAccount, beneficiaryAccount, claimerKeypair) { - if (!taskNodeAdministered) { - console.log('Cannot call sendTransaction in testing mode'); - return; - } - return await genericHandler( - 'claimReward', - stakePotAccount, - beneficiaryAccount, - claimerKeypair, - ); - } - async sendTransaction(serviceNodeAccount, beneficiaryAccount, amount) { - if (!taskNodeAdministered) { - console.log('Cannot call sendTransaction in testing mode'); - return; - } - return await genericHandler( - 'sendTransaction', - serviceNodeAccount, - beneficiaryAccount, - amount, - ); - } - - async getSubmitterAccount() { - if (taskNodeAdministered) { - const submitterAccountResp = await genericHandler('getSubmitterAccount'); - return Keypair.fromSecretKey( - Uint8Array.from(Object.values(submitterAccountResp._keypair.secretKey)), - ); - } else { - return this.#testingStakingSystemAccount; - } - } - - /** - * sendAndConfirmTransaction wrapper that injects mainSystemWallet as the first signer for paying the tx fees - * @param {connection} method // Receive method ["get", "post", "put", "delete"] - * @param {transaction} path // Endpoint path appended to namespace - * @param {Function} callback // Callback function on traffic receive - */ - async sendAndConfirmTransactionWrapper(transaction, signers) { - if (!taskNodeAdministered) { - console.log('Cannot call sendTransaction in testing mode'); - return; - } - const blockhash = (await connection.getRecentBlockhash('finalized')) - .blockhash; - transaction.recentBlockhash = blockhash; - transaction.feePayer = new PublicKey(MAIN_ACCOUNT_PUBKEY); - return await genericHandler( - 'sendAndConfirmTransactionWrapper', - transaction.serialize({ - requireAllSignatures: false, - verifySignatures: false, - }), - signers, - ); - } - - // async signArweave(transaction) { - // let tx = await genericHandler('signArweave', transaction.toJSON()); - // return arweave.transactions.fromRaw(tx); - // } - // async signEth(transaction) { - // return await genericHandler('signEth', transaction); - // } - async getTaskState(options) { - if (taskNodeAdministered) { - const response = await genericHandler('getTaskState', options); - if (response.error) { - console.log('Error in getting task state', response.error); - return null; - } - return response; - } else { - return this.#testingTaskState; - } - } - - async logMessage(level, message, action) { - switch (level) { - case LogLevel.Log: - console.log(message, action); - break; - case LogLevel.Warn: - console.warn(message, action); - break; - case LogLevel.Error: - console.error(message, action); - break; - default: - console.log( - `Invalid log level: ${level}. The log levels can be log, warn or error`, - ); - return false; - } - return true; - } - - /** - * This logger function is used to log the task erros , warnings and logs on desktop-node - * @param {level} enum // Receive method ["Log", "Warn", "Error"] - enum LogLevel { - Log = 'log', - Warn = 'warn', - Error = 'error', - } - * @param {message} string // log, error or warning message - * @returns {boolean} // true if the message is logged successfully otherwise false - */ - - async logger(level, message, action) { - if (taskNodeAdministered) { - return await genericHandler('logger', level, message, action); - } else { - return await this.logMessage(level, message, action); - } - } - - async auditSubmission(candidatePubkey, isValid, voterKeypair, round) { - if (taskNodeAdministered) { - return await genericHandler( - 'auditSubmission', - candidatePubkey, - isValid, - round, - ); - } else { - if ( - this.#testingTaskState.submissions_audit_trigger[round] && - this.#testingTaskState.submissions_audit_trigger[round][candidatePubkey] - ) { - this.#testingTaskState.submissions_audit_trigger[round][ - candidatePubkey - ].votes.push({ - is_valid: isValid, - voter: voterKeypair.pubKey.toBase58(), - slot: 100, - }); - } else { - this.#testingTaskState.submissions_audit_trigger[round] = { - [candidatePubkey]: { - trigger_by: this.#testingStakingSystemAccount.publicKey.toBase58(), - slot: 100, - votes: [], - }, - }; - } - } - } - - async distributionListAuditSubmission( - candidatePubkey, - isValid, - voterKeypair, - round, - ) { - if (taskNodeAdministered) { - return await genericHandler( - 'distributionListAuditSubmission', - candidatePubkey, - isValid, - round, - ); - } else { - if ( - this.#testingTaskState.distributions_audit_trigger[round] && - this.#testingTaskState.distributions_audit_trigger[round][ - candidatePubkey - ] - ) { - this.#testingTaskState.distributions_audit_trigger[round][ - candidatePubkey - ].votes.push({ - is_valid: isValid, - voter: voterKeypair.pubKey.toBase58(), - slot: 100, - }); - } else { - this.#testingTaskState.distributions_audit_trigger[round] = { - [candidatePubkey]: { - trigger_by: this.#testingStakingSystemAccount.publicKey.toBase58(), - slot: 100, - votes: [], - }, - }; - } - } - } - - async getRound() { - if (taskNodeAdministered) { - return await genericHandler('getRound'); - } else { - return 1; - } - } - - async payoutTrigger(round) { - if (taskNodeAdministered) { - return await genericHandler('payloadTrigger', round); - } else { - console.log( - 'Payout Trigger only handles possitive flows (Without audits)', - ); - let round = 1; - const submissionValAcc = - this.#testingDistributionList[round][ - this.#testingStakingSystemAccount.toBase58() - ].submission_value; - this.#testingTaskState.available_balances = - this.#testingDistributionList[round][submissionValAcc]; - } - } - - async uploadDistributionList(distributionList, round) { - if (taskNodeAdministered) { - return await genericHandler( - 'uploadDistributionList', - distributionList, - round, - ); - } else { - if (!this.#testingDistributionList[round]) - this.#testingDistributionList[round] = {}; - - this.#testingDistributionList[round][ - this.#testingStakingSystemAccount.publicKey.toBase58() - ] = Buffer.from(JSON.stringify(distributionList)); - return true; - } - } - - async distributionListSubmissionOnChain(round) { - if (taskNodeAdministered) { - return await genericHandler('distributionListSubmissionOnChain', round); - } else { - if (!this.#testingTaskState.distribution_rewards_submission[round]) - this.#testingTaskState.distribution_rewards_submission[round] = {}; - - this.#testingTaskState.distribution_rewards_submission[round][ - this.#testingStakingSystemAccount.publicKey.toBase58() - ] = { - submission_value: - this.#testingStakingSystemAccount.publicKey.toBase58(), - slot: 200, - round: 1, - }; - } - } - - async checkSubmissionAndUpdateRound(submissionValue = 'default', round) { - if (taskNodeAdministered) { - return await genericHandler( - 'checkSubmissionAndUpdateRound', - submissionValue, - round, - ); - } else { - if (!this.#testingTaskState.submissions[round]) - this.#testingTaskState.submissions[round] = {}; - this.#testingTaskState.submissions[round][ - this.#testingStakingSystemAccount.publicKey.toBase58() - ] = { - submission_value: submissionValue, - slot: 100, - round, - }; - } - } - async getProgramAccounts() { - if (taskNodeAdministered) { - return await genericHandler('getProgramAccounts'); - } else { - console.log('Cannot call getProgramAccounts in testing mode'); - } - } - async defaultTaskSetup() { - if (taskNodeAdministered) { - return await genericHandler('defaultTaskSetup'); - } else { - if (this.#testingTaskState) return; - this.#testingMainSystemAccount = new Keypair(); - this.#testingStakingSystemAccount = new Keypair(); - this.#testingDistributionList = {}; - this.#testingTaskState = { - task_name: 'DummyTestState', - task_description: 'Dummy Task state for testing flow', - submissions: {}, - submissions_audit_trigger: {}, - total_bounty_amount: 10000000000, - bounty_amount_per_round: 1000000000, - total_stake_amount: 50000000000, - minimum_stake_amount: 5000000000, - available_balances: {}, - stake_list: {}, - round_time: 600, - starting_slot: 0, - audit_window: 200, - submission_window: 200, - distribution_rewards_submission: {}, - distributions_audit_trigger: {}, - }; - } - } - async getRpcUrl() { - if (taskNodeAdministered) { - return await genericHandler('getRpcUrl'); - } else { - console.log('Cannot call getNodes in testing mode'); - } - } - async getNodes(url) { - if (taskNodeAdministered) { - return await genericHandler('getNodes', url); - } else { - console.log('Cannot call getNodes in testing mode'); - } - } - - async getDistributionList(publicKey, round) { - if (taskNodeAdministered) { - const response = await genericHandler( - 'getDistributionList', - publicKey, - round, - ); - if (response.error) { - return null; - } - return response; - } else { - const submissionValAcc = - this.#testingTaskState.distribution_rewards_submission[round][ - this.#testingStakingSystemAccount.publicKey.toBase58() - ].submission_value; - return this.#testingDistributionList[round][submissionValAcc]; - } - } - - async getTaskSubmissionInfo(round) { - if (taskNodeAdministered) { - const taskSubmissionInfo = await genericHandler( - 'getTaskSubmissionInfo', - round, - ); - if (taskSubmissionInfo.error) { - return null; - } - return taskSubmissionInfo; - } else { - // console.log(this.#testingTaskState) - return this.#testingTaskState; - } - } - - async validateAndVoteOnNodes(validate, round) { - console.log('******/ IN VOTING /******'); - let taskAccountDataJSON = null; - try { - taskAccountDataJSON = await this.getTaskSubmissionInfo(round); - } catch (error) { - console.error('Error in getting submissions for the round', error); - } - if (taskAccountDataJSON == null) { - console.log('No submissions found for the round', round); - return; - } - console.log( - `Fetching the submissions of round ${round}`, - taskAccountDataJSON.submissions[round], - ); - const submissions = taskAccountDataJSON.submissions[round]; - if (submissions == null) { - console.log(`No submisssions found in round ${round}`); - return `No submisssions found in round ${round}`; - } else { - const keys = Object.keys(submissions); - const values = Object.values(submissions); - const size = values.length; - console.log('Submissions from last round: ', keys, values, size); - let isValid; - const submitterAccountKeyPair = await this.getSubmitterAccount(); - const submitterPubkey = submitterAccountKeyPair.publicKey.toBase58(); - for (let i = 0; i < size; i++) { - let candidatePublicKey = keys[i]; - console.log('FOR CANDIDATE KEY', candidatePublicKey); - let candidateKeyPairPublicKey = new PublicKey(keys[i]); - if (candidatePublicKey == submitterPubkey && taskNodeAdministered) { - console.log('YOU CANNOT VOTE ON YOUR OWN SUBMISSIONS'); - } else { - try { - console.log( - 'SUBMISSION VALUE TO CHECK', - values[i].submission_value, - ); - isValid = await validate(values[i].submission_value, round); - console.log(`Voting ${isValid} to ${candidatePublicKey}`); - - if (isValid) { - // check for the submissions_audit_trigger , if it exists then vote true on that otherwise do nothing - const submissions_audit_trigger = - taskAccountDataJSON.submissions_audit_trigger[round]; - console.log('SUBMIT AUDIT TRIGGER', submissions_audit_trigger); - // console.log( - // "CANDIDATE PUBKEY CHECK IN AUDIT TRIGGER", - // submissions_audit_trigger[candidatePublicKey] - // ); - if ( - submissions_audit_trigger && - submissions_audit_trigger[candidatePublicKey] - ) { - console.log('VOTING TRUE ON AUDIT'); - const response = await this.auditSubmission( - candidateKeyPairPublicKey, - isValid, - submitterAccountKeyPair, - round, - ); - console.log('RESPONSE FROM AUDIT FUNCTION', response); - } - } else if (isValid == false) { - // Call auditSubmission function and isValid is passed as false - console.log('RAISING AUDIT / VOTING FALSE'); - const response = await this.auditSubmission( - candidateKeyPairPublicKey, - isValid, - submitterAccountKeyPair, - round, - ); - console.log('RESPONSE FROM AUDIT FUNCTION', response); - } - } catch (err) { - console.log('ERROR IN ELSE CONDITION', err); - } - } - } - } - } - - async getTaskDistributionInfo(round) { - if (taskNodeAdministered) { - const taskDistributionInfo = await genericHandler( - 'getTaskDistributionInfo', - round, - ); - if (taskDistributionInfo.error) { - return null; - } - return taskDistributionInfo; - } else { - return this.#testingTaskState; - } - } - - async validateAndVoteOnDistributionList(validateDistribution, round) { - // await this.checkVoteStatus(); - console.log('******/ IN VOTING OF DISTRIBUTION LIST /******'); - let taskAccountDataJSON = null; - try { - taskAccountDataJSON = await this.getTaskDistributionInfo(round); - } catch (error) { - console.error('Error in getting distributions for the round', error); - } - if (taskAccountDataJSON == null) { - console.log('No distribution submissions found for the round', round); - return; - } - console.log( - `Fetching the Distribution submissions of round ${round}`, - taskAccountDataJSON.distribution_rewards_submission[round], - ); - const submissions = - taskAccountDataJSON?.distribution_rewards_submission[round]; - if ( - submissions == null || - submissions == undefined || - submissions.length == 0 - ) { - console.log(`No submisssions found in round ${round}`); - return `No submisssions found in round ${round}`; - } else { - const keys = Object.keys(submissions); - const values = Object.values(submissions); - const size = values.length; - console.log( - 'Distribution Submissions from last round: ', - keys, - values, - size, - ); - let isValid; - const submitterAccountKeyPair = await this.getSubmitterAccount(); - const submitterPubkey = submitterAccountKeyPair.publicKey.toBase58(); - - for (let i = 0; i < size; i++) { - let candidatePublicKey = keys[i]; - console.log('FOR CANDIDATE KEY', candidatePublicKey); - let candidateKeyPairPublicKey = new PublicKey(keys[i]); - if (candidatePublicKey == submitterPubkey) { - console.log('YOU CANNOT VOTE ON YOUR OWN DISTRIBUTION SUBMISSIONS'); - } else { - try { - console.log( - 'DISTRIBUTION SUBMISSION VALUE TO CHECK', - values[i].submission_value, - ); - isValid = await validateDistribution( - values[i].submission_value, - round, - ); - console.log(`Voting ${isValid} to ${candidatePublicKey}`); - - if (isValid) { - // check for the submissions_audit_trigger , if it exists then vote true on that otherwise do nothing - const distributions_audit_trigger = - taskAccountDataJSON.distributions_audit_trigger[round]; - console.log( - 'SUBMIT DISTRIBUTION AUDIT TRIGGER', - distributions_audit_trigger, - ); - // console.log( - // "CANDIDATE PUBKEY CHECK IN AUDIT TRIGGER", - // distributions_audit_trigger[candidatePublicKey] - // ); - if ( - distributions_audit_trigger && - distributions_audit_trigger[candidatePublicKey] - ) { - console.log('VOTING TRUE ON DISTRIBUTION AUDIT'); - const response = await this.distributionListAuditSubmission( - candidateKeyPairPublicKey, - isValid, - submitterAccountKeyPair, - round, - ); - console.log( - 'RESPONSE FROM DISTRIBUTION AUDIT FUNCTION', - response, - ); - } - } else if (isValid == false) { - // Call auditSubmission function and isValid is passed as false - console.log('RAISING AUDIT / VOTING FALSE ON DISTRIBUTION'); - const response = await this.distributionListAuditSubmission( - candidateKeyPairPublicKey, - isValid, - submitterAccountKeyPair, - round, - ); - console.log( - 'RESPONSE FROM DISTRIBUTION AUDIT FUNCTION', - response, - ); - } - } catch (err) { - console.log('ERROR IN ELSE CONDITION FOR DISTRIBUTION', err); - } - } - } - } - } - async getTaskLevelDBPath() { - if (taskNodeAdministered) { - return await genericHandler('getTaskLevelDBPath'); - } else { - return './KOIIDB'; - } - } - async getBasePath() { - if (taskNodeAdministered) { - const basePath = (await namespaceWrapper.getTaskLevelDBPath()).replace( - '/KOIIDB', - '', - ); - return basePath; - } else { - return './'; - } - } - - async getAverageSlotTime() { - if (taskNodeAdministered) { - try { - return await genericHandler('getAverageSlotTime'); - } catch (error) { - console.error('Error getting average slot time', error); - return 400; - } - } else { - return 400; - } - } - - async nodeSelectionDistributionList(round, isPreviousFailed) { - let taskAccountDataJSON = null; - try { - taskAccountDataJSON = await this.getTaskSubmissionInfo(round); - } catch (error) { - console.error('Task submission not found', error); - return; - } - - if (taskAccountDataJSON == null) { - console.error('Task state not found'); - return; - } - console.log('EXPECTED ROUND', round); - - const submissions = taskAccountDataJSON.submissions[round]; - if (submissions == null) { - console.log('No submisssions found in N-1 round'); - return 'No submisssions found in N-1 round'; - } else { - // getting last 3 submissions for the rounds - let keys; - const latestRounds = [round, round - 1, round - 2].filter(r => r >= 0); - - const promises = latestRounds.map(async r => { - if (r == round) { - return new Set(Object.keys(submissions)); - } else { - let roundSubmissions = null; - try { - roundSubmissions = await this.getTaskSubmissionInfo(r); - if (roundSubmissions && roundSubmissions.submissions[r]) { - return new Set(Object.keys(roundSubmissions.submissions[r])); - } - } catch (error) { - console.error('Error in getting submissions for the round', error); - } - return new Set(); - } - }); - - const keySets = await Promise.all(promises); - - // Find the keys present in all the rounds - keys = - keySets.length > 0 - ? [...keySets[0]].filter(key => keySets.every(set => set.has(key))) - : []; - if (keys.length == 0) { - console.log('No common keys found in last 3 rounds'); - keys = Object.keys(submissions); - } - console.log('KEYS', keys.length); - const values = keys.map(key => submissions[key]); - - let size = keys.length; - console.log('Submissions from N-2 round: ', size); - - // Check the keys i.e if the submitter shall be excluded or not - try { - const distributionData = await this.getTaskDistributionInfo(round); - const audit_record = distributionData?.distributions_audit_record; - if (audit_record && audit_record[round] == 'PayoutFailed') { - console.log('ROUND DATA', audit_record[round]); - console.log( - 'SUBMITTER LIST', - distributionData.distribution_rewards_submission[round], - ); - const submitterList = - distributionData.distribution_rewards_submission[round]; - const submitterKeys = Object.keys(submitterList); - console.log('SUBMITTER KEYS', submitterKeys); - const submitterSize = submitterKeys.length; - console.log('SUBMITTER SIZE', submitterSize); - - for (let j = 0; j < submitterSize; j++) { - console.log('SUBMITTER KEY CANDIDATE', submitterKeys[j]); - const id = keys.indexOf(submitterKeys[j]); - console.log('ID', id); - if (id != -1) { - keys.splice(id, 1); - values.splice(id, 1); - size--; - } - } - - console.log('KEYS FOR HASH CALC', keys.length); - } - } catch (error) { - console.log('Error in getting distribution data', error); - } - - // calculating the digest - - const ValuesString = JSON.stringify(values); - - const hashDigest = createHash('sha256') - .update(ValuesString) - .digest('hex'); - - console.log('HASH DIGEST', hashDigest); - - // function to calculate the score - const calculateScore = (str = '') => { - return str.split('').reduce((acc, val) => { - return acc + val.charCodeAt(0); - }, 0); - }; - - // function to compare the ASCII values - - const compareASCII = (str1, str2) => { - const firstScore = calculateScore(str1); - const secondScore = calculateScore(str2); - return Math.abs(firstScore - secondScore); - }; - - // loop through the keys and select the one with higest score - - const selectedNode = { - score: 0, - pubkey: '', - }; - let score = 0; - if (isPreviousFailed) { - let leastScore = -Infinity; - let secondLeastScore = -Infinity; - for (let i = 0; i < size; i++) { - const candidateSubmissionJson = {}; - candidateSubmissionJson[keys[i]] = values[i]; - const candidateSubmissionString = JSON.stringify( - candidateSubmissionJson, - ); - const candidateSubmissionHash = createHash('sha256') - .update(candidateSubmissionString) - .digest('hex'); - const candidateScore = compareASCII( - hashDigest, - candidateSubmissionHash, - ); - if (candidateScore > leastScore) { - secondLeastScore = leastScore; - leastScore = candidateScore; - } else if (candidateScore > secondLeastScore) { - secondLeastScore = candidateScore; - selectedNode.score = candidateScore; - selectedNode.pubkey = keys[i]; - } - } - } else { - for (let i = 0; i < size; i++) { - const candidateSubmissionJson = {}; - candidateSubmissionJson[keys[i]] = values[i]; - const candidateSubmissionString = JSON.stringify( - candidateSubmissionJson, - ); - const candidateSubmissionHash = createHash('sha256') - .update(candidateSubmissionString) - .digest('hex'); - const candidateScore = compareASCII( - hashDigest, - candidateSubmissionHash, - ); - // console.log('CANDIDATE SCORE', candidateScore); - if (candidateScore > score) { - score = candidateScore; - selectedNode.score = candidateScore; - selectedNode.pubkey = keys[i]; - } - } - } - - console.log('SELECTED NODE OBJECT', selectedNode); - return selectedNode.pubkey; - } - } - - async selectAndGenerateDistributionList( - submitDistributionList, - round, - isPreviousRoundFailed, - ) { - console.log('SelectAndGenerateDistributionList called'); - const selectedNode = await this.nodeSelectionDistributionList( - round, - isPreviousRoundFailed, - ); - console.log('Selected Node', selectedNode); - const submitPubKey = await this.getSubmitterAccount(); - if ( - selectedNode == undefined || - selectedNode == '' || - submitPubKey == undefined - ) - return; - if (selectedNode == submitPubKey?.publicKey.toBase58()) { - await submitDistributionList(round); - const taskState = await this.getTaskState({}); - if (taskState == null) { - console.error('Task state not found'); - return; - } - const avgSlotTime = await this.getAverageSlotTime(); - if (avgSlotTime == null) { - console.error('Avg slot time not found'); - return; - } - setTimeout(async () => { - await this.payoutTrigger(round); - }, (taskState.audit_window + taskState.submission_window) * avgSlotTime); - } - } - - getMainAccountPubkey() { - if (taskNodeAdministered) { - return MAIN_ACCOUNT_PUBKEY; - } else { - return this.#testingMainSystemAccount.publicKey.toBase58(); - } - } -} - -async function genericHandler(...args) { - try { - let response = await axios.post(BASE_ROOT_URL, { - args, - taskId: TASK_ID, - secret: SECRET_KEY, - }); - if (response.status == 200) return response.data.response; - else { - console.error(response.status, response.data); - return null; - } - } catch (err) { - console.error(`Error in genericHandler: "${args[0]}"`, err.message); - console.error(err?.response?.data); - return { error: err }; - } -} - -const namespaceWrapper = new NamespaceWrapper(); -if (taskNodeAdministered) { - namespaceWrapper.getRpcUrl().then(rpcUrl => { - console.log(rpcUrl, 'RPC URL'); - connection = new Connection(rpcUrl, 'confirmed'); - }); -} -module.exports = { - namespaceWrapper, - taskNodeAdministered, // Boolean flag indicating that the task is being ran in active mode (Task node supervised), or development (testing) mode - app, // The initialized express app to be used to register endpoints - TASK_ID, // This will be the PORT on which the this task is expected to run the express server coming from the task node running this task. As all communication via the task node and this task will be done on this port. - MAIN_ACCOUNT_PUBKEY, // This will be the secret used to authenticate with task node running this task. - SECRET_KEY, // This will be the secret used by the task to authenticate with task node running this task. - K2_NODE_URL, // This will be K2 url being used by the task node, possible values are 'https://k2-testnet.koii.live' | 'https://k2-devnet.koii.live' | 'http://localhost:8899' - SERVICE_URL, // This will be public task node endpoint (Or local if it doesn't have any) of the task node running this task. - STAKE, // This will be stake of the task node running this task, can be double checked with the task state and staking public key. - TASK_NODE_PORT, // This will be the port used by task node as the express server port, so it can be used by the task for the communication with the task node - _server, // Express server object -}; diff --git a/Lesson 2/file-sharing/before/coreLogic.js b/Lesson 2/file-sharing/before/coreLogic.js index 7b09d08..b210551 100644 --- a/Lesson 2/file-sharing/before/coreLogic.js +++ b/Lesson 2/file-sharing/before/coreLogic.js @@ -1,4 +1,4 @@ -const { namespaceWrapper } = require('./_koiiNode/koiiNode'); +const { namespaceWrapper } = require('@_koii/namespace-wrapper'); const task = require('./task'); class CoreLogic { diff --git a/Lesson 2/file-sharing/before/index.js b/Lesson 2/file-sharing/before/index.js index c7d556a..8d05e4a 100644 --- a/Lesson 2/file-sharing/before/index.js +++ b/Lesson 2/file-sharing/before/index.js @@ -3,7 +3,7 @@ const { namespaceWrapper, taskNodeAdministered, app, -} = require('./_koiiNode/koiiNode'); +} = require('@_koii/namespace-wrapper'); const setupRoutes = require('./routes'); diff --git a/Lesson 2/file-sharing/before/package.json b/Lesson 2/file-sharing/before/package.json index 55858d7..e1532ee 100644 --- a/Lesson 2/file-sharing/before/package.json +++ b/Lesson 2/file-sharing/before/package.json @@ -16,6 +16,7 @@ "license": "ISC", "dependencies": { "@_koi/web3.js": "^0.0.6", + "@_koii/namespace-wrapper": "^1.0.1", "@_koii/storage-task-sdk": "^1.2.4", "axios": "^0.27.2", "body-parser": "^1.20.2", diff --git a/Lesson 2/file-sharing/before/task/audit.js b/Lesson 2/file-sharing/before/task/audit.js index 709a13d..e0a1017 100644 --- a/Lesson 2/file-sharing/before/task/audit.js +++ b/Lesson 2/file-sharing/before/task/audit.js @@ -1,4 +1,4 @@ -const { namespaceWrapper } = require('../_koiiNode/koiiNode'); +const { namespaceWrapper } = require('@_koii/namespace-wrapper'); class Audit { /** diff --git a/Lesson 2/file-sharing/before/task/distribution.js b/Lesson 2/file-sharing/before/task/distribution.js index 6b7c83b..f07071c 100644 --- a/Lesson 2/file-sharing/before/task/distribution.js +++ b/Lesson 2/file-sharing/before/task/distribution.js @@ -1,4 +1,4 @@ -const { namespaceWrapper } = require('../_koiiNode/koiiNode'); +const { namespaceWrapper } = require('@_koii/namespace-wrapper'); class Distribution { /** diff --git a/Lesson 2/file-sharing/before/task/submission.js b/Lesson 2/file-sharing/before/task/submission.js index 3768a3d..ae27343 100644 --- a/Lesson 2/file-sharing/before/task/submission.js +++ b/Lesson 2/file-sharing/before/task/submission.js @@ -1,4 +1,4 @@ -const { namespaceWrapper } = require('../_koiiNode/koiiNode'); +const { namespaceWrapper } = require('@_koii/namespace-wrapper'); const storeFile = require('./fileUtils/storeFile'); diff --git a/Lesson 2/file-sharing/before/tests/main.test.js b/Lesson 2/file-sharing/before/tests/main.test.js index 1eb8a52..05f5352 100644 --- a/Lesson 2/file-sharing/before/tests/main.test.js +++ b/Lesson 2/file-sharing/before/tests/main.test.js @@ -1,5 +1,5 @@ const { coreLogic } = require('../coreLogic'); -const { namespaceWrapper, _server } = require('../_koiiNode/koiiNode'); +const { namespaceWrapper, _server } = require('@_koii/namespace-wrapper'); const Joi = require('joi'); const axios = require('axios'); beforeAll(async () => { diff --git a/Lesson 2/upnp-basics/after/_koiiNode/koiiNode.js b/Lesson 2/upnp-basics/after/_koiiNode/koiiNode.js deleted file mode 100644 index 852bdb6..0000000 --- a/Lesson 2/upnp-basics/after/_koiiNode/koiiNode.js +++ /dev/null @@ -1,1156 +0,0 @@ -const { default: axios } = require('axios'); -const { createHash } = require('crypto'); - -const { Connection, PublicKey, Keypair } = require('@_koi/web3.js'); - -const Datastore = require('nedb-promises'); -const fsPromises = require('fs/promises'); -const bs58 = require('bs58'); -const nacl = require('tweetnacl'); - -/****************************************** init.js ***********************************/ - -const express = require('express'); -// Only used for testing purposes, in production the env will be injected by tasknode -require('dotenv').config(); -const bodyParser = require('body-parser'); -/** - * This will be the name of the current task as coming from the task node running this task. - */ -const TASK_NAME = process.argv[2] || 'Local'; -/** - * This will be the id of the current task as coming from the task node running this task. - */ -const TASK_ID = process.argv[3]; -/** - * This will be the PORT on which the this task is expected to run the express server coming from the task node running this task. - * As all communication via the task node and this task will be done on this port. - */ -const EXPRESS_PORT = process.argv[4] || 10000; - -const LogLevel = { - Log: 'log', - Warn: 'warn', - Error: 'error', -}; - -// Not used anymore -// const NODE_MODE = process.argv[5]; - -/** - * This will be the main account public key in string format of the task node running this task. - */ -const MAIN_ACCOUNT_PUBKEY = process.argv[6]; -/** - * This will be the secret used by the task to authenticate with task node running this task. - */ -const SECRET_KEY = process.argv[7]; -/** - * This will be K2 url being used by the task node, possible values are 'https://k2-testnet.koii.live' | 'https://k2-devnet.koii.live' | 'http://localhost:8899' - */ -const K2_NODE_URL = process.argv[8] || 'https://k2-testnet.koii.live'; -/** - * This will be public task node endpoint (Or local if it doesn't have any) of the task node running this task. - */ -const SERVICE_URL = process.argv[9]; -/** - * This will be stake of the task node running this task, can be double checked with the task state and staking public key. - */ -const STAKE = Number(process.argv[10]); -/** - * This will be the port used by task node as the express server port, so it can be used by the task for the communication with the task node - */ -const TASK_NODE_PORT = Number(process.argv[11]); - -const app = express(); - -console.log('SETTING UP EXPRESS'); - -app.use(bodyParser.urlencoded({ extended: false })); - -app.use(bodyParser.json()); - -app.use((req, res, next) => { - res.setHeader('Access-Control-Allow-Origin', '*'); - res.setHeader( - 'Access-Control-Allow-Methods', - 'GET, POST, PUT, PATCH, DELETE', - ); - res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); - res.setHeader('Access-Control-Allow-Credentials', false); - if (req.method === 'OPTIONS') - // if is preflight(OPTIONS) then response status 204(NO CONTENT) - return res.send(204); - next(); -}); - -app.get('/', (req, res) => { - res.send('Hello World!'); -}); - -const _server = app.listen(EXPRESS_PORT, () => { - console.log(`${TASK_NAME} listening on port ${EXPRESS_PORT}`); -}); - -/****************************************** NamespaceWrapper.js ***********************************/ - -const taskNodeAdministered = !!TASK_ID; -const BASE_ROOT_URL = `http://localhost:${TASK_NODE_PORT}/namespace-wrapper`; -let connection; - -class NamespaceWrapper { - #db; - #testingMainSystemAccount; - #testingStakingSystemAccount; - #testingTaskState; - #testingDistributionList; - - constructor() { - if (taskNodeAdministered) { - this.initializeDB(); - } else { - this.#db = Datastore.create('./localKOIIDB.db'); - this.defaultTaskSetup(); - } - } - - async initializeDB() { - if (this.#db) return; - try { - if (taskNodeAdministered) { - const path = await this.getTaskLevelDBPath(); - this.#db = Datastore.create(path); - } else { - this.#db = Datastore.create('./localKOIIDB.db'); - } - } catch (e) { - this.#db = Datastore.create(`../namespace/${TASK_ID}/KOIILevelDB.db`); - } - } - - async getDb() { - if (this.#db) return this.#db; - await this.initializeDB(); - return this.#db; - } - /** - * Namespace wrapper of storeGetAsync - * @param {string} key // Path to get - */ - async storeGet(key) { - try { - await this.initializeDB(); - const resp = await this.#db.findOne({ key: key }); - if (resp) { - return resp[key]; - } else { - return null; - } - } catch (e) { - console.error(e); - return null; - } - } - /** - * Namespace wrapper over storeSetAsync - * @param {string} key Path to set - * @param {*} value Data to set - */ - async storeSet(key, value) { - try { - await this.initializeDB(); - await this.#db.update( - { key: key }, - { [key]: value, key }, - { upsert: true }, - ); - } catch (e) { - console.error(e); - return undefined; - } - } - - /** - * Namespace wrapper over fsPromises methods - * @param {*} method The fsPromise method to call - * @param {*} path Path for the express call - * @param {...any} args Remaining parameters for the FS call - */ - async fs(method, path, ...args) { - if (taskNodeAdministered) { - return await genericHandler('fs', method, path, ...args); - } else { - return fsPromises[method](`${path}`, ...args); - } - } - async fsStaking(method, path, ...args) { - if (taskNodeAdministered) { - return await genericHandler('fsStaking', method, path, ...args); - } else { - return fsPromises[method](`${path}`, ...args); - } - } - - async fsWriteStream(imagepath) { - if (taskNodeAdministered) { - return await genericHandler('fsWriteStream', imagepath); - } else { - const writer = createWriteStream(imagepath); - return writer; - } - } - async fsReadStream(imagepath) { - if (taskNodeAdministered) { - return await genericHandler('fsReadStream', imagepath); - } else { - const file = readFileSync(imagepath); - return file; - } - } - - /** - * Namespace wrapper for getting current slots - */ - async getSlot() { - if (taskNodeAdministered) { - return await genericHandler('getCurrentSlot'); - } else { - return 100; - } - } - - async payloadSigning(body) { - if (taskNodeAdministered) { - return await genericHandler('signData', body); - } else { - const msg = new TextEncoder().encode(JSON.stringify(body)); - const signedMessage = nacl.sign( - msg, - this.#testingMainSystemAccount.secretKey, - ); - return await this.bs58Encode(signedMessage); - } - } - - async bs58Encode(data) { - return bs58.encode( - Buffer.from(data.buffer, data.byteOffset, data.byteLength), - ); - } - - async bs58Decode(data) { - return new Uint8Array(bs58.decode(data)); - } - - decodePayload(payload) { - return new TextDecoder().decode(payload); - } - - /** - * Namespace wrapper of storeGetAsync - * @param {string} signedMessage r // Path to get - */ - - async verifySignature(signedMessage, pubKey) { - if (taskNodeAdministered) { - return await genericHandler('verifySignedData', signedMessage, pubKey); - } else { - try { - const payload = nacl.sign.open( - await this.bs58Decode(signedMessage), - await this.bs58Decode(pubKey), - ); - if (!payload) return { error: 'Invalid signature' }; - return { data: this.decodePayload(payload) }; - } catch (e) { - console.error(e); - return { error: `Verification failed: ${e}` }; - } - } - } - - // async submissionOnChain(submitterKeypair, submission) { - // return await genericHandler( - // 'submissionOnChain', - // submitterKeypair, - // submission, - // ); - // } - - async stakeOnChain( - taskStateInfoPublicKey, - stakingAccKeypair, - stakePotAccount, - stakeAmount, - ) { - if (taskNodeAdministered) { - return await genericHandler( - 'stakeOnChain', - taskStateInfoPublicKey, - stakingAccKeypair, - stakePotAccount, - stakeAmount, - ); - } else { - this.#testingTaskState.stake_list[ - this.#testingStakingSystemAccount.publicKey.toBase58() - ] = stakeAmount; - } - } - async claimReward(stakePotAccount, beneficiaryAccount, claimerKeypair) { - if (!taskNodeAdministered) { - console.log('Cannot call sendTransaction in testing mode'); - return; - } - return await genericHandler( - 'claimReward', - stakePotAccount, - beneficiaryAccount, - claimerKeypair, - ); - } - async sendTransaction(serviceNodeAccount, beneficiaryAccount, amount) { - if (!taskNodeAdministered) { - console.log('Cannot call sendTransaction in testing mode'); - return; - } - return await genericHandler( - 'sendTransaction', - serviceNodeAccount, - beneficiaryAccount, - amount, - ); - } - - async getSubmitterAccount() { - if (taskNodeAdministered) { - const submitterAccountResp = await genericHandler('getSubmitterAccount'); - return Keypair.fromSecretKey( - Uint8Array.from(Object.values(submitterAccountResp._keypair.secretKey)), - ); - } else { - return this.#testingStakingSystemAccount; - } - } - - /** - * sendAndConfirmTransaction wrapper that injects mainSystemWallet as the first signer for paying the tx fees - * @param {connection} method // Receive method ["get", "post", "put", "delete"] - * @param {transaction} path // Endpoint path appended to namespace - * @param {Function} callback // Callback function on traffic receive - */ - async sendAndConfirmTransactionWrapper(transaction, signers) { - if (!taskNodeAdministered) { - console.log('Cannot call sendTransaction in testing mode'); - return; - } - const blockhash = (await connection.getRecentBlockhash('finalized')) - .blockhash; - transaction.recentBlockhash = blockhash; - transaction.feePayer = new PublicKey(MAIN_ACCOUNT_PUBKEY); - return await genericHandler( - 'sendAndConfirmTransactionWrapper', - transaction.serialize({ - requireAllSignatures: false, - verifySignatures: false, - }), - signers, - ); - } - - // async signArweave(transaction) { - // let tx = await genericHandler('signArweave', transaction.toJSON()); - // return arweave.transactions.fromRaw(tx); - // } - // async signEth(transaction) { - // return await genericHandler('signEth', transaction); - // } - async getTaskState(options) { - if (taskNodeAdministered) { - const response = await genericHandler('getTaskState', options); - if (response.error) { - console.log('Error in getting task state', response.error); - return null; - } - return response; - } else { - return this.#testingTaskState; - } - } - - async logMessage(level, message, action) { - switch (level) { - case LogLevel.Log: - console.log(message, action); - break; - case LogLevel.Warn: - console.warn(message, action); - break; - case LogLevel.Error: - console.error(message, action); - break; - default: - console.log( - `Invalid log level: ${level}. The log levels can be log, warn or error`, - ); - return false; - } - return true; - } - - /** - * This logger function is used to log the task erros , warnings and logs on desktop-node - * @param {level} enum // Receive method ["Log", "Warn", "Error"] - enum LogLevel { - Log = 'log', - Warn = 'warn', - Error = 'error', - } - * @param {message} string // log, error or warning message - * @returns {boolean} // true if the message is logged successfully otherwise false - */ - - async logger(level, message, action) { - if (taskNodeAdministered) { - return await genericHandler('logger', level, message, action); - } else { - return await this.logMessage(level, message, action); - } - } - - async auditSubmission(candidatePubkey, isValid, voterKeypair, round) { - if (taskNodeAdministered) { - return await genericHandler( - 'auditSubmission', - candidatePubkey, - isValid, - round, - ); - } else { - if ( - this.#testingTaskState.submissions_audit_trigger[round] && - this.#testingTaskState.submissions_audit_trigger[round][candidatePubkey] - ) { - this.#testingTaskState.submissions_audit_trigger[round][ - candidatePubkey - ].votes.push({ - is_valid: isValid, - voter: voterKeypair.pubKey.toBase58(), - slot: 100, - }); - } else { - this.#testingTaskState.submissions_audit_trigger[round] = { - [candidatePubkey]: { - trigger_by: this.#testingStakingSystemAccount.publicKey.toBase58(), - slot: 100, - votes: [], - }, - }; - } - } - } - - async distributionListAuditSubmission( - candidatePubkey, - isValid, - voterKeypair, - round, - ) { - if (taskNodeAdministered) { - return await genericHandler( - 'distributionListAuditSubmission', - candidatePubkey, - isValid, - round, - ); - } else { - if ( - this.#testingTaskState.distributions_audit_trigger[round] && - this.#testingTaskState.distributions_audit_trigger[round][ - candidatePubkey - ] - ) { - this.#testingTaskState.distributions_audit_trigger[round][ - candidatePubkey - ].votes.push({ - is_valid: isValid, - voter: voterKeypair.pubKey.toBase58(), - slot: 100, - }); - } else { - this.#testingTaskState.distributions_audit_trigger[round] = { - [candidatePubkey]: { - trigger_by: this.#testingStakingSystemAccount.publicKey.toBase58(), - slot: 100, - votes: [], - }, - }; - } - } - } - - async getRound() { - if (taskNodeAdministered) { - return await genericHandler('getRound'); - } else { - return 1; - } - } - - async payoutTrigger(round) { - if (taskNodeAdministered) { - return await genericHandler('payloadTrigger', round); - } else { - console.log( - 'Payout Trigger only handles possitive flows (Without audits)', - ); - let round = 1; - const submissionValAcc = - this.#testingDistributionList[round][ - this.#testingStakingSystemAccount.toBase58() - ].submission_value; - this.#testingTaskState.available_balances = - this.#testingDistributionList[round][submissionValAcc]; - } - } - - async uploadDistributionList(distributionList, round) { - if (taskNodeAdministered) { - return await genericHandler( - 'uploadDistributionList', - distributionList, - round, - ); - } else { - if (!this.#testingDistributionList[round]) - this.#testingDistributionList[round] = {}; - - this.#testingDistributionList[round][ - this.#testingStakingSystemAccount.publicKey.toBase58() - ] = Buffer.from(JSON.stringify(distributionList)); - return true; - } - } - - async distributionListSubmissionOnChain(round) { - if (taskNodeAdministered) { - return await genericHandler('distributionListSubmissionOnChain', round); - } else { - if (!this.#testingTaskState.distribution_rewards_submission[round]) - this.#testingTaskState.distribution_rewards_submission[round] = {}; - - this.#testingTaskState.distribution_rewards_submission[round][ - this.#testingStakingSystemAccount.publicKey.toBase58() - ] = { - submission_value: - this.#testingStakingSystemAccount.publicKey.toBase58(), - slot: 200, - round: 1, - }; - } - } - - async checkSubmissionAndUpdateRound(submissionValue = 'default', round) { - if (taskNodeAdministered) { - return await genericHandler( - 'checkSubmissionAndUpdateRound', - submissionValue, - round, - ); - } else { - if (!this.#testingTaskState.submissions[round]) - this.#testingTaskState.submissions[round] = {}; - this.#testingTaskState.submissions[round][ - this.#testingStakingSystemAccount.publicKey.toBase58() - ] = { - submission_value: submissionValue, - slot: 100, - round, - }; - } - } - async getProgramAccounts() { - if (taskNodeAdministered) { - return await genericHandler('getProgramAccounts'); - } else { - console.log('Cannot call getProgramAccounts in testing mode'); - } - } - async defaultTaskSetup() { - if (taskNodeAdministered) { - return await genericHandler('defaultTaskSetup'); - } else { - if (this.#testingTaskState) return; - this.#testingMainSystemAccount = new Keypair(); - this.#testingStakingSystemAccount = new Keypair(); - this.#testingDistributionList = {}; - this.#testingTaskState = { - task_name: 'DummyTestState', - task_description: 'Dummy Task state for testing flow', - submissions: {}, - submissions_audit_trigger: {}, - total_bounty_amount: 10000000000, - bounty_amount_per_round: 1000000000, - total_stake_amount: 50000000000, - minimum_stake_amount: 5000000000, - available_balances: {}, - stake_list: {}, - round_time: 600, - starting_slot: 0, - audit_window: 200, - submission_window: 200, - distribution_rewards_submission: {}, - distributions_audit_trigger: {}, - }; - } - } - async getRpcUrl() { - if (taskNodeAdministered) { - return await genericHandler('getRpcUrl'); - } else { - console.log('Cannot call getNodes in testing mode'); - } - } - async getNodes(url) { - if (taskNodeAdministered) { - return await genericHandler('getNodes', url); - } else { - console.log('Cannot call getNodes in testing mode'); - } - } - - async getDistributionList(publicKey, round) { - if (taskNodeAdministered) { - const response = await genericHandler( - 'getDistributionList', - publicKey, - round, - ); - if (response.error) { - return null; - } - return response; - } else { - const submissionValAcc = - this.#testingTaskState.distribution_rewards_submission[round][ - this.#testingStakingSystemAccount.publicKey.toBase58() - ].submission_value; - return this.#testingDistributionList[round][submissionValAcc]; - } - } - - async getTaskSubmissionInfo(round) { - if (taskNodeAdministered) { - const taskSubmissionInfo = await genericHandler( - 'getTaskSubmissionInfo', - round, - ); - if (taskSubmissionInfo.error) { - return null; - } - return taskSubmissionInfo; - } else { - // console.log(this.#testingTaskState) - return this.#testingTaskState; - } - } - - async validateAndVoteOnNodes(validate, round) { - console.log('******/ IN VOTING /******'); - let taskAccountDataJSON = null; - try { - taskAccountDataJSON = await this.getTaskSubmissionInfo(round); - } catch (error) { - console.error('Error in getting submissions for the round', error); - } - if (taskAccountDataJSON == null) { - console.log('No submissions found for the round', round); - return; - } - console.log( - `Fetching the submissions of round ${round}`, - taskAccountDataJSON.submissions[round], - ); - const submissions = taskAccountDataJSON.submissions[round]; - if (submissions == null) { - console.log(`No submisssions found in round ${round}`); - return `No submisssions found in round ${round}`; - } else { - const keys = Object.keys(submissions); - const values = Object.values(submissions); - const size = values.length; - console.log('Submissions from last round: ', keys, values, size); - let isValid; - const submitterAccountKeyPair = await this.getSubmitterAccount(); - const submitterPubkey = submitterAccountKeyPair.publicKey.toBase58(); - for (let i = 0; i < size; i++) { - let candidatePublicKey = keys[i]; - console.log('FOR CANDIDATE KEY', candidatePublicKey); - let candidateKeyPairPublicKey = new PublicKey(keys[i]); - if (candidatePublicKey == submitterPubkey && taskNodeAdministered) { - console.log('YOU CANNOT VOTE ON YOUR OWN SUBMISSIONS'); - } else { - try { - console.log( - 'SUBMISSION VALUE TO CHECK', - values[i].submission_value, - ); - isValid = await validate(values[i].submission_value, round); - console.log(`Voting ${isValid} to ${candidatePublicKey}`); - - if (isValid) { - // check for the submissions_audit_trigger , if it exists then vote true on that otherwise do nothing - const submissions_audit_trigger = - taskAccountDataJSON.submissions_audit_trigger[round]; - console.log('SUBMIT AUDIT TRIGGER', submissions_audit_trigger); - // console.log( - // "CANDIDATE PUBKEY CHECK IN AUDIT TRIGGER", - // submissions_audit_trigger[candidatePublicKey] - // ); - if ( - submissions_audit_trigger && - submissions_audit_trigger[candidatePublicKey] - ) { - console.log('VOTING TRUE ON AUDIT'); - const response = await this.auditSubmission( - candidateKeyPairPublicKey, - isValid, - submitterAccountKeyPair, - round, - ); - console.log('RESPONSE FROM AUDIT FUNCTION', response); - } - } else if (isValid == false) { - // Call auditSubmission function and isValid is passed as false - console.log('RAISING AUDIT / VOTING FALSE'); - const response = await this.auditSubmission( - candidateKeyPairPublicKey, - isValid, - submitterAccountKeyPair, - round, - ); - console.log('RESPONSE FROM AUDIT FUNCTION', response); - } - } catch (err) { - console.log('ERROR IN ELSE CONDITION', err); - } - } - } - } - } - - async getTaskDistributionInfo(round) { - if (taskNodeAdministered) { - const taskDistributionInfo = await genericHandler( - 'getTaskDistributionInfo', - round, - ); - if (taskDistributionInfo.error) { - return null; - } - return taskDistributionInfo; - } else { - return this.#testingTaskState; - } - } - - async validateAndVoteOnDistributionList(validateDistribution, round) { - // await this.checkVoteStatus(); - console.log('******/ IN VOTING OF DISTRIBUTION LIST /******'); - let taskAccountDataJSON = null; - try { - taskAccountDataJSON = await this.getTaskDistributionInfo(round); - } catch (error) { - console.error('Error in getting distributions for the round', error); - } - if (taskAccountDataJSON == null) { - console.log('No distribution submissions found for the round', round); - return; - } - console.log( - `Fetching the Distribution submissions of round ${round}`, - taskAccountDataJSON.distribution_rewards_submission[round], - ); - const submissions = - taskAccountDataJSON?.distribution_rewards_submission[round]; - if ( - submissions == null || - submissions == undefined || - submissions.length == 0 - ) { - console.log(`No submisssions found in round ${round}`); - return `No submisssions found in round ${round}`; - } else { - const keys = Object.keys(submissions); - const values = Object.values(submissions); - const size = values.length; - console.log( - 'Distribution Submissions from last round: ', - keys, - values, - size, - ); - let isValid; - const submitterAccountKeyPair = await this.getSubmitterAccount(); - const submitterPubkey = submitterAccountKeyPair.publicKey.toBase58(); - - for (let i = 0; i < size; i++) { - let candidatePublicKey = keys[i]; - console.log('FOR CANDIDATE KEY', candidatePublicKey); - let candidateKeyPairPublicKey = new PublicKey(keys[i]); - if (candidatePublicKey == submitterPubkey) { - console.log('YOU CANNOT VOTE ON YOUR OWN DISTRIBUTION SUBMISSIONS'); - } else { - try { - console.log( - 'DISTRIBUTION SUBMISSION VALUE TO CHECK', - values[i].submission_value, - ); - isValid = await validateDistribution( - values[i].submission_value, - round, - ); - console.log(`Voting ${isValid} to ${candidatePublicKey}`); - - if (isValid) { - // check for the submissions_audit_trigger , if it exists then vote true on that otherwise do nothing - const distributions_audit_trigger = - taskAccountDataJSON.distributions_audit_trigger[round]; - console.log( - 'SUBMIT DISTRIBUTION AUDIT TRIGGER', - distributions_audit_trigger, - ); - // console.log( - // "CANDIDATE PUBKEY CHECK IN AUDIT TRIGGER", - // distributions_audit_trigger[candidatePublicKey] - // ); - if ( - distributions_audit_trigger && - distributions_audit_trigger[candidatePublicKey] - ) { - console.log('VOTING TRUE ON DISTRIBUTION AUDIT'); - const response = await this.distributionListAuditSubmission( - candidateKeyPairPublicKey, - isValid, - submitterAccountKeyPair, - round, - ); - console.log( - 'RESPONSE FROM DISTRIBUTION AUDIT FUNCTION', - response, - ); - } - } else if (isValid == false) { - // Call auditSubmission function and isValid is passed as false - console.log('RAISING AUDIT / VOTING FALSE ON DISTRIBUTION'); - const response = await this.distributionListAuditSubmission( - candidateKeyPairPublicKey, - isValid, - submitterAccountKeyPair, - round, - ); - console.log( - 'RESPONSE FROM DISTRIBUTION AUDIT FUNCTION', - response, - ); - } - } catch (err) { - console.log('ERROR IN ELSE CONDITION FOR DISTRIBUTION', err); - } - } - } - } - } - async getTaskLevelDBPath() { - if (taskNodeAdministered) { - return await genericHandler('getTaskLevelDBPath'); - } else { - return './KOIIDB'; - } - } - async getBasePath() { - if (taskNodeAdministered) { - const basePath = (await namespaceWrapper.getTaskLevelDBPath()).replace( - '/KOIIDB', - '', - ); - return basePath; - } else { - return './'; - } - } - - async getAverageSlotTime() { - if (taskNodeAdministered) { - try { - return await genericHandler('getAverageSlotTime'); - } catch (error) { - console.error('Error getting average slot time', error); - return 400; - } - } else { - return 400; - } - } - - async nodeSelectionDistributionList(round, isPreviousFailed) { - let taskAccountDataJSON = null; - try { - taskAccountDataJSON = await this.getTaskSubmissionInfo(round); - } catch (error) { - console.error('Task submission not found', error); - return; - } - - if (taskAccountDataJSON == null) { - console.error('Task state not found'); - return; - } - console.log('EXPECTED ROUND', round); - - const submissions = taskAccountDataJSON.submissions[round]; - if (submissions == null) { - console.log('No submisssions found in N-1 round'); - return 'No submisssions found in N-1 round'; - } else { - // getting last 3 submissions for the rounds - let keys; - const latestRounds = [round, round - 1, round - 2].filter(r => r >= 0); - - const promises = latestRounds.map(async r => { - if (r == round) { - return new Set(Object.keys(submissions)); - } else { - let roundSubmissions = null; - try { - roundSubmissions = await this.getTaskSubmissionInfo(r); - if (roundSubmissions && roundSubmissions.submissions[r]) { - return new Set(Object.keys(roundSubmissions.submissions[r])); - } - } catch (error) { - console.error('Error in getting submissions for the round', error); - } - return new Set(); - } - }); - - const keySets = await Promise.all(promises); - - // Find the keys present in all the rounds - keys = - keySets.length > 0 - ? [...keySets[0]].filter(key => keySets.every(set => set.has(key))) - : []; - if (keys.length == 0) { - console.log('No common keys found in last 3 rounds'); - keys = Object.keys(submissions); - } - console.log('KEYS', keys.length); - const values = keys.map(key => submissions[key]); - - let size = keys.length; - console.log('Submissions from N-2 round: ', size); - - // Check the keys i.e if the submitter shall be excluded or not - try { - const distributionData = await this.getTaskDistributionInfo(round); - const audit_record = distributionData?.distributions_audit_record; - if (audit_record && audit_record[round] == 'PayoutFailed') { - console.log('ROUND DATA', audit_record[round]); - console.log( - 'SUBMITTER LIST', - distributionData.distribution_rewards_submission[round], - ); - const submitterList = - distributionData.distribution_rewards_submission[round]; - const submitterKeys = Object.keys(submitterList); - console.log('SUBMITTER KEYS', submitterKeys); - const submitterSize = submitterKeys.length; - console.log('SUBMITTER SIZE', submitterSize); - - for (let j = 0; j < submitterSize; j++) { - console.log('SUBMITTER KEY CANDIDATE', submitterKeys[j]); - const id = keys.indexOf(submitterKeys[j]); - console.log('ID', id); - if (id != -1) { - keys.splice(id, 1); - values.splice(id, 1); - size--; - } - } - - console.log('KEYS FOR HASH CALC', keys.length); - } - } catch (error) { - console.log('Error in getting distribution data', error); - } - - // calculating the digest - - const ValuesString = JSON.stringify(values); - - const hashDigest = createHash('sha256') - .update(ValuesString) - .digest('hex'); - - console.log('HASH DIGEST', hashDigest); - - // function to calculate the score - const calculateScore = (str = '') => { - return str.split('').reduce((acc, val) => { - return acc + val.charCodeAt(0); - }, 0); - }; - - // function to compare the ASCII values - - const compareASCII = (str1, str2) => { - const firstScore = calculateScore(str1); - const secondScore = calculateScore(str2); - return Math.abs(firstScore - secondScore); - }; - - // loop through the keys and select the one with higest score - - const selectedNode = { - score: 0, - pubkey: '', - }; - let score = 0; - if (isPreviousFailed) { - let leastScore = -Infinity; - let secondLeastScore = -Infinity; - for (let i = 0; i < size; i++) { - const candidateSubmissionJson = {}; - candidateSubmissionJson[keys[i]] = values[i]; - const candidateSubmissionString = JSON.stringify( - candidateSubmissionJson, - ); - const candidateSubmissionHash = createHash('sha256') - .update(candidateSubmissionString) - .digest('hex'); - const candidateScore = compareASCII( - hashDigest, - candidateSubmissionHash, - ); - if (candidateScore > leastScore) { - secondLeastScore = leastScore; - leastScore = candidateScore; - } else if (candidateScore > secondLeastScore) { - secondLeastScore = candidateScore; - selectedNode.score = candidateScore; - selectedNode.pubkey = keys[i]; - } - } - } else { - for (let i = 0; i < size; i++) { - const candidateSubmissionJson = {}; - candidateSubmissionJson[keys[i]] = values[i]; - const candidateSubmissionString = JSON.stringify( - candidateSubmissionJson, - ); - const candidateSubmissionHash = createHash('sha256') - .update(candidateSubmissionString) - .digest('hex'); - const candidateScore = compareASCII( - hashDigest, - candidateSubmissionHash, - ); - // console.log('CANDIDATE SCORE', candidateScore); - if (candidateScore > score) { - score = candidateScore; - selectedNode.score = candidateScore; - selectedNode.pubkey = keys[i]; - } - } - } - - console.log('SELECTED NODE OBJECT', selectedNode); - return selectedNode.pubkey; - } - } - - async selectAndGenerateDistributionList( - submitDistributionList, - round, - isPreviousRoundFailed, - ) { - console.log('SelectAndGenerateDistributionList called'); - const selectedNode = await this.nodeSelectionDistributionList( - round, - isPreviousRoundFailed, - ); - console.log('Selected Node', selectedNode); - const submitPubKey = await this.getSubmitterAccount(); - if ( - selectedNode == undefined || - selectedNode == '' || - submitPubKey == undefined - ) - return; - if (selectedNode == submitPubKey?.publicKey.toBase58()) { - await submitDistributionList(round); - const taskState = await this.getTaskState({}); - if (taskState == null) { - console.error('Task state not found'); - return; - } - const avgSlotTime = await this.getAverageSlotTime(); - if (avgSlotTime == null) { - console.error('Avg slot time not found'); - return; - } - setTimeout(async () => { - await this.payoutTrigger(round); - }, (taskState.audit_window + taskState.submission_window) * avgSlotTime); - } - } - - getMainAccountPubkey() { - if (taskNodeAdministered) { - return MAIN_ACCOUNT_PUBKEY; - } else { - return this.#testingMainSystemAccount.publicKey.toBase58(); - } - } -} - -async function genericHandler(...args) { - try { - let response = await axios.post(BASE_ROOT_URL, { - args, - taskId: TASK_ID, - secret: SECRET_KEY, - }); - if (response.status == 200) return response.data.response; - else { - console.error(response.status, response.data); - return null; - } - } catch (err) { - console.error(`Error in genericHandler: "${args[0]}"`, err.message); - console.error(err?.response?.data); - return { error: err }; - } -} - -const namespaceWrapper = new NamespaceWrapper(); -if (taskNodeAdministered) { - namespaceWrapper.getRpcUrl().then(rpcUrl => { - console.log(rpcUrl, 'RPC URL'); - connection = new Connection(rpcUrl, 'confirmed'); - }); -} -module.exports = { - namespaceWrapper, - taskNodeAdministered, // Boolean flag indicating that the task is being ran in active mode (Task node supervised), or development (testing) mode - app, // The initialized express app to be used to register endpoints - TASK_ID, // This will be the PORT on which the this task is expected to run the express server coming from the task node running this task. As all communication via the task node and this task will be done on this port. - MAIN_ACCOUNT_PUBKEY, // This will be the secret used to authenticate with task node running this task. - SECRET_KEY, // This will be the secret used by the task to authenticate with task node running this task. - K2_NODE_URL, // This will be K2 url being used by the task node, possible values are 'https://k2-testnet.koii.live' | 'https://k2-devnet.koii.live' | 'http://localhost:8899' - SERVICE_URL, // This will be public task node endpoint (Or local if it doesn't have any) of the task node running this task. - STAKE, // This will be stake of the task node running this task, can be double checked with the task state and staking public key. - TASK_NODE_PORT, // This will be the port used by task node as the express server port, so it can be used by the task for the communication with the task node - _server, // Express server object -}; diff --git a/Lesson 2/upnp-basics/after/coreLogic.js b/Lesson 2/upnp-basics/after/coreLogic.js index 7b09d08..b210551 100644 --- a/Lesson 2/upnp-basics/after/coreLogic.js +++ b/Lesson 2/upnp-basics/after/coreLogic.js @@ -1,4 +1,4 @@ -const { namespaceWrapper } = require('./_koiiNode/koiiNode'); +const { namespaceWrapper } = require('@_koii/namespace-wrapper'); const task = require('./task'); class CoreLogic { diff --git a/Lesson 2/upnp-basics/after/index.js b/Lesson 2/upnp-basics/after/index.js index b14447e..8e983a1 100644 --- a/Lesson 2/upnp-basics/after/index.js +++ b/Lesson 2/upnp-basics/after/index.js @@ -4,7 +4,7 @@ const { namespaceWrapper, taskNodeAdministered, app, -} = require('./_koiiNode/koiiNode'); +} = require('@_koii/namespace-wrapper'); const setupRoutes = require('./routes'); if (app) { @@ -44,13 +44,13 @@ async function setup() { /* GUIDE TO CALLS K2 FUNCTIONS MANUALLY - If you wish to do the development by avoiding the timers then you can do the intended calls to K2 - directly using these function calls. + If you wish to do the development by avoiding the timers then you can do the intended calls to K2 + directly using these function calls. To disable timers please set the TIMERS flag in task-node ENV to disable NOTE : K2 will still have the windows to accept the submission value, audit, so you are expected - to make calls in the intended slots of your round time. + to make calls in the intended slots of your round time. */ diff --git a/Lesson 2/upnp-basics/after/package.json b/Lesson 2/upnp-basics/after/package.json index 0406a57..e1532ee 100644 --- a/Lesson 2/upnp-basics/after/package.json +++ b/Lesson 2/upnp-basics/after/package.json @@ -16,6 +16,8 @@ "license": "ISC", "dependencies": { "@_koi/web3.js": "^0.0.6", + "@_koii/namespace-wrapper": "^1.0.1", + "@_koii/storage-task-sdk": "^1.2.4", "axios": "^0.27.2", "body-parser": "^1.20.2", "cross-spawn": "^7.0.3", diff --git a/Lesson 2/upnp-basics/after/task/audit.js b/Lesson 2/upnp-basics/after/task/audit.js index b7ae5a8..2e91f41 100644 --- a/Lesson 2/upnp-basics/after/task/audit.js +++ b/Lesson 2/upnp-basics/after/task/audit.js @@ -1,4 +1,4 @@ -const { namespaceWrapper } = require('../_koiiNode/koiiNode'); +const { namespaceWrapper } = require('@_koii/namespace-wrapper'); class Audit { /** @@ -12,7 +12,7 @@ class Audit { let vote; console.log('SUBMISSION VALUE', submission_value, round); try { - // Verify the type of value is string + // Verify the type of value is string if (typeof submission_value === 'string' && submission_value.length > 0) { vote = true; } else { diff --git a/Lesson 2/upnp-basics/after/task/distribution.js b/Lesson 2/upnp-basics/after/task/distribution.js index 6b7c83b..f07071c 100644 --- a/Lesson 2/upnp-basics/after/task/distribution.js +++ b/Lesson 2/upnp-basics/after/task/distribution.js @@ -1,4 +1,4 @@ -const { namespaceWrapper } = require('../_koiiNode/koiiNode'); +const { namespaceWrapper } = require('@_koii/namespace-wrapper'); class Distribution { /** diff --git a/Lesson 2/upnp-basics/after/task/submission.js b/Lesson 2/upnp-basics/after/task/submission.js index 9bef0c3..78eb3d1 100644 --- a/Lesson 2/upnp-basics/after/task/submission.js +++ b/Lesson 2/upnp-basics/after/task/submission.js @@ -1,4 +1,4 @@ -const { namespaceWrapper, TASK_ID } = require('../_koiiNode/koiiNode'); +const { namespaceWrapper, TASK_ID } = require('@_koii/namespace-wrapper'); const { default: axios } = require('axios'); const getData = require('./getData'); diff --git a/Lesson 2/upnp-basics/after/tests/main.test.js b/Lesson 2/upnp-basics/after/tests/main.test.js index 1eb8a52..05f5352 100644 --- a/Lesson 2/upnp-basics/after/tests/main.test.js +++ b/Lesson 2/upnp-basics/after/tests/main.test.js @@ -1,5 +1,5 @@ const { coreLogic } = require('../coreLogic'); -const { namespaceWrapper, _server } = require('../_koiiNode/koiiNode'); +const { namespaceWrapper, _server } = require('@_koii/namespace-wrapper'); const Joi = require('joi'); const axios = require('axios'); beforeAll(async () => { diff --git a/Lesson 2/upnp-basics/before/_koiiNode/koiiNode.js b/Lesson 2/upnp-basics/before/_koiiNode/koiiNode.js deleted file mode 100644 index 852bdb6..0000000 --- a/Lesson 2/upnp-basics/before/_koiiNode/koiiNode.js +++ /dev/null @@ -1,1156 +0,0 @@ -const { default: axios } = require('axios'); -const { createHash } = require('crypto'); - -const { Connection, PublicKey, Keypair } = require('@_koi/web3.js'); - -const Datastore = require('nedb-promises'); -const fsPromises = require('fs/promises'); -const bs58 = require('bs58'); -const nacl = require('tweetnacl'); - -/****************************************** init.js ***********************************/ - -const express = require('express'); -// Only used for testing purposes, in production the env will be injected by tasknode -require('dotenv').config(); -const bodyParser = require('body-parser'); -/** - * This will be the name of the current task as coming from the task node running this task. - */ -const TASK_NAME = process.argv[2] || 'Local'; -/** - * This will be the id of the current task as coming from the task node running this task. - */ -const TASK_ID = process.argv[3]; -/** - * This will be the PORT on which the this task is expected to run the express server coming from the task node running this task. - * As all communication via the task node and this task will be done on this port. - */ -const EXPRESS_PORT = process.argv[4] || 10000; - -const LogLevel = { - Log: 'log', - Warn: 'warn', - Error: 'error', -}; - -// Not used anymore -// const NODE_MODE = process.argv[5]; - -/** - * This will be the main account public key in string format of the task node running this task. - */ -const MAIN_ACCOUNT_PUBKEY = process.argv[6]; -/** - * This will be the secret used by the task to authenticate with task node running this task. - */ -const SECRET_KEY = process.argv[7]; -/** - * This will be K2 url being used by the task node, possible values are 'https://k2-testnet.koii.live' | 'https://k2-devnet.koii.live' | 'http://localhost:8899' - */ -const K2_NODE_URL = process.argv[8] || 'https://k2-testnet.koii.live'; -/** - * This will be public task node endpoint (Or local if it doesn't have any) of the task node running this task. - */ -const SERVICE_URL = process.argv[9]; -/** - * This will be stake of the task node running this task, can be double checked with the task state and staking public key. - */ -const STAKE = Number(process.argv[10]); -/** - * This will be the port used by task node as the express server port, so it can be used by the task for the communication with the task node - */ -const TASK_NODE_PORT = Number(process.argv[11]); - -const app = express(); - -console.log('SETTING UP EXPRESS'); - -app.use(bodyParser.urlencoded({ extended: false })); - -app.use(bodyParser.json()); - -app.use((req, res, next) => { - res.setHeader('Access-Control-Allow-Origin', '*'); - res.setHeader( - 'Access-Control-Allow-Methods', - 'GET, POST, PUT, PATCH, DELETE', - ); - res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); - res.setHeader('Access-Control-Allow-Credentials', false); - if (req.method === 'OPTIONS') - // if is preflight(OPTIONS) then response status 204(NO CONTENT) - return res.send(204); - next(); -}); - -app.get('/', (req, res) => { - res.send('Hello World!'); -}); - -const _server = app.listen(EXPRESS_PORT, () => { - console.log(`${TASK_NAME} listening on port ${EXPRESS_PORT}`); -}); - -/****************************************** NamespaceWrapper.js ***********************************/ - -const taskNodeAdministered = !!TASK_ID; -const BASE_ROOT_URL = `http://localhost:${TASK_NODE_PORT}/namespace-wrapper`; -let connection; - -class NamespaceWrapper { - #db; - #testingMainSystemAccount; - #testingStakingSystemAccount; - #testingTaskState; - #testingDistributionList; - - constructor() { - if (taskNodeAdministered) { - this.initializeDB(); - } else { - this.#db = Datastore.create('./localKOIIDB.db'); - this.defaultTaskSetup(); - } - } - - async initializeDB() { - if (this.#db) return; - try { - if (taskNodeAdministered) { - const path = await this.getTaskLevelDBPath(); - this.#db = Datastore.create(path); - } else { - this.#db = Datastore.create('./localKOIIDB.db'); - } - } catch (e) { - this.#db = Datastore.create(`../namespace/${TASK_ID}/KOIILevelDB.db`); - } - } - - async getDb() { - if (this.#db) return this.#db; - await this.initializeDB(); - return this.#db; - } - /** - * Namespace wrapper of storeGetAsync - * @param {string} key // Path to get - */ - async storeGet(key) { - try { - await this.initializeDB(); - const resp = await this.#db.findOne({ key: key }); - if (resp) { - return resp[key]; - } else { - return null; - } - } catch (e) { - console.error(e); - return null; - } - } - /** - * Namespace wrapper over storeSetAsync - * @param {string} key Path to set - * @param {*} value Data to set - */ - async storeSet(key, value) { - try { - await this.initializeDB(); - await this.#db.update( - { key: key }, - { [key]: value, key }, - { upsert: true }, - ); - } catch (e) { - console.error(e); - return undefined; - } - } - - /** - * Namespace wrapper over fsPromises methods - * @param {*} method The fsPromise method to call - * @param {*} path Path for the express call - * @param {...any} args Remaining parameters for the FS call - */ - async fs(method, path, ...args) { - if (taskNodeAdministered) { - return await genericHandler('fs', method, path, ...args); - } else { - return fsPromises[method](`${path}`, ...args); - } - } - async fsStaking(method, path, ...args) { - if (taskNodeAdministered) { - return await genericHandler('fsStaking', method, path, ...args); - } else { - return fsPromises[method](`${path}`, ...args); - } - } - - async fsWriteStream(imagepath) { - if (taskNodeAdministered) { - return await genericHandler('fsWriteStream', imagepath); - } else { - const writer = createWriteStream(imagepath); - return writer; - } - } - async fsReadStream(imagepath) { - if (taskNodeAdministered) { - return await genericHandler('fsReadStream', imagepath); - } else { - const file = readFileSync(imagepath); - return file; - } - } - - /** - * Namespace wrapper for getting current slots - */ - async getSlot() { - if (taskNodeAdministered) { - return await genericHandler('getCurrentSlot'); - } else { - return 100; - } - } - - async payloadSigning(body) { - if (taskNodeAdministered) { - return await genericHandler('signData', body); - } else { - const msg = new TextEncoder().encode(JSON.stringify(body)); - const signedMessage = nacl.sign( - msg, - this.#testingMainSystemAccount.secretKey, - ); - return await this.bs58Encode(signedMessage); - } - } - - async bs58Encode(data) { - return bs58.encode( - Buffer.from(data.buffer, data.byteOffset, data.byteLength), - ); - } - - async bs58Decode(data) { - return new Uint8Array(bs58.decode(data)); - } - - decodePayload(payload) { - return new TextDecoder().decode(payload); - } - - /** - * Namespace wrapper of storeGetAsync - * @param {string} signedMessage r // Path to get - */ - - async verifySignature(signedMessage, pubKey) { - if (taskNodeAdministered) { - return await genericHandler('verifySignedData', signedMessage, pubKey); - } else { - try { - const payload = nacl.sign.open( - await this.bs58Decode(signedMessage), - await this.bs58Decode(pubKey), - ); - if (!payload) return { error: 'Invalid signature' }; - return { data: this.decodePayload(payload) }; - } catch (e) { - console.error(e); - return { error: `Verification failed: ${e}` }; - } - } - } - - // async submissionOnChain(submitterKeypair, submission) { - // return await genericHandler( - // 'submissionOnChain', - // submitterKeypair, - // submission, - // ); - // } - - async stakeOnChain( - taskStateInfoPublicKey, - stakingAccKeypair, - stakePotAccount, - stakeAmount, - ) { - if (taskNodeAdministered) { - return await genericHandler( - 'stakeOnChain', - taskStateInfoPublicKey, - stakingAccKeypair, - stakePotAccount, - stakeAmount, - ); - } else { - this.#testingTaskState.stake_list[ - this.#testingStakingSystemAccount.publicKey.toBase58() - ] = stakeAmount; - } - } - async claimReward(stakePotAccount, beneficiaryAccount, claimerKeypair) { - if (!taskNodeAdministered) { - console.log('Cannot call sendTransaction in testing mode'); - return; - } - return await genericHandler( - 'claimReward', - stakePotAccount, - beneficiaryAccount, - claimerKeypair, - ); - } - async sendTransaction(serviceNodeAccount, beneficiaryAccount, amount) { - if (!taskNodeAdministered) { - console.log('Cannot call sendTransaction in testing mode'); - return; - } - return await genericHandler( - 'sendTransaction', - serviceNodeAccount, - beneficiaryAccount, - amount, - ); - } - - async getSubmitterAccount() { - if (taskNodeAdministered) { - const submitterAccountResp = await genericHandler('getSubmitterAccount'); - return Keypair.fromSecretKey( - Uint8Array.from(Object.values(submitterAccountResp._keypair.secretKey)), - ); - } else { - return this.#testingStakingSystemAccount; - } - } - - /** - * sendAndConfirmTransaction wrapper that injects mainSystemWallet as the first signer for paying the tx fees - * @param {connection} method // Receive method ["get", "post", "put", "delete"] - * @param {transaction} path // Endpoint path appended to namespace - * @param {Function} callback // Callback function on traffic receive - */ - async sendAndConfirmTransactionWrapper(transaction, signers) { - if (!taskNodeAdministered) { - console.log('Cannot call sendTransaction in testing mode'); - return; - } - const blockhash = (await connection.getRecentBlockhash('finalized')) - .blockhash; - transaction.recentBlockhash = blockhash; - transaction.feePayer = new PublicKey(MAIN_ACCOUNT_PUBKEY); - return await genericHandler( - 'sendAndConfirmTransactionWrapper', - transaction.serialize({ - requireAllSignatures: false, - verifySignatures: false, - }), - signers, - ); - } - - // async signArweave(transaction) { - // let tx = await genericHandler('signArweave', transaction.toJSON()); - // return arweave.transactions.fromRaw(tx); - // } - // async signEth(transaction) { - // return await genericHandler('signEth', transaction); - // } - async getTaskState(options) { - if (taskNodeAdministered) { - const response = await genericHandler('getTaskState', options); - if (response.error) { - console.log('Error in getting task state', response.error); - return null; - } - return response; - } else { - return this.#testingTaskState; - } - } - - async logMessage(level, message, action) { - switch (level) { - case LogLevel.Log: - console.log(message, action); - break; - case LogLevel.Warn: - console.warn(message, action); - break; - case LogLevel.Error: - console.error(message, action); - break; - default: - console.log( - `Invalid log level: ${level}. The log levels can be log, warn or error`, - ); - return false; - } - return true; - } - - /** - * This logger function is used to log the task erros , warnings and logs on desktop-node - * @param {level} enum // Receive method ["Log", "Warn", "Error"] - enum LogLevel { - Log = 'log', - Warn = 'warn', - Error = 'error', - } - * @param {message} string // log, error or warning message - * @returns {boolean} // true if the message is logged successfully otherwise false - */ - - async logger(level, message, action) { - if (taskNodeAdministered) { - return await genericHandler('logger', level, message, action); - } else { - return await this.logMessage(level, message, action); - } - } - - async auditSubmission(candidatePubkey, isValid, voterKeypair, round) { - if (taskNodeAdministered) { - return await genericHandler( - 'auditSubmission', - candidatePubkey, - isValid, - round, - ); - } else { - if ( - this.#testingTaskState.submissions_audit_trigger[round] && - this.#testingTaskState.submissions_audit_trigger[round][candidatePubkey] - ) { - this.#testingTaskState.submissions_audit_trigger[round][ - candidatePubkey - ].votes.push({ - is_valid: isValid, - voter: voterKeypair.pubKey.toBase58(), - slot: 100, - }); - } else { - this.#testingTaskState.submissions_audit_trigger[round] = { - [candidatePubkey]: { - trigger_by: this.#testingStakingSystemAccount.publicKey.toBase58(), - slot: 100, - votes: [], - }, - }; - } - } - } - - async distributionListAuditSubmission( - candidatePubkey, - isValid, - voterKeypair, - round, - ) { - if (taskNodeAdministered) { - return await genericHandler( - 'distributionListAuditSubmission', - candidatePubkey, - isValid, - round, - ); - } else { - if ( - this.#testingTaskState.distributions_audit_trigger[round] && - this.#testingTaskState.distributions_audit_trigger[round][ - candidatePubkey - ] - ) { - this.#testingTaskState.distributions_audit_trigger[round][ - candidatePubkey - ].votes.push({ - is_valid: isValid, - voter: voterKeypair.pubKey.toBase58(), - slot: 100, - }); - } else { - this.#testingTaskState.distributions_audit_trigger[round] = { - [candidatePubkey]: { - trigger_by: this.#testingStakingSystemAccount.publicKey.toBase58(), - slot: 100, - votes: [], - }, - }; - } - } - } - - async getRound() { - if (taskNodeAdministered) { - return await genericHandler('getRound'); - } else { - return 1; - } - } - - async payoutTrigger(round) { - if (taskNodeAdministered) { - return await genericHandler('payloadTrigger', round); - } else { - console.log( - 'Payout Trigger only handles possitive flows (Without audits)', - ); - let round = 1; - const submissionValAcc = - this.#testingDistributionList[round][ - this.#testingStakingSystemAccount.toBase58() - ].submission_value; - this.#testingTaskState.available_balances = - this.#testingDistributionList[round][submissionValAcc]; - } - } - - async uploadDistributionList(distributionList, round) { - if (taskNodeAdministered) { - return await genericHandler( - 'uploadDistributionList', - distributionList, - round, - ); - } else { - if (!this.#testingDistributionList[round]) - this.#testingDistributionList[round] = {}; - - this.#testingDistributionList[round][ - this.#testingStakingSystemAccount.publicKey.toBase58() - ] = Buffer.from(JSON.stringify(distributionList)); - return true; - } - } - - async distributionListSubmissionOnChain(round) { - if (taskNodeAdministered) { - return await genericHandler('distributionListSubmissionOnChain', round); - } else { - if (!this.#testingTaskState.distribution_rewards_submission[round]) - this.#testingTaskState.distribution_rewards_submission[round] = {}; - - this.#testingTaskState.distribution_rewards_submission[round][ - this.#testingStakingSystemAccount.publicKey.toBase58() - ] = { - submission_value: - this.#testingStakingSystemAccount.publicKey.toBase58(), - slot: 200, - round: 1, - }; - } - } - - async checkSubmissionAndUpdateRound(submissionValue = 'default', round) { - if (taskNodeAdministered) { - return await genericHandler( - 'checkSubmissionAndUpdateRound', - submissionValue, - round, - ); - } else { - if (!this.#testingTaskState.submissions[round]) - this.#testingTaskState.submissions[round] = {}; - this.#testingTaskState.submissions[round][ - this.#testingStakingSystemAccount.publicKey.toBase58() - ] = { - submission_value: submissionValue, - slot: 100, - round, - }; - } - } - async getProgramAccounts() { - if (taskNodeAdministered) { - return await genericHandler('getProgramAccounts'); - } else { - console.log('Cannot call getProgramAccounts in testing mode'); - } - } - async defaultTaskSetup() { - if (taskNodeAdministered) { - return await genericHandler('defaultTaskSetup'); - } else { - if (this.#testingTaskState) return; - this.#testingMainSystemAccount = new Keypair(); - this.#testingStakingSystemAccount = new Keypair(); - this.#testingDistributionList = {}; - this.#testingTaskState = { - task_name: 'DummyTestState', - task_description: 'Dummy Task state for testing flow', - submissions: {}, - submissions_audit_trigger: {}, - total_bounty_amount: 10000000000, - bounty_amount_per_round: 1000000000, - total_stake_amount: 50000000000, - minimum_stake_amount: 5000000000, - available_balances: {}, - stake_list: {}, - round_time: 600, - starting_slot: 0, - audit_window: 200, - submission_window: 200, - distribution_rewards_submission: {}, - distributions_audit_trigger: {}, - }; - } - } - async getRpcUrl() { - if (taskNodeAdministered) { - return await genericHandler('getRpcUrl'); - } else { - console.log('Cannot call getNodes in testing mode'); - } - } - async getNodes(url) { - if (taskNodeAdministered) { - return await genericHandler('getNodes', url); - } else { - console.log('Cannot call getNodes in testing mode'); - } - } - - async getDistributionList(publicKey, round) { - if (taskNodeAdministered) { - const response = await genericHandler( - 'getDistributionList', - publicKey, - round, - ); - if (response.error) { - return null; - } - return response; - } else { - const submissionValAcc = - this.#testingTaskState.distribution_rewards_submission[round][ - this.#testingStakingSystemAccount.publicKey.toBase58() - ].submission_value; - return this.#testingDistributionList[round][submissionValAcc]; - } - } - - async getTaskSubmissionInfo(round) { - if (taskNodeAdministered) { - const taskSubmissionInfo = await genericHandler( - 'getTaskSubmissionInfo', - round, - ); - if (taskSubmissionInfo.error) { - return null; - } - return taskSubmissionInfo; - } else { - // console.log(this.#testingTaskState) - return this.#testingTaskState; - } - } - - async validateAndVoteOnNodes(validate, round) { - console.log('******/ IN VOTING /******'); - let taskAccountDataJSON = null; - try { - taskAccountDataJSON = await this.getTaskSubmissionInfo(round); - } catch (error) { - console.error('Error in getting submissions for the round', error); - } - if (taskAccountDataJSON == null) { - console.log('No submissions found for the round', round); - return; - } - console.log( - `Fetching the submissions of round ${round}`, - taskAccountDataJSON.submissions[round], - ); - const submissions = taskAccountDataJSON.submissions[round]; - if (submissions == null) { - console.log(`No submisssions found in round ${round}`); - return `No submisssions found in round ${round}`; - } else { - const keys = Object.keys(submissions); - const values = Object.values(submissions); - const size = values.length; - console.log('Submissions from last round: ', keys, values, size); - let isValid; - const submitterAccountKeyPair = await this.getSubmitterAccount(); - const submitterPubkey = submitterAccountKeyPair.publicKey.toBase58(); - for (let i = 0; i < size; i++) { - let candidatePublicKey = keys[i]; - console.log('FOR CANDIDATE KEY', candidatePublicKey); - let candidateKeyPairPublicKey = new PublicKey(keys[i]); - if (candidatePublicKey == submitterPubkey && taskNodeAdministered) { - console.log('YOU CANNOT VOTE ON YOUR OWN SUBMISSIONS'); - } else { - try { - console.log( - 'SUBMISSION VALUE TO CHECK', - values[i].submission_value, - ); - isValid = await validate(values[i].submission_value, round); - console.log(`Voting ${isValid} to ${candidatePublicKey}`); - - if (isValid) { - // check for the submissions_audit_trigger , if it exists then vote true on that otherwise do nothing - const submissions_audit_trigger = - taskAccountDataJSON.submissions_audit_trigger[round]; - console.log('SUBMIT AUDIT TRIGGER', submissions_audit_trigger); - // console.log( - // "CANDIDATE PUBKEY CHECK IN AUDIT TRIGGER", - // submissions_audit_trigger[candidatePublicKey] - // ); - if ( - submissions_audit_trigger && - submissions_audit_trigger[candidatePublicKey] - ) { - console.log('VOTING TRUE ON AUDIT'); - const response = await this.auditSubmission( - candidateKeyPairPublicKey, - isValid, - submitterAccountKeyPair, - round, - ); - console.log('RESPONSE FROM AUDIT FUNCTION', response); - } - } else if (isValid == false) { - // Call auditSubmission function and isValid is passed as false - console.log('RAISING AUDIT / VOTING FALSE'); - const response = await this.auditSubmission( - candidateKeyPairPublicKey, - isValid, - submitterAccountKeyPair, - round, - ); - console.log('RESPONSE FROM AUDIT FUNCTION', response); - } - } catch (err) { - console.log('ERROR IN ELSE CONDITION', err); - } - } - } - } - } - - async getTaskDistributionInfo(round) { - if (taskNodeAdministered) { - const taskDistributionInfo = await genericHandler( - 'getTaskDistributionInfo', - round, - ); - if (taskDistributionInfo.error) { - return null; - } - return taskDistributionInfo; - } else { - return this.#testingTaskState; - } - } - - async validateAndVoteOnDistributionList(validateDistribution, round) { - // await this.checkVoteStatus(); - console.log('******/ IN VOTING OF DISTRIBUTION LIST /******'); - let taskAccountDataJSON = null; - try { - taskAccountDataJSON = await this.getTaskDistributionInfo(round); - } catch (error) { - console.error('Error in getting distributions for the round', error); - } - if (taskAccountDataJSON == null) { - console.log('No distribution submissions found for the round', round); - return; - } - console.log( - `Fetching the Distribution submissions of round ${round}`, - taskAccountDataJSON.distribution_rewards_submission[round], - ); - const submissions = - taskAccountDataJSON?.distribution_rewards_submission[round]; - if ( - submissions == null || - submissions == undefined || - submissions.length == 0 - ) { - console.log(`No submisssions found in round ${round}`); - return `No submisssions found in round ${round}`; - } else { - const keys = Object.keys(submissions); - const values = Object.values(submissions); - const size = values.length; - console.log( - 'Distribution Submissions from last round: ', - keys, - values, - size, - ); - let isValid; - const submitterAccountKeyPair = await this.getSubmitterAccount(); - const submitterPubkey = submitterAccountKeyPair.publicKey.toBase58(); - - for (let i = 0; i < size; i++) { - let candidatePublicKey = keys[i]; - console.log('FOR CANDIDATE KEY', candidatePublicKey); - let candidateKeyPairPublicKey = new PublicKey(keys[i]); - if (candidatePublicKey == submitterPubkey) { - console.log('YOU CANNOT VOTE ON YOUR OWN DISTRIBUTION SUBMISSIONS'); - } else { - try { - console.log( - 'DISTRIBUTION SUBMISSION VALUE TO CHECK', - values[i].submission_value, - ); - isValid = await validateDistribution( - values[i].submission_value, - round, - ); - console.log(`Voting ${isValid} to ${candidatePublicKey}`); - - if (isValid) { - // check for the submissions_audit_trigger , if it exists then vote true on that otherwise do nothing - const distributions_audit_trigger = - taskAccountDataJSON.distributions_audit_trigger[round]; - console.log( - 'SUBMIT DISTRIBUTION AUDIT TRIGGER', - distributions_audit_trigger, - ); - // console.log( - // "CANDIDATE PUBKEY CHECK IN AUDIT TRIGGER", - // distributions_audit_trigger[candidatePublicKey] - // ); - if ( - distributions_audit_trigger && - distributions_audit_trigger[candidatePublicKey] - ) { - console.log('VOTING TRUE ON DISTRIBUTION AUDIT'); - const response = await this.distributionListAuditSubmission( - candidateKeyPairPublicKey, - isValid, - submitterAccountKeyPair, - round, - ); - console.log( - 'RESPONSE FROM DISTRIBUTION AUDIT FUNCTION', - response, - ); - } - } else if (isValid == false) { - // Call auditSubmission function and isValid is passed as false - console.log('RAISING AUDIT / VOTING FALSE ON DISTRIBUTION'); - const response = await this.distributionListAuditSubmission( - candidateKeyPairPublicKey, - isValid, - submitterAccountKeyPair, - round, - ); - console.log( - 'RESPONSE FROM DISTRIBUTION AUDIT FUNCTION', - response, - ); - } - } catch (err) { - console.log('ERROR IN ELSE CONDITION FOR DISTRIBUTION', err); - } - } - } - } - } - async getTaskLevelDBPath() { - if (taskNodeAdministered) { - return await genericHandler('getTaskLevelDBPath'); - } else { - return './KOIIDB'; - } - } - async getBasePath() { - if (taskNodeAdministered) { - const basePath = (await namespaceWrapper.getTaskLevelDBPath()).replace( - '/KOIIDB', - '', - ); - return basePath; - } else { - return './'; - } - } - - async getAverageSlotTime() { - if (taskNodeAdministered) { - try { - return await genericHandler('getAverageSlotTime'); - } catch (error) { - console.error('Error getting average slot time', error); - return 400; - } - } else { - return 400; - } - } - - async nodeSelectionDistributionList(round, isPreviousFailed) { - let taskAccountDataJSON = null; - try { - taskAccountDataJSON = await this.getTaskSubmissionInfo(round); - } catch (error) { - console.error('Task submission not found', error); - return; - } - - if (taskAccountDataJSON == null) { - console.error('Task state not found'); - return; - } - console.log('EXPECTED ROUND', round); - - const submissions = taskAccountDataJSON.submissions[round]; - if (submissions == null) { - console.log('No submisssions found in N-1 round'); - return 'No submisssions found in N-1 round'; - } else { - // getting last 3 submissions for the rounds - let keys; - const latestRounds = [round, round - 1, round - 2].filter(r => r >= 0); - - const promises = latestRounds.map(async r => { - if (r == round) { - return new Set(Object.keys(submissions)); - } else { - let roundSubmissions = null; - try { - roundSubmissions = await this.getTaskSubmissionInfo(r); - if (roundSubmissions && roundSubmissions.submissions[r]) { - return new Set(Object.keys(roundSubmissions.submissions[r])); - } - } catch (error) { - console.error('Error in getting submissions for the round', error); - } - return new Set(); - } - }); - - const keySets = await Promise.all(promises); - - // Find the keys present in all the rounds - keys = - keySets.length > 0 - ? [...keySets[0]].filter(key => keySets.every(set => set.has(key))) - : []; - if (keys.length == 0) { - console.log('No common keys found in last 3 rounds'); - keys = Object.keys(submissions); - } - console.log('KEYS', keys.length); - const values = keys.map(key => submissions[key]); - - let size = keys.length; - console.log('Submissions from N-2 round: ', size); - - // Check the keys i.e if the submitter shall be excluded or not - try { - const distributionData = await this.getTaskDistributionInfo(round); - const audit_record = distributionData?.distributions_audit_record; - if (audit_record && audit_record[round] == 'PayoutFailed') { - console.log('ROUND DATA', audit_record[round]); - console.log( - 'SUBMITTER LIST', - distributionData.distribution_rewards_submission[round], - ); - const submitterList = - distributionData.distribution_rewards_submission[round]; - const submitterKeys = Object.keys(submitterList); - console.log('SUBMITTER KEYS', submitterKeys); - const submitterSize = submitterKeys.length; - console.log('SUBMITTER SIZE', submitterSize); - - for (let j = 0; j < submitterSize; j++) { - console.log('SUBMITTER KEY CANDIDATE', submitterKeys[j]); - const id = keys.indexOf(submitterKeys[j]); - console.log('ID', id); - if (id != -1) { - keys.splice(id, 1); - values.splice(id, 1); - size--; - } - } - - console.log('KEYS FOR HASH CALC', keys.length); - } - } catch (error) { - console.log('Error in getting distribution data', error); - } - - // calculating the digest - - const ValuesString = JSON.stringify(values); - - const hashDigest = createHash('sha256') - .update(ValuesString) - .digest('hex'); - - console.log('HASH DIGEST', hashDigest); - - // function to calculate the score - const calculateScore = (str = '') => { - return str.split('').reduce((acc, val) => { - return acc + val.charCodeAt(0); - }, 0); - }; - - // function to compare the ASCII values - - const compareASCII = (str1, str2) => { - const firstScore = calculateScore(str1); - const secondScore = calculateScore(str2); - return Math.abs(firstScore - secondScore); - }; - - // loop through the keys and select the one with higest score - - const selectedNode = { - score: 0, - pubkey: '', - }; - let score = 0; - if (isPreviousFailed) { - let leastScore = -Infinity; - let secondLeastScore = -Infinity; - for (let i = 0; i < size; i++) { - const candidateSubmissionJson = {}; - candidateSubmissionJson[keys[i]] = values[i]; - const candidateSubmissionString = JSON.stringify( - candidateSubmissionJson, - ); - const candidateSubmissionHash = createHash('sha256') - .update(candidateSubmissionString) - .digest('hex'); - const candidateScore = compareASCII( - hashDigest, - candidateSubmissionHash, - ); - if (candidateScore > leastScore) { - secondLeastScore = leastScore; - leastScore = candidateScore; - } else if (candidateScore > secondLeastScore) { - secondLeastScore = candidateScore; - selectedNode.score = candidateScore; - selectedNode.pubkey = keys[i]; - } - } - } else { - for (let i = 0; i < size; i++) { - const candidateSubmissionJson = {}; - candidateSubmissionJson[keys[i]] = values[i]; - const candidateSubmissionString = JSON.stringify( - candidateSubmissionJson, - ); - const candidateSubmissionHash = createHash('sha256') - .update(candidateSubmissionString) - .digest('hex'); - const candidateScore = compareASCII( - hashDigest, - candidateSubmissionHash, - ); - // console.log('CANDIDATE SCORE', candidateScore); - if (candidateScore > score) { - score = candidateScore; - selectedNode.score = candidateScore; - selectedNode.pubkey = keys[i]; - } - } - } - - console.log('SELECTED NODE OBJECT', selectedNode); - return selectedNode.pubkey; - } - } - - async selectAndGenerateDistributionList( - submitDistributionList, - round, - isPreviousRoundFailed, - ) { - console.log('SelectAndGenerateDistributionList called'); - const selectedNode = await this.nodeSelectionDistributionList( - round, - isPreviousRoundFailed, - ); - console.log('Selected Node', selectedNode); - const submitPubKey = await this.getSubmitterAccount(); - if ( - selectedNode == undefined || - selectedNode == '' || - submitPubKey == undefined - ) - return; - if (selectedNode == submitPubKey?.publicKey.toBase58()) { - await submitDistributionList(round); - const taskState = await this.getTaskState({}); - if (taskState == null) { - console.error('Task state not found'); - return; - } - const avgSlotTime = await this.getAverageSlotTime(); - if (avgSlotTime == null) { - console.error('Avg slot time not found'); - return; - } - setTimeout(async () => { - await this.payoutTrigger(round); - }, (taskState.audit_window + taskState.submission_window) * avgSlotTime); - } - } - - getMainAccountPubkey() { - if (taskNodeAdministered) { - return MAIN_ACCOUNT_PUBKEY; - } else { - return this.#testingMainSystemAccount.publicKey.toBase58(); - } - } -} - -async function genericHandler(...args) { - try { - let response = await axios.post(BASE_ROOT_URL, { - args, - taskId: TASK_ID, - secret: SECRET_KEY, - }); - if (response.status == 200) return response.data.response; - else { - console.error(response.status, response.data); - return null; - } - } catch (err) { - console.error(`Error in genericHandler: "${args[0]}"`, err.message); - console.error(err?.response?.data); - return { error: err }; - } -} - -const namespaceWrapper = new NamespaceWrapper(); -if (taskNodeAdministered) { - namespaceWrapper.getRpcUrl().then(rpcUrl => { - console.log(rpcUrl, 'RPC URL'); - connection = new Connection(rpcUrl, 'confirmed'); - }); -} -module.exports = { - namespaceWrapper, - taskNodeAdministered, // Boolean flag indicating that the task is being ran in active mode (Task node supervised), or development (testing) mode - app, // The initialized express app to be used to register endpoints - TASK_ID, // This will be the PORT on which the this task is expected to run the express server coming from the task node running this task. As all communication via the task node and this task will be done on this port. - MAIN_ACCOUNT_PUBKEY, // This will be the secret used to authenticate with task node running this task. - SECRET_KEY, // This will be the secret used by the task to authenticate with task node running this task. - K2_NODE_URL, // This will be K2 url being used by the task node, possible values are 'https://k2-testnet.koii.live' | 'https://k2-devnet.koii.live' | 'http://localhost:8899' - SERVICE_URL, // This will be public task node endpoint (Or local if it doesn't have any) of the task node running this task. - STAKE, // This will be stake of the task node running this task, can be double checked with the task state and staking public key. - TASK_NODE_PORT, // This will be the port used by task node as the express server port, so it can be used by the task for the communication with the task node - _server, // Express server object -}; diff --git a/Lesson 2/upnp-basics/before/coreLogic.js b/Lesson 2/upnp-basics/before/coreLogic.js index 7b09d08..b210551 100644 --- a/Lesson 2/upnp-basics/before/coreLogic.js +++ b/Lesson 2/upnp-basics/before/coreLogic.js @@ -1,4 +1,4 @@ -const { namespaceWrapper } = require('./_koiiNode/koiiNode'); +const { namespaceWrapper } = require('@_koii/namespace-wrapper'); const task = require('./task'); class CoreLogic { diff --git a/Lesson 2/upnp-basics/before/index.js b/Lesson 2/upnp-basics/before/index.js index 99607ec..5cc9df9 100644 --- a/Lesson 2/upnp-basics/before/index.js +++ b/Lesson 2/upnp-basics/before/index.js @@ -3,7 +3,7 @@ const { namespaceWrapper, taskNodeAdministered, app, -} = require('./_koiiNode/koiiNode'); +} = require('@_koii/namespace-wrapper'); const setupRoutes = require('./routes'); diff --git a/Lesson 2/upnp-basics/before/package.json b/Lesson 2/upnp-basics/before/package.json index 0406a57..e1532ee 100644 --- a/Lesson 2/upnp-basics/before/package.json +++ b/Lesson 2/upnp-basics/before/package.json @@ -16,6 +16,8 @@ "license": "ISC", "dependencies": { "@_koi/web3.js": "^0.0.6", + "@_koii/namespace-wrapper": "^1.0.1", + "@_koii/storage-task-sdk": "^1.2.4", "axios": "^0.27.2", "body-parser": "^1.20.2", "cross-spawn": "^7.0.3", diff --git a/Lesson 2/upnp-basics/before/task/audit.js b/Lesson 2/upnp-basics/before/task/audit.js index 709a13d..e0a1017 100644 --- a/Lesson 2/upnp-basics/before/task/audit.js +++ b/Lesson 2/upnp-basics/before/task/audit.js @@ -1,4 +1,4 @@ -const { namespaceWrapper } = require('../_koiiNode/koiiNode'); +const { namespaceWrapper } = require('@_koii/namespace-wrapper'); class Audit { /** diff --git a/Lesson 2/upnp-basics/before/task/distribution.js b/Lesson 2/upnp-basics/before/task/distribution.js index 6b7c83b..f07071c 100644 --- a/Lesson 2/upnp-basics/before/task/distribution.js +++ b/Lesson 2/upnp-basics/before/task/distribution.js @@ -1,4 +1,4 @@ -const { namespaceWrapper } = require('../_koiiNode/koiiNode'); +const { namespaceWrapper } = require('@_koii/namespace-wrapper'); class Distribution { /** diff --git a/Lesson 2/upnp-basics/before/task/submission.js b/Lesson 2/upnp-basics/before/task/submission.js index b9d0fc2..8789cdc 100644 --- a/Lesson 2/upnp-basics/before/task/submission.js +++ b/Lesson 2/upnp-basics/before/task/submission.js @@ -1,4 +1,4 @@ -const { namespaceWrapper } = require('../_koiiNode/koiiNode'); +const { namespaceWrapper } = require('@_koii/namespace-wrapper'); class Submission { /** * Executes your task, optionally storing the result. diff --git a/Lesson 2/upnp-basics/before/tests/main.test.js b/Lesson 2/upnp-basics/before/tests/main.test.js index 1eb8a52..05f5352 100644 --- a/Lesson 2/upnp-basics/before/tests/main.test.js +++ b/Lesson 2/upnp-basics/before/tests/main.test.js @@ -1,5 +1,5 @@ const { coreLogic } = require('../coreLogic'); -const { namespaceWrapper, _server } = require('../_koiiNode/koiiNode'); +const { namespaceWrapper, _server } = require('@_koii/namespace-wrapper'); const Joi = require('joi'); const axios = require('axios'); beforeAll(async () => { diff --git a/Lesson 3/simple-crawler/after/_koiiNode/koiiNode.js b/Lesson 3/simple-crawler/after/_koiiNode/koiiNode.js deleted file mode 100644 index 852bdb6..0000000 --- a/Lesson 3/simple-crawler/after/_koiiNode/koiiNode.js +++ /dev/null @@ -1,1156 +0,0 @@ -const { default: axios } = require('axios'); -const { createHash } = require('crypto'); - -const { Connection, PublicKey, Keypair } = require('@_koi/web3.js'); - -const Datastore = require('nedb-promises'); -const fsPromises = require('fs/promises'); -const bs58 = require('bs58'); -const nacl = require('tweetnacl'); - -/****************************************** init.js ***********************************/ - -const express = require('express'); -// Only used for testing purposes, in production the env will be injected by tasknode -require('dotenv').config(); -const bodyParser = require('body-parser'); -/** - * This will be the name of the current task as coming from the task node running this task. - */ -const TASK_NAME = process.argv[2] || 'Local'; -/** - * This will be the id of the current task as coming from the task node running this task. - */ -const TASK_ID = process.argv[3]; -/** - * This will be the PORT on which the this task is expected to run the express server coming from the task node running this task. - * As all communication via the task node and this task will be done on this port. - */ -const EXPRESS_PORT = process.argv[4] || 10000; - -const LogLevel = { - Log: 'log', - Warn: 'warn', - Error: 'error', -}; - -// Not used anymore -// const NODE_MODE = process.argv[5]; - -/** - * This will be the main account public key in string format of the task node running this task. - */ -const MAIN_ACCOUNT_PUBKEY = process.argv[6]; -/** - * This will be the secret used by the task to authenticate with task node running this task. - */ -const SECRET_KEY = process.argv[7]; -/** - * This will be K2 url being used by the task node, possible values are 'https://k2-testnet.koii.live' | 'https://k2-devnet.koii.live' | 'http://localhost:8899' - */ -const K2_NODE_URL = process.argv[8] || 'https://k2-testnet.koii.live'; -/** - * This will be public task node endpoint (Or local if it doesn't have any) of the task node running this task. - */ -const SERVICE_URL = process.argv[9]; -/** - * This will be stake of the task node running this task, can be double checked with the task state and staking public key. - */ -const STAKE = Number(process.argv[10]); -/** - * This will be the port used by task node as the express server port, so it can be used by the task for the communication with the task node - */ -const TASK_NODE_PORT = Number(process.argv[11]); - -const app = express(); - -console.log('SETTING UP EXPRESS'); - -app.use(bodyParser.urlencoded({ extended: false })); - -app.use(bodyParser.json()); - -app.use((req, res, next) => { - res.setHeader('Access-Control-Allow-Origin', '*'); - res.setHeader( - 'Access-Control-Allow-Methods', - 'GET, POST, PUT, PATCH, DELETE', - ); - res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); - res.setHeader('Access-Control-Allow-Credentials', false); - if (req.method === 'OPTIONS') - // if is preflight(OPTIONS) then response status 204(NO CONTENT) - return res.send(204); - next(); -}); - -app.get('/', (req, res) => { - res.send('Hello World!'); -}); - -const _server = app.listen(EXPRESS_PORT, () => { - console.log(`${TASK_NAME} listening on port ${EXPRESS_PORT}`); -}); - -/****************************************** NamespaceWrapper.js ***********************************/ - -const taskNodeAdministered = !!TASK_ID; -const BASE_ROOT_URL = `http://localhost:${TASK_NODE_PORT}/namespace-wrapper`; -let connection; - -class NamespaceWrapper { - #db; - #testingMainSystemAccount; - #testingStakingSystemAccount; - #testingTaskState; - #testingDistributionList; - - constructor() { - if (taskNodeAdministered) { - this.initializeDB(); - } else { - this.#db = Datastore.create('./localKOIIDB.db'); - this.defaultTaskSetup(); - } - } - - async initializeDB() { - if (this.#db) return; - try { - if (taskNodeAdministered) { - const path = await this.getTaskLevelDBPath(); - this.#db = Datastore.create(path); - } else { - this.#db = Datastore.create('./localKOIIDB.db'); - } - } catch (e) { - this.#db = Datastore.create(`../namespace/${TASK_ID}/KOIILevelDB.db`); - } - } - - async getDb() { - if (this.#db) return this.#db; - await this.initializeDB(); - return this.#db; - } - /** - * Namespace wrapper of storeGetAsync - * @param {string} key // Path to get - */ - async storeGet(key) { - try { - await this.initializeDB(); - const resp = await this.#db.findOne({ key: key }); - if (resp) { - return resp[key]; - } else { - return null; - } - } catch (e) { - console.error(e); - return null; - } - } - /** - * Namespace wrapper over storeSetAsync - * @param {string} key Path to set - * @param {*} value Data to set - */ - async storeSet(key, value) { - try { - await this.initializeDB(); - await this.#db.update( - { key: key }, - { [key]: value, key }, - { upsert: true }, - ); - } catch (e) { - console.error(e); - return undefined; - } - } - - /** - * Namespace wrapper over fsPromises methods - * @param {*} method The fsPromise method to call - * @param {*} path Path for the express call - * @param {...any} args Remaining parameters for the FS call - */ - async fs(method, path, ...args) { - if (taskNodeAdministered) { - return await genericHandler('fs', method, path, ...args); - } else { - return fsPromises[method](`${path}`, ...args); - } - } - async fsStaking(method, path, ...args) { - if (taskNodeAdministered) { - return await genericHandler('fsStaking', method, path, ...args); - } else { - return fsPromises[method](`${path}`, ...args); - } - } - - async fsWriteStream(imagepath) { - if (taskNodeAdministered) { - return await genericHandler('fsWriteStream', imagepath); - } else { - const writer = createWriteStream(imagepath); - return writer; - } - } - async fsReadStream(imagepath) { - if (taskNodeAdministered) { - return await genericHandler('fsReadStream', imagepath); - } else { - const file = readFileSync(imagepath); - return file; - } - } - - /** - * Namespace wrapper for getting current slots - */ - async getSlot() { - if (taskNodeAdministered) { - return await genericHandler('getCurrentSlot'); - } else { - return 100; - } - } - - async payloadSigning(body) { - if (taskNodeAdministered) { - return await genericHandler('signData', body); - } else { - const msg = new TextEncoder().encode(JSON.stringify(body)); - const signedMessage = nacl.sign( - msg, - this.#testingMainSystemAccount.secretKey, - ); - return await this.bs58Encode(signedMessage); - } - } - - async bs58Encode(data) { - return bs58.encode( - Buffer.from(data.buffer, data.byteOffset, data.byteLength), - ); - } - - async bs58Decode(data) { - return new Uint8Array(bs58.decode(data)); - } - - decodePayload(payload) { - return new TextDecoder().decode(payload); - } - - /** - * Namespace wrapper of storeGetAsync - * @param {string} signedMessage r // Path to get - */ - - async verifySignature(signedMessage, pubKey) { - if (taskNodeAdministered) { - return await genericHandler('verifySignedData', signedMessage, pubKey); - } else { - try { - const payload = nacl.sign.open( - await this.bs58Decode(signedMessage), - await this.bs58Decode(pubKey), - ); - if (!payload) return { error: 'Invalid signature' }; - return { data: this.decodePayload(payload) }; - } catch (e) { - console.error(e); - return { error: `Verification failed: ${e}` }; - } - } - } - - // async submissionOnChain(submitterKeypair, submission) { - // return await genericHandler( - // 'submissionOnChain', - // submitterKeypair, - // submission, - // ); - // } - - async stakeOnChain( - taskStateInfoPublicKey, - stakingAccKeypair, - stakePotAccount, - stakeAmount, - ) { - if (taskNodeAdministered) { - return await genericHandler( - 'stakeOnChain', - taskStateInfoPublicKey, - stakingAccKeypair, - stakePotAccount, - stakeAmount, - ); - } else { - this.#testingTaskState.stake_list[ - this.#testingStakingSystemAccount.publicKey.toBase58() - ] = stakeAmount; - } - } - async claimReward(stakePotAccount, beneficiaryAccount, claimerKeypair) { - if (!taskNodeAdministered) { - console.log('Cannot call sendTransaction in testing mode'); - return; - } - return await genericHandler( - 'claimReward', - stakePotAccount, - beneficiaryAccount, - claimerKeypair, - ); - } - async sendTransaction(serviceNodeAccount, beneficiaryAccount, amount) { - if (!taskNodeAdministered) { - console.log('Cannot call sendTransaction in testing mode'); - return; - } - return await genericHandler( - 'sendTransaction', - serviceNodeAccount, - beneficiaryAccount, - amount, - ); - } - - async getSubmitterAccount() { - if (taskNodeAdministered) { - const submitterAccountResp = await genericHandler('getSubmitterAccount'); - return Keypair.fromSecretKey( - Uint8Array.from(Object.values(submitterAccountResp._keypair.secretKey)), - ); - } else { - return this.#testingStakingSystemAccount; - } - } - - /** - * sendAndConfirmTransaction wrapper that injects mainSystemWallet as the first signer for paying the tx fees - * @param {connection} method // Receive method ["get", "post", "put", "delete"] - * @param {transaction} path // Endpoint path appended to namespace - * @param {Function} callback // Callback function on traffic receive - */ - async sendAndConfirmTransactionWrapper(transaction, signers) { - if (!taskNodeAdministered) { - console.log('Cannot call sendTransaction in testing mode'); - return; - } - const blockhash = (await connection.getRecentBlockhash('finalized')) - .blockhash; - transaction.recentBlockhash = blockhash; - transaction.feePayer = new PublicKey(MAIN_ACCOUNT_PUBKEY); - return await genericHandler( - 'sendAndConfirmTransactionWrapper', - transaction.serialize({ - requireAllSignatures: false, - verifySignatures: false, - }), - signers, - ); - } - - // async signArweave(transaction) { - // let tx = await genericHandler('signArweave', transaction.toJSON()); - // return arweave.transactions.fromRaw(tx); - // } - // async signEth(transaction) { - // return await genericHandler('signEth', transaction); - // } - async getTaskState(options) { - if (taskNodeAdministered) { - const response = await genericHandler('getTaskState', options); - if (response.error) { - console.log('Error in getting task state', response.error); - return null; - } - return response; - } else { - return this.#testingTaskState; - } - } - - async logMessage(level, message, action) { - switch (level) { - case LogLevel.Log: - console.log(message, action); - break; - case LogLevel.Warn: - console.warn(message, action); - break; - case LogLevel.Error: - console.error(message, action); - break; - default: - console.log( - `Invalid log level: ${level}. The log levels can be log, warn or error`, - ); - return false; - } - return true; - } - - /** - * This logger function is used to log the task erros , warnings and logs on desktop-node - * @param {level} enum // Receive method ["Log", "Warn", "Error"] - enum LogLevel { - Log = 'log', - Warn = 'warn', - Error = 'error', - } - * @param {message} string // log, error or warning message - * @returns {boolean} // true if the message is logged successfully otherwise false - */ - - async logger(level, message, action) { - if (taskNodeAdministered) { - return await genericHandler('logger', level, message, action); - } else { - return await this.logMessage(level, message, action); - } - } - - async auditSubmission(candidatePubkey, isValid, voterKeypair, round) { - if (taskNodeAdministered) { - return await genericHandler( - 'auditSubmission', - candidatePubkey, - isValid, - round, - ); - } else { - if ( - this.#testingTaskState.submissions_audit_trigger[round] && - this.#testingTaskState.submissions_audit_trigger[round][candidatePubkey] - ) { - this.#testingTaskState.submissions_audit_trigger[round][ - candidatePubkey - ].votes.push({ - is_valid: isValid, - voter: voterKeypair.pubKey.toBase58(), - slot: 100, - }); - } else { - this.#testingTaskState.submissions_audit_trigger[round] = { - [candidatePubkey]: { - trigger_by: this.#testingStakingSystemAccount.publicKey.toBase58(), - slot: 100, - votes: [], - }, - }; - } - } - } - - async distributionListAuditSubmission( - candidatePubkey, - isValid, - voterKeypair, - round, - ) { - if (taskNodeAdministered) { - return await genericHandler( - 'distributionListAuditSubmission', - candidatePubkey, - isValid, - round, - ); - } else { - if ( - this.#testingTaskState.distributions_audit_trigger[round] && - this.#testingTaskState.distributions_audit_trigger[round][ - candidatePubkey - ] - ) { - this.#testingTaskState.distributions_audit_trigger[round][ - candidatePubkey - ].votes.push({ - is_valid: isValid, - voter: voterKeypair.pubKey.toBase58(), - slot: 100, - }); - } else { - this.#testingTaskState.distributions_audit_trigger[round] = { - [candidatePubkey]: { - trigger_by: this.#testingStakingSystemAccount.publicKey.toBase58(), - slot: 100, - votes: [], - }, - }; - } - } - } - - async getRound() { - if (taskNodeAdministered) { - return await genericHandler('getRound'); - } else { - return 1; - } - } - - async payoutTrigger(round) { - if (taskNodeAdministered) { - return await genericHandler('payloadTrigger', round); - } else { - console.log( - 'Payout Trigger only handles possitive flows (Without audits)', - ); - let round = 1; - const submissionValAcc = - this.#testingDistributionList[round][ - this.#testingStakingSystemAccount.toBase58() - ].submission_value; - this.#testingTaskState.available_balances = - this.#testingDistributionList[round][submissionValAcc]; - } - } - - async uploadDistributionList(distributionList, round) { - if (taskNodeAdministered) { - return await genericHandler( - 'uploadDistributionList', - distributionList, - round, - ); - } else { - if (!this.#testingDistributionList[round]) - this.#testingDistributionList[round] = {}; - - this.#testingDistributionList[round][ - this.#testingStakingSystemAccount.publicKey.toBase58() - ] = Buffer.from(JSON.stringify(distributionList)); - return true; - } - } - - async distributionListSubmissionOnChain(round) { - if (taskNodeAdministered) { - return await genericHandler('distributionListSubmissionOnChain', round); - } else { - if (!this.#testingTaskState.distribution_rewards_submission[round]) - this.#testingTaskState.distribution_rewards_submission[round] = {}; - - this.#testingTaskState.distribution_rewards_submission[round][ - this.#testingStakingSystemAccount.publicKey.toBase58() - ] = { - submission_value: - this.#testingStakingSystemAccount.publicKey.toBase58(), - slot: 200, - round: 1, - }; - } - } - - async checkSubmissionAndUpdateRound(submissionValue = 'default', round) { - if (taskNodeAdministered) { - return await genericHandler( - 'checkSubmissionAndUpdateRound', - submissionValue, - round, - ); - } else { - if (!this.#testingTaskState.submissions[round]) - this.#testingTaskState.submissions[round] = {}; - this.#testingTaskState.submissions[round][ - this.#testingStakingSystemAccount.publicKey.toBase58() - ] = { - submission_value: submissionValue, - slot: 100, - round, - }; - } - } - async getProgramAccounts() { - if (taskNodeAdministered) { - return await genericHandler('getProgramAccounts'); - } else { - console.log('Cannot call getProgramAccounts in testing mode'); - } - } - async defaultTaskSetup() { - if (taskNodeAdministered) { - return await genericHandler('defaultTaskSetup'); - } else { - if (this.#testingTaskState) return; - this.#testingMainSystemAccount = new Keypair(); - this.#testingStakingSystemAccount = new Keypair(); - this.#testingDistributionList = {}; - this.#testingTaskState = { - task_name: 'DummyTestState', - task_description: 'Dummy Task state for testing flow', - submissions: {}, - submissions_audit_trigger: {}, - total_bounty_amount: 10000000000, - bounty_amount_per_round: 1000000000, - total_stake_amount: 50000000000, - minimum_stake_amount: 5000000000, - available_balances: {}, - stake_list: {}, - round_time: 600, - starting_slot: 0, - audit_window: 200, - submission_window: 200, - distribution_rewards_submission: {}, - distributions_audit_trigger: {}, - }; - } - } - async getRpcUrl() { - if (taskNodeAdministered) { - return await genericHandler('getRpcUrl'); - } else { - console.log('Cannot call getNodes in testing mode'); - } - } - async getNodes(url) { - if (taskNodeAdministered) { - return await genericHandler('getNodes', url); - } else { - console.log('Cannot call getNodes in testing mode'); - } - } - - async getDistributionList(publicKey, round) { - if (taskNodeAdministered) { - const response = await genericHandler( - 'getDistributionList', - publicKey, - round, - ); - if (response.error) { - return null; - } - return response; - } else { - const submissionValAcc = - this.#testingTaskState.distribution_rewards_submission[round][ - this.#testingStakingSystemAccount.publicKey.toBase58() - ].submission_value; - return this.#testingDistributionList[round][submissionValAcc]; - } - } - - async getTaskSubmissionInfo(round) { - if (taskNodeAdministered) { - const taskSubmissionInfo = await genericHandler( - 'getTaskSubmissionInfo', - round, - ); - if (taskSubmissionInfo.error) { - return null; - } - return taskSubmissionInfo; - } else { - // console.log(this.#testingTaskState) - return this.#testingTaskState; - } - } - - async validateAndVoteOnNodes(validate, round) { - console.log('******/ IN VOTING /******'); - let taskAccountDataJSON = null; - try { - taskAccountDataJSON = await this.getTaskSubmissionInfo(round); - } catch (error) { - console.error('Error in getting submissions for the round', error); - } - if (taskAccountDataJSON == null) { - console.log('No submissions found for the round', round); - return; - } - console.log( - `Fetching the submissions of round ${round}`, - taskAccountDataJSON.submissions[round], - ); - const submissions = taskAccountDataJSON.submissions[round]; - if (submissions == null) { - console.log(`No submisssions found in round ${round}`); - return `No submisssions found in round ${round}`; - } else { - const keys = Object.keys(submissions); - const values = Object.values(submissions); - const size = values.length; - console.log('Submissions from last round: ', keys, values, size); - let isValid; - const submitterAccountKeyPair = await this.getSubmitterAccount(); - const submitterPubkey = submitterAccountKeyPair.publicKey.toBase58(); - for (let i = 0; i < size; i++) { - let candidatePublicKey = keys[i]; - console.log('FOR CANDIDATE KEY', candidatePublicKey); - let candidateKeyPairPublicKey = new PublicKey(keys[i]); - if (candidatePublicKey == submitterPubkey && taskNodeAdministered) { - console.log('YOU CANNOT VOTE ON YOUR OWN SUBMISSIONS'); - } else { - try { - console.log( - 'SUBMISSION VALUE TO CHECK', - values[i].submission_value, - ); - isValid = await validate(values[i].submission_value, round); - console.log(`Voting ${isValid} to ${candidatePublicKey}`); - - if (isValid) { - // check for the submissions_audit_trigger , if it exists then vote true on that otherwise do nothing - const submissions_audit_trigger = - taskAccountDataJSON.submissions_audit_trigger[round]; - console.log('SUBMIT AUDIT TRIGGER', submissions_audit_trigger); - // console.log( - // "CANDIDATE PUBKEY CHECK IN AUDIT TRIGGER", - // submissions_audit_trigger[candidatePublicKey] - // ); - if ( - submissions_audit_trigger && - submissions_audit_trigger[candidatePublicKey] - ) { - console.log('VOTING TRUE ON AUDIT'); - const response = await this.auditSubmission( - candidateKeyPairPublicKey, - isValid, - submitterAccountKeyPair, - round, - ); - console.log('RESPONSE FROM AUDIT FUNCTION', response); - } - } else if (isValid == false) { - // Call auditSubmission function and isValid is passed as false - console.log('RAISING AUDIT / VOTING FALSE'); - const response = await this.auditSubmission( - candidateKeyPairPublicKey, - isValid, - submitterAccountKeyPair, - round, - ); - console.log('RESPONSE FROM AUDIT FUNCTION', response); - } - } catch (err) { - console.log('ERROR IN ELSE CONDITION', err); - } - } - } - } - } - - async getTaskDistributionInfo(round) { - if (taskNodeAdministered) { - const taskDistributionInfo = await genericHandler( - 'getTaskDistributionInfo', - round, - ); - if (taskDistributionInfo.error) { - return null; - } - return taskDistributionInfo; - } else { - return this.#testingTaskState; - } - } - - async validateAndVoteOnDistributionList(validateDistribution, round) { - // await this.checkVoteStatus(); - console.log('******/ IN VOTING OF DISTRIBUTION LIST /******'); - let taskAccountDataJSON = null; - try { - taskAccountDataJSON = await this.getTaskDistributionInfo(round); - } catch (error) { - console.error('Error in getting distributions for the round', error); - } - if (taskAccountDataJSON == null) { - console.log('No distribution submissions found for the round', round); - return; - } - console.log( - `Fetching the Distribution submissions of round ${round}`, - taskAccountDataJSON.distribution_rewards_submission[round], - ); - const submissions = - taskAccountDataJSON?.distribution_rewards_submission[round]; - if ( - submissions == null || - submissions == undefined || - submissions.length == 0 - ) { - console.log(`No submisssions found in round ${round}`); - return `No submisssions found in round ${round}`; - } else { - const keys = Object.keys(submissions); - const values = Object.values(submissions); - const size = values.length; - console.log( - 'Distribution Submissions from last round: ', - keys, - values, - size, - ); - let isValid; - const submitterAccountKeyPair = await this.getSubmitterAccount(); - const submitterPubkey = submitterAccountKeyPair.publicKey.toBase58(); - - for (let i = 0; i < size; i++) { - let candidatePublicKey = keys[i]; - console.log('FOR CANDIDATE KEY', candidatePublicKey); - let candidateKeyPairPublicKey = new PublicKey(keys[i]); - if (candidatePublicKey == submitterPubkey) { - console.log('YOU CANNOT VOTE ON YOUR OWN DISTRIBUTION SUBMISSIONS'); - } else { - try { - console.log( - 'DISTRIBUTION SUBMISSION VALUE TO CHECK', - values[i].submission_value, - ); - isValid = await validateDistribution( - values[i].submission_value, - round, - ); - console.log(`Voting ${isValid} to ${candidatePublicKey}`); - - if (isValid) { - // check for the submissions_audit_trigger , if it exists then vote true on that otherwise do nothing - const distributions_audit_trigger = - taskAccountDataJSON.distributions_audit_trigger[round]; - console.log( - 'SUBMIT DISTRIBUTION AUDIT TRIGGER', - distributions_audit_trigger, - ); - // console.log( - // "CANDIDATE PUBKEY CHECK IN AUDIT TRIGGER", - // distributions_audit_trigger[candidatePublicKey] - // ); - if ( - distributions_audit_trigger && - distributions_audit_trigger[candidatePublicKey] - ) { - console.log('VOTING TRUE ON DISTRIBUTION AUDIT'); - const response = await this.distributionListAuditSubmission( - candidateKeyPairPublicKey, - isValid, - submitterAccountKeyPair, - round, - ); - console.log( - 'RESPONSE FROM DISTRIBUTION AUDIT FUNCTION', - response, - ); - } - } else if (isValid == false) { - // Call auditSubmission function and isValid is passed as false - console.log('RAISING AUDIT / VOTING FALSE ON DISTRIBUTION'); - const response = await this.distributionListAuditSubmission( - candidateKeyPairPublicKey, - isValid, - submitterAccountKeyPair, - round, - ); - console.log( - 'RESPONSE FROM DISTRIBUTION AUDIT FUNCTION', - response, - ); - } - } catch (err) { - console.log('ERROR IN ELSE CONDITION FOR DISTRIBUTION', err); - } - } - } - } - } - async getTaskLevelDBPath() { - if (taskNodeAdministered) { - return await genericHandler('getTaskLevelDBPath'); - } else { - return './KOIIDB'; - } - } - async getBasePath() { - if (taskNodeAdministered) { - const basePath = (await namespaceWrapper.getTaskLevelDBPath()).replace( - '/KOIIDB', - '', - ); - return basePath; - } else { - return './'; - } - } - - async getAverageSlotTime() { - if (taskNodeAdministered) { - try { - return await genericHandler('getAverageSlotTime'); - } catch (error) { - console.error('Error getting average slot time', error); - return 400; - } - } else { - return 400; - } - } - - async nodeSelectionDistributionList(round, isPreviousFailed) { - let taskAccountDataJSON = null; - try { - taskAccountDataJSON = await this.getTaskSubmissionInfo(round); - } catch (error) { - console.error('Task submission not found', error); - return; - } - - if (taskAccountDataJSON == null) { - console.error('Task state not found'); - return; - } - console.log('EXPECTED ROUND', round); - - const submissions = taskAccountDataJSON.submissions[round]; - if (submissions == null) { - console.log('No submisssions found in N-1 round'); - return 'No submisssions found in N-1 round'; - } else { - // getting last 3 submissions for the rounds - let keys; - const latestRounds = [round, round - 1, round - 2].filter(r => r >= 0); - - const promises = latestRounds.map(async r => { - if (r == round) { - return new Set(Object.keys(submissions)); - } else { - let roundSubmissions = null; - try { - roundSubmissions = await this.getTaskSubmissionInfo(r); - if (roundSubmissions && roundSubmissions.submissions[r]) { - return new Set(Object.keys(roundSubmissions.submissions[r])); - } - } catch (error) { - console.error('Error in getting submissions for the round', error); - } - return new Set(); - } - }); - - const keySets = await Promise.all(promises); - - // Find the keys present in all the rounds - keys = - keySets.length > 0 - ? [...keySets[0]].filter(key => keySets.every(set => set.has(key))) - : []; - if (keys.length == 0) { - console.log('No common keys found in last 3 rounds'); - keys = Object.keys(submissions); - } - console.log('KEYS', keys.length); - const values = keys.map(key => submissions[key]); - - let size = keys.length; - console.log('Submissions from N-2 round: ', size); - - // Check the keys i.e if the submitter shall be excluded or not - try { - const distributionData = await this.getTaskDistributionInfo(round); - const audit_record = distributionData?.distributions_audit_record; - if (audit_record && audit_record[round] == 'PayoutFailed') { - console.log('ROUND DATA', audit_record[round]); - console.log( - 'SUBMITTER LIST', - distributionData.distribution_rewards_submission[round], - ); - const submitterList = - distributionData.distribution_rewards_submission[round]; - const submitterKeys = Object.keys(submitterList); - console.log('SUBMITTER KEYS', submitterKeys); - const submitterSize = submitterKeys.length; - console.log('SUBMITTER SIZE', submitterSize); - - for (let j = 0; j < submitterSize; j++) { - console.log('SUBMITTER KEY CANDIDATE', submitterKeys[j]); - const id = keys.indexOf(submitterKeys[j]); - console.log('ID', id); - if (id != -1) { - keys.splice(id, 1); - values.splice(id, 1); - size--; - } - } - - console.log('KEYS FOR HASH CALC', keys.length); - } - } catch (error) { - console.log('Error in getting distribution data', error); - } - - // calculating the digest - - const ValuesString = JSON.stringify(values); - - const hashDigest = createHash('sha256') - .update(ValuesString) - .digest('hex'); - - console.log('HASH DIGEST', hashDigest); - - // function to calculate the score - const calculateScore = (str = '') => { - return str.split('').reduce((acc, val) => { - return acc + val.charCodeAt(0); - }, 0); - }; - - // function to compare the ASCII values - - const compareASCII = (str1, str2) => { - const firstScore = calculateScore(str1); - const secondScore = calculateScore(str2); - return Math.abs(firstScore - secondScore); - }; - - // loop through the keys and select the one with higest score - - const selectedNode = { - score: 0, - pubkey: '', - }; - let score = 0; - if (isPreviousFailed) { - let leastScore = -Infinity; - let secondLeastScore = -Infinity; - for (let i = 0; i < size; i++) { - const candidateSubmissionJson = {}; - candidateSubmissionJson[keys[i]] = values[i]; - const candidateSubmissionString = JSON.stringify( - candidateSubmissionJson, - ); - const candidateSubmissionHash = createHash('sha256') - .update(candidateSubmissionString) - .digest('hex'); - const candidateScore = compareASCII( - hashDigest, - candidateSubmissionHash, - ); - if (candidateScore > leastScore) { - secondLeastScore = leastScore; - leastScore = candidateScore; - } else if (candidateScore > secondLeastScore) { - secondLeastScore = candidateScore; - selectedNode.score = candidateScore; - selectedNode.pubkey = keys[i]; - } - } - } else { - for (let i = 0; i < size; i++) { - const candidateSubmissionJson = {}; - candidateSubmissionJson[keys[i]] = values[i]; - const candidateSubmissionString = JSON.stringify( - candidateSubmissionJson, - ); - const candidateSubmissionHash = createHash('sha256') - .update(candidateSubmissionString) - .digest('hex'); - const candidateScore = compareASCII( - hashDigest, - candidateSubmissionHash, - ); - // console.log('CANDIDATE SCORE', candidateScore); - if (candidateScore > score) { - score = candidateScore; - selectedNode.score = candidateScore; - selectedNode.pubkey = keys[i]; - } - } - } - - console.log('SELECTED NODE OBJECT', selectedNode); - return selectedNode.pubkey; - } - } - - async selectAndGenerateDistributionList( - submitDistributionList, - round, - isPreviousRoundFailed, - ) { - console.log('SelectAndGenerateDistributionList called'); - const selectedNode = await this.nodeSelectionDistributionList( - round, - isPreviousRoundFailed, - ); - console.log('Selected Node', selectedNode); - const submitPubKey = await this.getSubmitterAccount(); - if ( - selectedNode == undefined || - selectedNode == '' || - submitPubKey == undefined - ) - return; - if (selectedNode == submitPubKey?.publicKey.toBase58()) { - await submitDistributionList(round); - const taskState = await this.getTaskState({}); - if (taskState == null) { - console.error('Task state not found'); - return; - } - const avgSlotTime = await this.getAverageSlotTime(); - if (avgSlotTime == null) { - console.error('Avg slot time not found'); - return; - } - setTimeout(async () => { - await this.payoutTrigger(round); - }, (taskState.audit_window + taskState.submission_window) * avgSlotTime); - } - } - - getMainAccountPubkey() { - if (taskNodeAdministered) { - return MAIN_ACCOUNT_PUBKEY; - } else { - return this.#testingMainSystemAccount.publicKey.toBase58(); - } - } -} - -async function genericHandler(...args) { - try { - let response = await axios.post(BASE_ROOT_URL, { - args, - taskId: TASK_ID, - secret: SECRET_KEY, - }); - if (response.status == 200) return response.data.response; - else { - console.error(response.status, response.data); - return null; - } - } catch (err) { - console.error(`Error in genericHandler: "${args[0]}"`, err.message); - console.error(err?.response?.data); - return { error: err }; - } -} - -const namespaceWrapper = new NamespaceWrapper(); -if (taskNodeAdministered) { - namespaceWrapper.getRpcUrl().then(rpcUrl => { - console.log(rpcUrl, 'RPC URL'); - connection = new Connection(rpcUrl, 'confirmed'); - }); -} -module.exports = { - namespaceWrapper, - taskNodeAdministered, // Boolean flag indicating that the task is being ran in active mode (Task node supervised), or development (testing) mode - app, // The initialized express app to be used to register endpoints - TASK_ID, // This will be the PORT on which the this task is expected to run the express server coming from the task node running this task. As all communication via the task node and this task will be done on this port. - MAIN_ACCOUNT_PUBKEY, // This will be the secret used to authenticate with task node running this task. - SECRET_KEY, // This will be the secret used by the task to authenticate with task node running this task. - K2_NODE_URL, // This will be K2 url being used by the task node, possible values are 'https://k2-testnet.koii.live' | 'https://k2-devnet.koii.live' | 'http://localhost:8899' - SERVICE_URL, // This will be public task node endpoint (Or local if it doesn't have any) of the task node running this task. - STAKE, // This will be stake of the task node running this task, can be double checked with the task state and staking public key. - TASK_NODE_PORT, // This will be the port used by task node as the express server port, so it can be used by the task for the communication with the task node - _server, // Express server object -}; diff --git a/Lesson 3/simple-crawler/after/coreLogic.js b/Lesson 3/simple-crawler/after/coreLogic.js index 7b09d08..b210551 100644 --- a/Lesson 3/simple-crawler/after/coreLogic.js +++ b/Lesson 3/simple-crawler/after/coreLogic.js @@ -1,4 +1,4 @@ -const { namespaceWrapper } = require('./_koiiNode/koiiNode'); +const { namespaceWrapper } = require('@_koii/namespace-wrapper'); const task = require('./task'); class CoreLogic { diff --git a/Lesson 3/simple-crawler/after/index.js b/Lesson 3/simple-crawler/after/index.js index 75723ec..17ac502 100644 --- a/Lesson 3/simple-crawler/after/index.js +++ b/Lesson 3/simple-crawler/after/index.js @@ -3,12 +3,12 @@ const { namespaceWrapper, taskNodeAdministered, app, -} = require('./_koiiNode/koiiNode'); +} = require('@_koii/namespace-wrapper'); if (app) { // Write your Express Endpoints here. // Ex. app.post('/accept-cid', async (req, res) => {}) - + // Sample API that return your task state app.get('/taskState', async (req, res) => { const state = await namespaceWrapper.getTaskState(); @@ -54,13 +54,13 @@ async function setup() { /* GUIDE TO CALLS K2 FUNCTIONS MANUALLY - If you wish to do the development by avoiding the timers then you can do the intended calls to K2 - directly using these function calls. + If you wish to do the development by avoiding the timers then you can do the intended calls to K2 + directly using these function calls. To disable timers please set the TIMERS flag in task-node ENV to disable NOTE : K2 will still have the windows to accept the submission value, audit, so you are expected - to make calls in the intended slots of your round time. + to make calls in the intended slots of your round time. */ diff --git a/Lesson 3/simple-crawler/after/package.json b/Lesson 3/simple-crawler/after/package.json index f6bc56c..a4e1ed2 100644 --- a/Lesson 3/simple-crawler/after/package.json +++ b/Lesson 3/simple-crawler/after/package.json @@ -17,6 +17,7 @@ "dependencies": { "@_koi/web3.js": "^0.0.6", "@_koii/storage-task-sdk": "^1.2.4", + "@_koii/namespace-wrapper": "^1.0.1", "axios": "^0.27.2", "body-parser": "^1.20.2", "cross-spawn": "^7.0.3", diff --git a/Lesson 3/simple-crawler/after/task/audit.js b/Lesson 3/simple-crawler/after/task/audit.js index b7bfac4..fa5a061 100644 --- a/Lesson 3/simple-crawler/after/task/audit.js +++ b/Lesson 3/simple-crawler/after/task/audit.js @@ -1,4 +1,4 @@ -const { namespaceWrapper } = require('../_koiiNode/koiiNode'); +const { namespaceWrapper } = require('@_koii/namespace-wrapper'); const SimpleCrawlerTask = require('../crawler/SimpleCrawlerTask'); class Audit { diff --git a/Lesson 3/simple-crawler/after/task/distribution.js b/Lesson 3/simple-crawler/after/task/distribution.js index 6b7c83b..f07071c 100644 --- a/Lesson 3/simple-crawler/after/task/distribution.js +++ b/Lesson 3/simple-crawler/after/task/distribution.js @@ -1,4 +1,4 @@ -const { namespaceWrapper } = require('../_koiiNode/koiiNode'); +const { namespaceWrapper } = require('@_koii/namespace-wrapper'); class Distribution { /** diff --git a/Lesson 3/simple-crawler/after/task/submission.js b/Lesson 3/simple-crawler/after/task/submission.js index 8c13686..62cf3d8 100644 --- a/Lesson 3/simple-crawler/after/task/submission.js +++ b/Lesson 3/simple-crawler/after/task/submission.js @@ -1,4 +1,4 @@ -const { namespaceWrapper } = require('../_koiiNode/koiiNode'); +const { namespaceWrapper } = require('@_koii/namespace-wrapper'); const { KoiiStorageClient } = require('@_koii/storage-task-sdk'); const fs = require('fs'); diff --git a/Lesson 3/simple-crawler/after/tests/main.test.js b/Lesson 3/simple-crawler/after/tests/main.test.js index 1eb8a52..05f5352 100644 --- a/Lesson 3/simple-crawler/after/tests/main.test.js +++ b/Lesson 3/simple-crawler/after/tests/main.test.js @@ -1,5 +1,5 @@ const { coreLogic } = require('../coreLogic'); -const { namespaceWrapper, _server } = require('../_koiiNode/koiiNode'); +const { namespaceWrapper, _server } = require('@_koii/namespace-wrapper'); const Joi = require('joi'); const axios = require('axios'); beforeAll(async () => { diff --git a/Lesson 3/simple-crawler/before/_koiiNode/koiiNode.js b/Lesson 3/simple-crawler/before/_koiiNode/koiiNode.js deleted file mode 100644 index 852bdb6..0000000 --- a/Lesson 3/simple-crawler/before/_koiiNode/koiiNode.js +++ /dev/null @@ -1,1156 +0,0 @@ -const { default: axios } = require('axios'); -const { createHash } = require('crypto'); - -const { Connection, PublicKey, Keypair } = require('@_koi/web3.js'); - -const Datastore = require('nedb-promises'); -const fsPromises = require('fs/promises'); -const bs58 = require('bs58'); -const nacl = require('tweetnacl'); - -/****************************************** init.js ***********************************/ - -const express = require('express'); -// Only used for testing purposes, in production the env will be injected by tasknode -require('dotenv').config(); -const bodyParser = require('body-parser'); -/** - * This will be the name of the current task as coming from the task node running this task. - */ -const TASK_NAME = process.argv[2] || 'Local'; -/** - * This will be the id of the current task as coming from the task node running this task. - */ -const TASK_ID = process.argv[3]; -/** - * This will be the PORT on which the this task is expected to run the express server coming from the task node running this task. - * As all communication via the task node and this task will be done on this port. - */ -const EXPRESS_PORT = process.argv[4] || 10000; - -const LogLevel = { - Log: 'log', - Warn: 'warn', - Error: 'error', -}; - -// Not used anymore -// const NODE_MODE = process.argv[5]; - -/** - * This will be the main account public key in string format of the task node running this task. - */ -const MAIN_ACCOUNT_PUBKEY = process.argv[6]; -/** - * This will be the secret used by the task to authenticate with task node running this task. - */ -const SECRET_KEY = process.argv[7]; -/** - * This will be K2 url being used by the task node, possible values are 'https://k2-testnet.koii.live' | 'https://k2-devnet.koii.live' | 'http://localhost:8899' - */ -const K2_NODE_URL = process.argv[8] || 'https://k2-testnet.koii.live'; -/** - * This will be public task node endpoint (Or local if it doesn't have any) of the task node running this task. - */ -const SERVICE_URL = process.argv[9]; -/** - * This will be stake of the task node running this task, can be double checked with the task state and staking public key. - */ -const STAKE = Number(process.argv[10]); -/** - * This will be the port used by task node as the express server port, so it can be used by the task for the communication with the task node - */ -const TASK_NODE_PORT = Number(process.argv[11]); - -const app = express(); - -console.log('SETTING UP EXPRESS'); - -app.use(bodyParser.urlencoded({ extended: false })); - -app.use(bodyParser.json()); - -app.use((req, res, next) => { - res.setHeader('Access-Control-Allow-Origin', '*'); - res.setHeader( - 'Access-Control-Allow-Methods', - 'GET, POST, PUT, PATCH, DELETE', - ); - res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); - res.setHeader('Access-Control-Allow-Credentials', false); - if (req.method === 'OPTIONS') - // if is preflight(OPTIONS) then response status 204(NO CONTENT) - return res.send(204); - next(); -}); - -app.get('/', (req, res) => { - res.send('Hello World!'); -}); - -const _server = app.listen(EXPRESS_PORT, () => { - console.log(`${TASK_NAME} listening on port ${EXPRESS_PORT}`); -}); - -/****************************************** NamespaceWrapper.js ***********************************/ - -const taskNodeAdministered = !!TASK_ID; -const BASE_ROOT_URL = `http://localhost:${TASK_NODE_PORT}/namespace-wrapper`; -let connection; - -class NamespaceWrapper { - #db; - #testingMainSystemAccount; - #testingStakingSystemAccount; - #testingTaskState; - #testingDistributionList; - - constructor() { - if (taskNodeAdministered) { - this.initializeDB(); - } else { - this.#db = Datastore.create('./localKOIIDB.db'); - this.defaultTaskSetup(); - } - } - - async initializeDB() { - if (this.#db) return; - try { - if (taskNodeAdministered) { - const path = await this.getTaskLevelDBPath(); - this.#db = Datastore.create(path); - } else { - this.#db = Datastore.create('./localKOIIDB.db'); - } - } catch (e) { - this.#db = Datastore.create(`../namespace/${TASK_ID}/KOIILevelDB.db`); - } - } - - async getDb() { - if (this.#db) return this.#db; - await this.initializeDB(); - return this.#db; - } - /** - * Namespace wrapper of storeGetAsync - * @param {string} key // Path to get - */ - async storeGet(key) { - try { - await this.initializeDB(); - const resp = await this.#db.findOne({ key: key }); - if (resp) { - return resp[key]; - } else { - return null; - } - } catch (e) { - console.error(e); - return null; - } - } - /** - * Namespace wrapper over storeSetAsync - * @param {string} key Path to set - * @param {*} value Data to set - */ - async storeSet(key, value) { - try { - await this.initializeDB(); - await this.#db.update( - { key: key }, - { [key]: value, key }, - { upsert: true }, - ); - } catch (e) { - console.error(e); - return undefined; - } - } - - /** - * Namespace wrapper over fsPromises methods - * @param {*} method The fsPromise method to call - * @param {*} path Path for the express call - * @param {...any} args Remaining parameters for the FS call - */ - async fs(method, path, ...args) { - if (taskNodeAdministered) { - return await genericHandler('fs', method, path, ...args); - } else { - return fsPromises[method](`${path}`, ...args); - } - } - async fsStaking(method, path, ...args) { - if (taskNodeAdministered) { - return await genericHandler('fsStaking', method, path, ...args); - } else { - return fsPromises[method](`${path}`, ...args); - } - } - - async fsWriteStream(imagepath) { - if (taskNodeAdministered) { - return await genericHandler('fsWriteStream', imagepath); - } else { - const writer = createWriteStream(imagepath); - return writer; - } - } - async fsReadStream(imagepath) { - if (taskNodeAdministered) { - return await genericHandler('fsReadStream', imagepath); - } else { - const file = readFileSync(imagepath); - return file; - } - } - - /** - * Namespace wrapper for getting current slots - */ - async getSlot() { - if (taskNodeAdministered) { - return await genericHandler('getCurrentSlot'); - } else { - return 100; - } - } - - async payloadSigning(body) { - if (taskNodeAdministered) { - return await genericHandler('signData', body); - } else { - const msg = new TextEncoder().encode(JSON.stringify(body)); - const signedMessage = nacl.sign( - msg, - this.#testingMainSystemAccount.secretKey, - ); - return await this.bs58Encode(signedMessage); - } - } - - async bs58Encode(data) { - return bs58.encode( - Buffer.from(data.buffer, data.byteOffset, data.byteLength), - ); - } - - async bs58Decode(data) { - return new Uint8Array(bs58.decode(data)); - } - - decodePayload(payload) { - return new TextDecoder().decode(payload); - } - - /** - * Namespace wrapper of storeGetAsync - * @param {string} signedMessage r // Path to get - */ - - async verifySignature(signedMessage, pubKey) { - if (taskNodeAdministered) { - return await genericHandler('verifySignedData', signedMessage, pubKey); - } else { - try { - const payload = nacl.sign.open( - await this.bs58Decode(signedMessage), - await this.bs58Decode(pubKey), - ); - if (!payload) return { error: 'Invalid signature' }; - return { data: this.decodePayload(payload) }; - } catch (e) { - console.error(e); - return { error: `Verification failed: ${e}` }; - } - } - } - - // async submissionOnChain(submitterKeypair, submission) { - // return await genericHandler( - // 'submissionOnChain', - // submitterKeypair, - // submission, - // ); - // } - - async stakeOnChain( - taskStateInfoPublicKey, - stakingAccKeypair, - stakePotAccount, - stakeAmount, - ) { - if (taskNodeAdministered) { - return await genericHandler( - 'stakeOnChain', - taskStateInfoPublicKey, - stakingAccKeypair, - stakePotAccount, - stakeAmount, - ); - } else { - this.#testingTaskState.stake_list[ - this.#testingStakingSystemAccount.publicKey.toBase58() - ] = stakeAmount; - } - } - async claimReward(stakePotAccount, beneficiaryAccount, claimerKeypair) { - if (!taskNodeAdministered) { - console.log('Cannot call sendTransaction in testing mode'); - return; - } - return await genericHandler( - 'claimReward', - stakePotAccount, - beneficiaryAccount, - claimerKeypair, - ); - } - async sendTransaction(serviceNodeAccount, beneficiaryAccount, amount) { - if (!taskNodeAdministered) { - console.log('Cannot call sendTransaction in testing mode'); - return; - } - return await genericHandler( - 'sendTransaction', - serviceNodeAccount, - beneficiaryAccount, - amount, - ); - } - - async getSubmitterAccount() { - if (taskNodeAdministered) { - const submitterAccountResp = await genericHandler('getSubmitterAccount'); - return Keypair.fromSecretKey( - Uint8Array.from(Object.values(submitterAccountResp._keypair.secretKey)), - ); - } else { - return this.#testingStakingSystemAccount; - } - } - - /** - * sendAndConfirmTransaction wrapper that injects mainSystemWallet as the first signer for paying the tx fees - * @param {connection} method // Receive method ["get", "post", "put", "delete"] - * @param {transaction} path // Endpoint path appended to namespace - * @param {Function} callback // Callback function on traffic receive - */ - async sendAndConfirmTransactionWrapper(transaction, signers) { - if (!taskNodeAdministered) { - console.log('Cannot call sendTransaction in testing mode'); - return; - } - const blockhash = (await connection.getRecentBlockhash('finalized')) - .blockhash; - transaction.recentBlockhash = blockhash; - transaction.feePayer = new PublicKey(MAIN_ACCOUNT_PUBKEY); - return await genericHandler( - 'sendAndConfirmTransactionWrapper', - transaction.serialize({ - requireAllSignatures: false, - verifySignatures: false, - }), - signers, - ); - } - - // async signArweave(transaction) { - // let tx = await genericHandler('signArweave', transaction.toJSON()); - // return arweave.transactions.fromRaw(tx); - // } - // async signEth(transaction) { - // return await genericHandler('signEth', transaction); - // } - async getTaskState(options) { - if (taskNodeAdministered) { - const response = await genericHandler('getTaskState', options); - if (response.error) { - console.log('Error in getting task state', response.error); - return null; - } - return response; - } else { - return this.#testingTaskState; - } - } - - async logMessage(level, message, action) { - switch (level) { - case LogLevel.Log: - console.log(message, action); - break; - case LogLevel.Warn: - console.warn(message, action); - break; - case LogLevel.Error: - console.error(message, action); - break; - default: - console.log( - `Invalid log level: ${level}. The log levels can be log, warn or error`, - ); - return false; - } - return true; - } - - /** - * This logger function is used to log the task erros , warnings and logs on desktop-node - * @param {level} enum // Receive method ["Log", "Warn", "Error"] - enum LogLevel { - Log = 'log', - Warn = 'warn', - Error = 'error', - } - * @param {message} string // log, error or warning message - * @returns {boolean} // true if the message is logged successfully otherwise false - */ - - async logger(level, message, action) { - if (taskNodeAdministered) { - return await genericHandler('logger', level, message, action); - } else { - return await this.logMessage(level, message, action); - } - } - - async auditSubmission(candidatePubkey, isValid, voterKeypair, round) { - if (taskNodeAdministered) { - return await genericHandler( - 'auditSubmission', - candidatePubkey, - isValid, - round, - ); - } else { - if ( - this.#testingTaskState.submissions_audit_trigger[round] && - this.#testingTaskState.submissions_audit_trigger[round][candidatePubkey] - ) { - this.#testingTaskState.submissions_audit_trigger[round][ - candidatePubkey - ].votes.push({ - is_valid: isValid, - voter: voterKeypair.pubKey.toBase58(), - slot: 100, - }); - } else { - this.#testingTaskState.submissions_audit_trigger[round] = { - [candidatePubkey]: { - trigger_by: this.#testingStakingSystemAccount.publicKey.toBase58(), - slot: 100, - votes: [], - }, - }; - } - } - } - - async distributionListAuditSubmission( - candidatePubkey, - isValid, - voterKeypair, - round, - ) { - if (taskNodeAdministered) { - return await genericHandler( - 'distributionListAuditSubmission', - candidatePubkey, - isValid, - round, - ); - } else { - if ( - this.#testingTaskState.distributions_audit_trigger[round] && - this.#testingTaskState.distributions_audit_trigger[round][ - candidatePubkey - ] - ) { - this.#testingTaskState.distributions_audit_trigger[round][ - candidatePubkey - ].votes.push({ - is_valid: isValid, - voter: voterKeypair.pubKey.toBase58(), - slot: 100, - }); - } else { - this.#testingTaskState.distributions_audit_trigger[round] = { - [candidatePubkey]: { - trigger_by: this.#testingStakingSystemAccount.publicKey.toBase58(), - slot: 100, - votes: [], - }, - }; - } - } - } - - async getRound() { - if (taskNodeAdministered) { - return await genericHandler('getRound'); - } else { - return 1; - } - } - - async payoutTrigger(round) { - if (taskNodeAdministered) { - return await genericHandler('payloadTrigger', round); - } else { - console.log( - 'Payout Trigger only handles possitive flows (Without audits)', - ); - let round = 1; - const submissionValAcc = - this.#testingDistributionList[round][ - this.#testingStakingSystemAccount.toBase58() - ].submission_value; - this.#testingTaskState.available_balances = - this.#testingDistributionList[round][submissionValAcc]; - } - } - - async uploadDistributionList(distributionList, round) { - if (taskNodeAdministered) { - return await genericHandler( - 'uploadDistributionList', - distributionList, - round, - ); - } else { - if (!this.#testingDistributionList[round]) - this.#testingDistributionList[round] = {}; - - this.#testingDistributionList[round][ - this.#testingStakingSystemAccount.publicKey.toBase58() - ] = Buffer.from(JSON.stringify(distributionList)); - return true; - } - } - - async distributionListSubmissionOnChain(round) { - if (taskNodeAdministered) { - return await genericHandler('distributionListSubmissionOnChain', round); - } else { - if (!this.#testingTaskState.distribution_rewards_submission[round]) - this.#testingTaskState.distribution_rewards_submission[round] = {}; - - this.#testingTaskState.distribution_rewards_submission[round][ - this.#testingStakingSystemAccount.publicKey.toBase58() - ] = { - submission_value: - this.#testingStakingSystemAccount.publicKey.toBase58(), - slot: 200, - round: 1, - }; - } - } - - async checkSubmissionAndUpdateRound(submissionValue = 'default', round) { - if (taskNodeAdministered) { - return await genericHandler( - 'checkSubmissionAndUpdateRound', - submissionValue, - round, - ); - } else { - if (!this.#testingTaskState.submissions[round]) - this.#testingTaskState.submissions[round] = {}; - this.#testingTaskState.submissions[round][ - this.#testingStakingSystemAccount.publicKey.toBase58() - ] = { - submission_value: submissionValue, - slot: 100, - round, - }; - } - } - async getProgramAccounts() { - if (taskNodeAdministered) { - return await genericHandler('getProgramAccounts'); - } else { - console.log('Cannot call getProgramAccounts in testing mode'); - } - } - async defaultTaskSetup() { - if (taskNodeAdministered) { - return await genericHandler('defaultTaskSetup'); - } else { - if (this.#testingTaskState) return; - this.#testingMainSystemAccount = new Keypair(); - this.#testingStakingSystemAccount = new Keypair(); - this.#testingDistributionList = {}; - this.#testingTaskState = { - task_name: 'DummyTestState', - task_description: 'Dummy Task state for testing flow', - submissions: {}, - submissions_audit_trigger: {}, - total_bounty_amount: 10000000000, - bounty_amount_per_round: 1000000000, - total_stake_amount: 50000000000, - minimum_stake_amount: 5000000000, - available_balances: {}, - stake_list: {}, - round_time: 600, - starting_slot: 0, - audit_window: 200, - submission_window: 200, - distribution_rewards_submission: {}, - distributions_audit_trigger: {}, - }; - } - } - async getRpcUrl() { - if (taskNodeAdministered) { - return await genericHandler('getRpcUrl'); - } else { - console.log('Cannot call getNodes in testing mode'); - } - } - async getNodes(url) { - if (taskNodeAdministered) { - return await genericHandler('getNodes', url); - } else { - console.log('Cannot call getNodes in testing mode'); - } - } - - async getDistributionList(publicKey, round) { - if (taskNodeAdministered) { - const response = await genericHandler( - 'getDistributionList', - publicKey, - round, - ); - if (response.error) { - return null; - } - return response; - } else { - const submissionValAcc = - this.#testingTaskState.distribution_rewards_submission[round][ - this.#testingStakingSystemAccount.publicKey.toBase58() - ].submission_value; - return this.#testingDistributionList[round][submissionValAcc]; - } - } - - async getTaskSubmissionInfo(round) { - if (taskNodeAdministered) { - const taskSubmissionInfo = await genericHandler( - 'getTaskSubmissionInfo', - round, - ); - if (taskSubmissionInfo.error) { - return null; - } - return taskSubmissionInfo; - } else { - // console.log(this.#testingTaskState) - return this.#testingTaskState; - } - } - - async validateAndVoteOnNodes(validate, round) { - console.log('******/ IN VOTING /******'); - let taskAccountDataJSON = null; - try { - taskAccountDataJSON = await this.getTaskSubmissionInfo(round); - } catch (error) { - console.error('Error in getting submissions for the round', error); - } - if (taskAccountDataJSON == null) { - console.log('No submissions found for the round', round); - return; - } - console.log( - `Fetching the submissions of round ${round}`, - taskAccountDataJSON.submissions[round], - ); - const submissions = taskAccountDataJSON.submissions[round]; - if (submissions == null) { - console.log(`No submisssions found in round ${round}`); - return `No submisssions found in round ${round}`; - } else { - const keys = Object.keys(submissions); - const values = Object.values(submissions); - const size = values.length; - console.log('Submissions from last round: ', keys, values, size); - let isValid; - const submitterAccountKeyPair = await this.getSubmitterAccount(); - const submitterPubkey = submitterAccountKeyPair.publicKey.toBase58(); - for (let i = 0; i < size; i++) { - let candidatePublicKey = keys[i]; - console.log('FOR CANDIDATE KEY', candidatePublicKey); - let candidateKeyPairPublicKey = new PublicKey(keys[i]); - if (candidatePublicKey == submitterPubkey && taskNodeAdministered) { - console.log('YOU CANNOT VOTE ON YOUR OWN SUBMISSIONS'); - } else { - try { - console.log( - 'SUBMISSION VALUE TO CHECK', - values[i].submission_value, - ); - isValid = await validate(values[i].submission_value, round); - console.log(`Voting ${isValid} to ${candidatePublicKey}`); - - if (isValid) { - // check for the submissions_audit_trigger , if it exists then vote true on that otherwise do nothing - const submissions_audit_trigger = - taskAccountDataJSON.submissions_audit_trigger[round]; - console.log('SUBMIT AUDIT TRIGGER', submissions_audit_trigger); - // console.log( - // "CANDIDATE PUBKEY CHECK IN AUDIT TRIGGER", - // submissions_audit_trigger[candidatePublicKey] - // ); - if ( - submissions_audit_trigger && - submissions_audit_trigger[candidatePublicKey] - ) { - console.log('VOTING TRUE ON AUDIT'); - const response = await this.auditSubmission( - candidateKeyPairPublicKey, - isValid, - submitterAccountKeyPair, - round, - ); - console.log('RESPONSE FROM AUDIT FUNCTION', response); - } - } else if (isValid == false) { - // Call auditSubmission function and isValid is passed as false - console.log('RAISING AUDIT / VOTING FALSE'); - const response = await this.auditSubmission( - candidateKeyPairPublicKey, - isValid, - submitterAccountKeyPair, - round, - ); - console.log('RESPONSE FROM AUDIT FUNCTION', response); - } - } catch (err) { - console.log('ERROR IN ELSE CONDITION', err); - } - } - } - } - } - - async getTaskDistributionInfo(round) { - if (taskNodeAdministered) { - const taskDistributionInfo = await genericHandler( - 'getTaskDistributionInfo', - round, - ); - if (taskDistributionInfo.error) { - return null; - } - return taskDistributionInfo; - } else { - return this.#testingTaskState; - } - } - - async validateAndVoteOnDistributionList(validateDistribution, round) { - // await this.checkVoteStatus(); - console.log('******/ IN VOTING OF DISTRIBUTION LIST /******'); - let taskAccountDataJSON = null; - try { - taskAccountDataJSON = await this.getTaskDistributionInfo(round); - } catch (error) { - console.error('Error in getting distributions for the round', error); - } - if (taskAccountDataJSON == null) { - console.log('No distribution submissions found for the round', round); - return; - } - console.log( - `Fetching the Distribution submissions of round ${round}`, - taskAccountDataJSON.distribution_rewards_submission[round], - ); - const submissions = - taskAccountDataJSON?.distribution_rewards_submission[round]; - if ( - submissions == null || - submissions == undefined || - submissions.length == 0 - ) { - console.log(`No submisssions found in round ${round}`); - return `No submisssions found in round ${round}`; - } else { - const keys = Object.keys(submissions); - const values = Object.values(submissions); - const size = values.length; - console.log( - 'Distribution Submissions from last round: ', - keys, - values, - size, - ); - let isValid; - const submitterAccountKeyPair = await this.getSubmitterAccount(); - const submitterPubkey = submitterAccountKeyPair.publicKey.toBase58(); - - for (let i = 0; i < size; i++) { - let candidatePublicKey = keys[i]; - console.log('FOR CANDIDATE KEY', candidatePublicKey); - let candidateKeyPairPublicKey = new PublicKey(keys[i]); - if (candidatePublicKey == submitterPubkey) { - console.log('YOU CANNOT VOTE ON YOUR OWN DISTRIBUTION SUBMISSIONS'); - } else { - try { - console.log( - 'DISTRIBUTION SUBMISSION VALUE TO CHECK', - values[i].submission_value, - ); - isValid = await validateDistribution( - values[i].submission_value, - round, - ); - console.log(`Voting ${isValid} to ${candidatePublicKey}`); - - if (isValid) { - // check for the submissions_audit_trigger , if it exists then vote true on that otherwise do nothing - const distributions_audit_trigger = - taskAccountDataJSON.distributions_audit_trigger[round]; - console.log( - 'SUBMIT DISTRIBUTION AUDIT TRIGGER', - distributions_audit_trigger, - ); - // console.log( - // "CANDIDATE PUBKEY CHECK IN AUDIT TRIGGER", - // distributions_audit_trigger[candidatePublicKey] - // ); - if ( - distributions_audit_trigger && - distributions_audit_trigger[candidatePublicKey] - ) { - console.log('VOTING TRUE ON DISTRIBUTION AUDIT'); - const response = await this.distributionListAuditSubmission( - candidateKeyPairPublicKey, - isValid, - submitterAccountKeyPair, - round, - ); - console.log( - 'RESPONSE FROM DISTRIBUTION AUDIT FUNCTION', - response, - ); - } - } else if (isValid == false) { - // Call auditSubmission function and isValid is passed as false - console.log('RAISING AUDIT / VOTING FALSE ON DISTRIBUTION'); - const response = await this.distributionListAuditSubmission( - candidateKeyPairPublicKey, - isValid, - submitterAccountKeyPair, - round, - ); - console.log( - 'RESPONSE FROM DISTRIBUTION AUDIT FUNCTION', - response, - ); - } - } catch (err) { - console.log('ERROR IN ELSE CONDITION FOR DISTRIBUTION', err); - } - } - } - } - } - async getTaskLevelDBPath() { - if (taskNodeAdministered) { - return await genericHandler('getTaskLevelDBPath'); - } else { - return './KOIIDB'; - } - } - async getBasePath() { - if (taskNodeAdministered) { - const basePath = (await namespaceWrapper.getTaskLevelDBPath()).replace( - '/KOIIDB', - '', - ); - return basePath; - } else { - return './'; - } - } - - async getAverageSlotTime() { - if (taskNodeAdministered) { - try { - return await genericHandler('getAverageSlotTime'); - } catch (error) { - console.error('Error getting average slot time', error); - return 400; - } - } else { - return 400; - } - } - - async nodeSelectionDistributionList(round, isPreviousFailed) { - let taskAccountDataJSON = null; - try { - taskAccountDataJSON = await this.getTaskSubmissionInfo(round); - } catch (error) { - console.error('Task submission not found', error); - return; - } - - if (taskAccountDataJSON == null) { - console.error('Task state not found'); - return; - } - console.log('EXPECTED ROUND', round); - - const submissions = taskAccountDataJSON.submissions[round]; - if (submissions == null) { - console.log('No submisssions found in N-1 round'); - return 'No submisssions found in N-1 round'; - } else { - // getting last 3 submissions for the rounds - let keys; - const latestRounds = [round, round - 1, round - 2].filter(r => r >= 0); - - const promises = latestRounds.map(async r => { - if (r == round) { - return new Set(Object.keys(submissions)); - } else { - let roundSubmissions = null; - try { - roundSubmissions = await this.getTaskSubmissionInfo(r); - if (roundSubmissions && roundSubmissions.submissions[r]) { - return new Set(Object.keys(roundSubmissions.submissions[r])); - } - } catch (error) { - console.error('Error in getting submissions for the round', error); - } - return new Set(); - } - }); - - const keySets = await Promise.all(promises); - - // Find the keys present in all the rounds - keys = - keySets.length > 0 - ? [...keySets[0]].filter(key => keySets.every(set => set.has(key))) - : []; - if (keys.length == 0) { - console.log('No common keys found in last 3 rounds'); - keys = Object.keys(submissions); - } - console.log('KEYS', keys.length); - const values = keys.map(key => submissions[key]); - - let size = keys.length; - console.log('Submissions from N-2 round: ', size); - - // Check the keys i.e if the submitter shall be excluded or not - try { - const distributionData = await this.getTaskDistributionInfo(round); - const audit_record = distributionData?.distributions_audit_record; - if (audit_record && audit_record[round] == 'PayoutFailed') { - console.log('ROUND DATA', audit_record[round]); - console.log( - 'SUBMITTER LIST', - distributionData.distribution_rewards_submission[round], - ); - const submitterList = - distributionData.distribution_rewards_submission[round]; - const submitterKeys = Object.keys(submitterList); - console.log('SUBMITTER KEYS', submitterKeys); - const submitterSize = submitterKeys.length; - console.log('SUBMITTER SIZE', submitterSize); - - for (let j = 0; j < submitterSize; j++) { - console.log('SUBMITTER KEY CANDIDATE', submitterKeys[j]); - const id = keys.indexOf(submitterKeys[j]); - console.log('ID', id); - if (id != -1) { - keys.splice(id, 1); - values.splice(id, 1); - size--; - } - } - - console.log('KEYS FOR HASH CALC', keys.length); - } - } catch (error) { - console.log('Error in getting distribution data', error); - } - - // calculating the digest - - const ValuesString = JSON.stringify(values); - - const hashDigest = createHash('sha256') - .update(ValuesString) - .digest('hex'); - - console.log('HASH DIGEST', hashDigest); - - // function to calculate the score - const calculateScore = (str = '') => { - return str.split('').reduce((acc, val) => { - return acc + val.charCodeAt(0); - }, 0); - }; - - // function to compare the ASCII values - - const compareASCII = (str1, str2) => { - const firstScore = calculateScore(str1); - const secondScore = calculateScore(str2); - return Math.abs(firstScore - secondScore); - }; - - // loop through the keys and select the one with higest score - - const selectedNode = { - score: 0, - pubkey: '', - }; - let score = 0; - if (isPreviousFailed) { - let leastScore = -Infinity; - let secondLeastScore = -Infinity; - for (let i = 0; i < size; i++) { - const candidateSubmissionJson = {}; - candidateSubmissionJson[keys[i]] = values[i]; - const candidateSubmissionString = JSON.stringify( - candidateSubmissionJson, - ); - const candidateSubmissionHash = createHash('sha256') - .update(candidateSubmissionString) - .digest('hex'); - const candidateScore = compareASCII( - hashDigest, - candidateSubmissionHash, - ); - if (candidateScore > leastScore) { - secondLeastScore = leastScore; - leastScore = candidateScore; - } else if (candidateScore > secondLeastScore) { - secondLeastScore = candidateScore; - selectedNode.score = candidateScore; - selectedNode.pubkey = keys[i]; - } - } - } else { - for (let i = 0; i < size; i++) { - const candidateSubmissionJson = {}; - candidateSubmissionJson[keys[i]] = values[i]; - const candidateSubmissionString = JSON.stringify( - candidateSubmissionJson, - ); - const candidateSubmissionHash = createHash('sha256') - .update(candidateSubmissionString) - .digest('hex'); - const candidateScore = compareASCII( - hashDigest, - candidateSubmissionHash, - ); - // console.log('CANDIDATE SCORE', candidateScore); - if (candidateScore > score) { - score = candidateScore; - selectedNode.score = candidateScore; - selectedNode.pubkey = keys[i]; - } - } - } - - console.log('SELECTED NODE OBJECT', selectedNode); - return selectedNode.pubkey; - } - } - - async selectAndGenerateDistributionList( - submitDistributionList, - round, - isPreviousRoundFailed, - ) { - console.log('SelectAndGenerateDistributionList called'); - const selectedNode = await this.nodeSelectionDistributionList( - round, - isPreviousRoundFailed, - ); - console.log('Selected Node', selectedNode); - const submitPubKey = await this.getSubmitterAccount(); - if ( - selectedNode == undefined || - selectedNode == '' || - submitPubKey == undefined - ) - return; - if (selectedNode == submitPubKey?.publicKey.toBase58()) { - await submitDistributionList(round); - const taskState = await this.getTaskState({}); - if (taskState == null) { - console.error('Task state not found'); - return; - } - const avgSlotTime = await this.getAverageSlotTime(); - if (avgSlotTime == null) { - console.error('Avg slot time not found'); - return; - } - setTimeout(async () => { - await this.payoutTrigger(round); - }, (taskState.audit_window + taskState.submission_window) * avgSlotTime); - } - } - - getMainAccountPubkey() { - if (taskNodeAdministered) { - return MAIN_ACCOUNT_PUBKEY; - } else { - return this.#testingMainSystemAccount.publicKey.toBase58(); - } - } -} - -async function genericHandler(...args) { - try { - let response = await axios.post(BASE_ROOT_URL, { - args, - taskId: TASK_ID, - secret: SECRET_KEY, - }); - if (response.status == 200) return response.data.response; - else { - console.error(response.status, response.data); - return null; - } - } catch (err) { - console.error(`Error in genericHandler: "${args[0]}"`, err.message); - console.error(err?.response?.data); - return { error: err }; - } -} - -const namespaceWrapper = new NamespaceWrapper(); -if (taskNodeAdministered) { - namespaceWrapper.getRpcUrl().then(rpcUrl => { - console.log(rpcUrl, 'RPC URL'); - connection = new Connection(rpcUrl, 'confirmed'); - }); -} -module.exports = { - namespaceWrapper, - taskNodeAdministered, // Boolean flag indicating that the task is being ran in active mode (Task node supervised), or development (testing) mode - app, // The initialized express app to be used to register endpoints - TASK_ID, // This will be the PORT on which the this task is expected to run the express server coming from the task node running this task. As all communication via the task node and this task will be done on this port. - MAIN_ACCOUNT_PUBKEY, // This will be the secret used to authenticate with task node running this task. - SECRET_KEY, // This will be the secret used by the task to authenticate with task node running this task. - K2_NODE_URL, // This will be K2 url being used by the task node, possible values are 'https://k2-testnet.koii.live' | 'https://k2-devnet.koii.live' | 'http://localhost:8899' - SERVICE_URL, // This will be public task node endpoint (Or local if it doesn't have any) of the task node running this task. - STAKE, // This will be stake of the task node running this task, can be double checked with the task state and staking public key. - TASK_NODE_PORT, // This will be the port used by task node as the express server port, so it can be used by the task for the communication with the task node - _server, // Express server object -}; diff --git a/Lesson 3/simple-crawler/before/coreLogic.js b/Lesson 3/simple-crawler/before/coreLogic.js index 7b09d08..b210551 100644 --- a/Lesson 3/simple-crawler/before/coreLogic.js +++ b/Lesson 3/simple-crawler/before/coreLogic.js @@ -1,4 +1,4 @@ -const { namespaceWrapper } = require('./_koiiNode/koiiNode'); +const { namespaceWrapper } = require('@_koii/namespace-wrapper'); const task = require('./task'); class CoreLogic { diff --git a/Lesson 3/simple-crawler/before/index.js b/Lesson 3/simple-crawler/before/index.js index e8166ae..803068c 100644 --- a/Lesson 3/simple-crawler/before/index.js +++ b/Lesson 3/simple-crawler/before/index.js @@ -3,7 +3,7 @@ const { namespaceWrapper, taskNodeAdministered, app, -} = require('./_koiiNode/koiiNode'); +} = require('@_koii/namespace-wrapper'); if (app) { // Write your Express Endpoints here. @@ -59,13 +59,13 @@ async function setup() { /* GUIDE TO CALLS K2 FUNCTIONS MANUALLY - If you wish to do the development by avoiding the timers then you can do the intended calls to K2 - directly using these function calls. + If you wish to do the development by avoiding the timers then you can do the intended calls to K2 + directly using these function calls. To disable timers please set the TIMERS flag in task-node ENV to disable NOTE : K2 will still have the windows to accept the submission value, audit, so you are expected - to make calls in the intended slots of your round time. + to make calls in the intended slots of your round time. */ diff --git a/Lesson 3/simple-crawler/before/package.json b/Lesson 3/simple-crawler/before/package.json index 0406a57..e1532ee 100644 --- a/Lesson 3/simple-crawler/before/package.json +++ b/Lesson 3/simple-crawler/before/package.json @@ -16,6 +16,8 @@ "license": "ISC", "dependencies": { "@_koi/web3.js": "^0.0.6", + "@_koii/namespace-wrapper": "^1.0.1", + "@_koii/storage-task-sdk": "^1.2.4", "axios": "^0.27.2", "body-parser": "^1.20.2", "cross-spawn": "^7.0.3", diff --git a/Lesson 3/simple-crawler/before/task/audit.js b/Lesson 3/simple-crawler/before/task/audit.js index 8e580c0..ba99e54 100644 --- a/Lesson 3/simple-crawler/before/task/audit.js +++ b/Lesson 3/simple-crawler/before/task/audit.js @@ -1,4 +1,4 @@ -const { namespaceWrapper } = require('../_koiiNode/koiiNode'); +const { namespaceWrapper } = require('@_koii/namespace-wrapper'); class Audit { /** diff --git a/Lesson 3/simple-crawler/before/task/distribution.js b/Lesson 3/simple-crawler/before/task/distribution.js index 6b7c83b..f07071c 100644 --- a/Lesson 3/simple-crawler/before/task/distribution.js +++ b/Lesson 3/simple-crawler/before/task/distribution.js @@ -1,4 +1,4 @@ -const { namespaceWrapper } = require('../_koiiNode/koiiNode'); +const { namespaceWrapper } = require('@_koii/namespace-wrapper'); class Distribution { /** diff --git a/Lesson 3/simple-crawler/before/task/submission.js b/Lesson 3/simple-crawler/before/task/submission.js index 342663f..46849cf 100644 --- a/Lesson 3/simple-crawler/before/task/submission.js +++ b/Lesson 3/simple-crawler/before/task/submission.js @@ -1,4 +1,4 @@ -const { namespaceWrapper } = require('../_koiiNode/koiiNode'); +const { namespaceWrapper } = require('@_koii/namespace-wrapper'); const { KoiiStorageClient } = require('@_koii/storage-task-sdk'); const fs = require('fs'); diff --git a/Lesson 3/simple-crawler/before/tests/main.test.js b/Lesson 3/simple-crawler/before/tests/main.test.js index 1eb8a52..05f5352 100644 --- a/Lesson 3/simple-crawler/before/tests/main.test.js +++ b/Lesson 3/simple-crawler/before/tests/main.test.js @@ -1,5 +1,5 @@ const { coreLogic } = require('../coreLogic'); -const { namespaceWrapper, _server } = require('../_koiiNode/koiiNode'); +const { namespaceWrapper, _server } = require('@_koii/namespace-wrapper'); const Joi = require('joi'); const axios = require('axios'); beforeAll(async () => { diff --git a/Lesson 4/caesar-task/after/_koiiNode/koiiNode.js b/Lesson 4/caesar-task/after/_koiiNode/koiiNode.js deleted file mode 100644 index 6f17a8d..0000000 --- a/Lesson 4/caesar-task/after/_koiiNode/koiiNode.js +++ /dev/null @@ -1,1155 +0,0 @@ -const { default: axios } = require('axios'); -const { createHash } = require('crypto'); - -const { Connection, PublicKey, Keypair } = require('@_koi/web3.js'); - -const Datastore = require('nedb-promises'); -const fsPromises = require('fs/promises'); -const bs58 = require('bs58'); -const nacl = require('tweetnacl'); - -/****************************************** init.js ***********************************/ - -const express = require('express'); -// Only used for testing purposes, in production the env will be injected by tasknode -require('dotenv').config(); -const bodyParser = require('body-parser'); -/** - * This will be the name of the current task as coming from the task node running this task. - */ -const TASK_NAME = process.argv[2] || 'Local'; -/** - * This will be the id of the current task as coming from the task node running this task. - */ -const TASK_ID = process.argv[3]; -/** - * This will be the PORT on which the this task is expected to run the express server coming from the task node running this task. - * As all communication via the task node and this task will be done on this port. - */ -const EXPRESS_PORT = process.argv[4] || 10000; - -const LogLevel = { - Log: 'log', - Warn: 'warn', - Error: 'error', -}; - -// Not used anymore -// const NODE_MODE = process.argv[5]; - -/** - * This will be the main account public key in string format of the task node running this task. - */ -const MAIN_ACCOUNT_PUBKEY = process.argv[6]; -/** - * This will be the secret used by the task to authenticate with task node running this task. - */ -const SECRET_KEY = process.argv[7]; -/** - * This will be K2 url being used by the task node, possible values are 'https://k2-testnet.koii.live' | 'https://k2-devnet.koii.live' | 'http://localhost:8899' - */ -const K2_NODE_URL = process.argv[8] || 'https://k2-testnet.koii.live'; -/** - * This will be public task node endpoint (Or local if it doesn't have any) of the task node running this task. - */ -const SERVICE_URL = process.argv[9]; -/** - * This will be stake of the task node running this task, can be double checked with the task state and staking public key. - */ -const STAKE = Number(process.argv[10]); -/** - * This will be the port used by task node as the express server port, so it can be used by the task for the communication with the task node - */ -const TASK_NODE_PORT = Number(process.argv[11]); - -const app = express(); - -console.log('SETTING UP EXPRESS'); - -app.use(bodyParser.urlencoded({ extended: false })); - -app.use(bodyParser.json()); - -app.use((req, res, next) => { - res.setHeader('Access-Control-Allow-Origin', '*'); - res.setHeader( - 'Access-Control-Allow-Methods', - 'GET, POST, PUT, PATCH, DELETE', - ); - res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); - res.setHeader('Access-Control-Allow-Credentials', false); - if (req.method === 'OPTIONS') - // if is preflight(OPTIONS) then response status 204(NO CONTENT) - return res.send(204); - next(); -}); - -app.get('/', (req, res) => { - res.send('Hello World!'); -}); - -const _server = app.listen(EXPRESS_PORT, () => { - console.log(`${TASK_NAME} listening on port ${EXPRESS_PORT}`); -}); - -/****************************************** NamespaceWrapper.js ***********************************/ - -const taskNodeAdministered = !!TASK_ID; -const BASE_ROOT_URL = `http://localhost:${TASK_NODE_PORT}/namespace-wrapper`; -let connection; - -class NamespaceWrapper { - #db; - #testingMainSystemAccount; - #testingStakingSystemAccount; - #testingTaskState; - #testingDistributionList; - - constructor() { - if (taskNodeAdministered) { - this.initializeDB(); - } else { - this.#db = Datastore.create('./localKOIIDB.db'); - this.defaultTaskSetup(); - } - } - - async initializeDB() { - if (this.#db) return; - try { - if (taskNodeAdministered) { - const path = await this.getTaskLevelDBPath(); - this.#db = Datastore.create(path); - } else { - this.#db = Datastore.create('./localKOIIDB.db'); - } - } catch (e) { - this.#db = Datastore.create(`../namespace/${TASK_ID}/KOIILevelDB.db`); - } - } - - async getDb() { - if (this.#db) return this.#db; - await this.initializeDB(); - return this.#db; - } - /** - * Namespace wrapper of storeGetAsync - * @param {string} key // Path to get - */ - async storeGet(key) { - try { - await this.initializeDB(); - const resp = await this.#db.findOne({ key: key }); - if (resp) { - return resp[key]; - } else { - return null; - } - } catch (e) { - console.error(e); - return null; - } - } - /** - * Namespace wrapper over storeSetAsync - * @param {string} key Path to set - * @param {*} value Data to set - */ - async storeSet(key, value) { - try { - await this.initializeDB(); - await this.#db.update( - { key: key }, - { [key]: value, key }, - { upsert: true }, - ); - } catch (e) { - console.error(e); - return undefined; - } - } - - /** - * Namespace wrapper over fsPromises methods - * @param {*} method The fsPromise method to call - * @param {*} path Path for the express call - * @param {...any} args Remaining parameters for the FS call - */ - async fs(method, path, ...args) { - if (taskNodeAdministered) { - return await genericHandler('fs', method, path, ...args); - } else { - return fsPromises[method](`${path}`, ...args); - } - } - async fsStaking(method, path, ...args) { - if (taskNodeAdministered) { - return await genericHandler('fsStaking', method, path, ...args); - } else { - return fsPromises[method](`${path}`, ...args); - } - } - - async fsWriteStream(imagepath) { - if (taskNodeAdministered) { - return await genericHandler('fsWriteStream', imagepath); - } else { - const writer = createWriteStream(imagepath); - return writer; - } - } - async fsReadStream(imagepath) { - if (taskNodeAdministered) { - return await genericHandler('fsReadStream', imagepath); - } else { - const file = readFileSync(imagepath); - return file; - } - } - - /** - * Namespace wrapper for getting current slots - */ - async getSlot() { - if (taskNodeAdministered) { - return await genericHandler('getCurrentSlot'); - } else { - return 100; - } - } - - async payloadSigning(body) { - if (taskNodeAdministered) { - return await genericHandler('signData', body); - } else { - const msg = new TextEncoder().encode(JSON.stringify(body)); - const signedMessage = nacl.sign( - msg, - this.#testingMainSystemAccount.secretKey, - ); - return await this.bs58Encode(signedMessage); - } - } - - async bs58Encode(data) { - return bs58.encode( - Buffer.from(data.buffer, data.byteOffset, data.byteLength), - ); - } - - async bs58Decode(data) { - return new Uint8Array(bs58.decode(data)); - } - - decodePayload(payload) { - return new TextDecoder().decode(payload); - } - - /** - * Namespace wrapper of storeGetAsync - * @param {string} signedMessage r // Path to get - */ - - async verifySignature(signedMessage, pubKey) { - if (taskNodeAdministered) { - return await genericHandler('verifySignedData', signedMessage, pubKey); - } else { - try { - const payload = nacl.sign.open( - await this.bs58Decode(signedMessage), - await this.bs58Decode(pubKey), - ); - if (!payload) return { error: 'Invalid signature' }; - return { data: this.decodePayload(payload) }; - } catch (e) { - console.error(e); - return { error: `Verification failed: ${e}` }; - } - } - } - - // async submissionOnChain(submitterKeypair, submission) { - // return await genericHandler( - // 'submissionOnChain', - // submitterKeypair, - // submission, - // ); - // } - - async stakeOnChain( - taskStateInfoPublicKey, - stakingAccKeypair, - stakePotAccount, - stakeAmount, - ) { - if (taskNodeAdministered) { - return await genericHandler( - 'stakeOnChain', - taskStateInfoPublicKey, - stakingAccKeypair, - stakePotAccount, - stakeAmount, - ); - } else { - this.#testingTaskState.stake_list[ - this.#testingStakingSystemAccount.publicKey.toBase58() - ] = stakeAmount; - } - } - async claimReward(stakePotAccount, beneficiaryAccount, claimerKeypair) { - if (!taskNodeAdministered) { - console.log('Cannot call sendTransaction in testing mode'); - return; - } - return await genericHandler( - 'claimReward', - stakePotAccount, - beneficiaryAccount, - claimerKeypair, - ); - } - async sendTransaction(serviceNodeAccount, beneficiaryAccount, amount) { - if (!taskNodeAdministered) { - console.log('Cannot call sendTransaction in testing mode'); - return; - } - return await genericHandler( - 'sendTransaction', - serviceNodeAccount, - beneficiaryAccount, - amount, - ); - } - - async getSubmitterAccount() { - if (taskNodeAdministered) { - const submitterAccountResp = await genericHandler('getSubmitterAccount'); - return Keypair.fromSecretKey( - Uint8Array.from(Object.values(submitterAccountResp._keypair.secretKey)), - ); - } else { - return this.#testingStakingSystemAccount; - } - } - - /** - * sendAndConfirmTransaction wrapper that injects mainSystemWallet as the first signer for paying the tx fees - * @param {connection} method // Receive method ["get", "post", "put", "delete"] - * @param {transaction} path // Endpoint path appended to namespace - * @param {Function} callback // Callback function on traffic receive - */ - async sendAndConfirmTransactionWrapper(transaction, signers) { - if (!taskNodeAdministered) { - console.log('Cannot call sendTransaction in testing mode'); - return; - } - const blockhash = (await connection.getRecentBlockhash('finalized')) - .blockhash; - transaction.recentBlockhash = blockhash; - transaction.feePayer = new PublicKey(MAIN_ACCOUNT_PUBKEY); - return await genericHandler( - 'sendAndConfirmTransactionWrapper', - transaction.serialize({ - requireAllSignatures: false, - verifySignatures: false, - }), - signers, - ); - } - - // async signArweave(transaction) { - // let tx = await genericHandler('signArweave', transaction.toJSON()); - // return arweave.transactions.fromRaw(tx); - // } - // async signEth(transaction) { - // return await genericHandler('signEth', transaction); - // } - async getTaskState(options) { - if (taskNodeAdministered) { - const response = await genericHandler('getTaskState', options); - if (response.error) { - console.log('Error in getting task state', response.error); - return null; - } - return response; - } else { - return this.#testingTaskState; - } - } - - async logMessage(level, message, action) { - switch (level) { - case LogLevel.Log: - console.log(message, action); - break; - case LogLevel.Warn: - console.warn(message, action); - break; - case LogLevel.Error: - console.error(message, action); - break; - default: - console.log( - `Invalid log level: ${level}. The log levels can be log, warn or error`, - ); - return false; - } - return true; - } - - /** - * This logger function is used to log the task erros , warnings and logs on desktop-node - * @param {level} enum // Receive method ["Log", "Warn", "Error"] - enum LogLevel { - Log = 'log', - Warn = 'warn', - Error = 'error', - } - * @param {message} string // log, error or warning message - * @returns {boolean} // true if the message is logged successfully otherwise false - */ - - async logger(level, message, action) { - if (taskNodeAdministered) { - return await genericHandler('logger', level, message, action); - } else { - return await this.logMessage(level, message, action); - } - } - - async auditSubmission(candidatePubkey, isValid, voterKeypair, round) { - if (taskNodeAdministered) { - return await genericHandler( - 'auditSubmission', - candidatePubkey, - isValid, - round, - ); - } else { - if ( - this.#testingTaskState.submissions_audit_trigger[round] && - this.#testingTaskState.submissions_audit_trigger[round][candidatePubkey] - ) { - this.#testingTaskState.submissions_audit_trigger[round][ - candidatePubkey - ].votes.push({ - is_valid: isValid, - voter: voterKeypair.pubKey.toBase58(), - slot: 100, - }); - } else { - this.#testingTaskState.submissions_audit_trigger[round] = { - [candidatePubkey]: { - trigger_by: this.#testingStakingSystemAccount.publicKey.toBase58(), - slot: 100, - votes: [], - }, - }; - } - } - } - - async distributionListAuditSubmission( - candidatePubkey, - isValid, - voterKeypair, - round, - ) { - if (taskNodeAdministered) { - return await genericHandler( - 'distributionListAuditSubmission', - candidatePubkey, - isValid, - round, - ); - } else { - if ( - this.#testingTaskState.distributions_audit_trigger[round] && - this.#testingTaskState.distributions_audit_trigger[round][ - candidatePubkey - ] - ) { - this.#testingTaskState.distributions_audit_trigger[round][ - candidatePubkey - ].votes.push({ - is_valid: isValid, - voter: voterKeypair.pubKey.toBase58(), - slot: 100, - }); - } else { - this.#testingTaskState.distributions_audit_trigger[round] = { - [candidatePubkey]: { - trigger_by: this.#testingStakingSystemAccount.publicKey.toBase58(), - slot: 100, - votes: [], - }, - }; - } - } - } - - async getRound() { - if (taskNodeAdministered) { - return await genericHandler('getRound'); - } else { - return 1; - } - } - - async payoutTrigger(round) { - if (taskNodeAdministered) { - return await genericHandler('payloadTrigger', round); - } else { - console.log( - 'Payout Trigger only handles possitive flows (Without audits)', - ); - let round = 1; - const submissionValAcc = - this.#testingDistributionList[round][ - this.#testingStakingSystemAccount.toBase58() - ].submission_value; - this.#testingTaskState.available_balances = - this.#testingDistributionList[round][submissionValAcc]; - } - } - - async uploadDistributionList(distributionList, round) { - if (taskNodeAdministered) { - return await genericHandler( - 'uploadDistributionList', - distributionList, - round, - ); - } else { - if (!this.#testingDistributionList[round]) - this.#testingDistributionList[round] = {}; - - this.#testingDistributionList[round][ - this.#testingStakingSystemAccount.publicKey.toBase58() - ] = Buffer.from(JSON.stringify(distributionList)); - return true; - } - } - - async distributionListSubmissionOnChain(round) { - if (taskNodeAdministered) { - return await genericHandler('distributionListSubmissionOnChain', round); - } else { - if (!this.#testingTaskState.distribution_rewards_submission[round]) - this.#testingTaskState.distribution_rewards_submission[round] = {}; - - this.#testingTaskState.distribution_rewards_submission[round][ - this.#testingStakingSystemAccount.publicKey.toBase58() - ] = { - submission_value: - this.#testingStakingSystemAccount.publicKey.toBase58(), - slot: 200, - round: 1, - }; - } - } - - async checkSubmissionAndUpdateRound(submissionValue = 'default', round) { - if (taskNodeAdministered) { - return await genericHandler( - 'checkSubmissionAndUpdateRound', - submissionValue, - round, - ); - } else { - if (!this.#testingTaskState.submissions[round]) - this.#testingTaskState.submissions[round] = {}; - this.#testingTaskState.submissions[round][ - this.#testingStakingSystemAccount.publicKey.toBase58() - ] = { - submission_value: submissionValue, - slot: 100, - round: 1, - }; - } - } - async getProgramAccounts() { - if (taskNodeAdministered) { - return await genericHandler('getProgramAccounts'); - } else { - console.log('Cannot call getProgramAccounts in testing mode'); - } - } - async defaultTaskSetup() { - if (taskNodeAdministered) { - return await genericHandler('defaultTaskSetup'); - } else { - if (this.#testingTaskState) return; - this.#testingMainSystemAccount = new Keypair(); - this.#testingStakingSystemAccount = new Keypair(); - this.#testingDistributionList = {}; - this.#testingTaskState = { - task_name: 'DummyTestState', - task_description: 'Dummy Task state for testing flow', - submissions: {}, - submissions_audit_trigger: {}, - total_bounty_amount: 10000000000, - bounty_amount_per_round: 1000000000, - total_stake_amount: 50000000000, - minimum_stake_amount: 5000000000, - available_balances: {}, - stake_list: {}, - round_time: 600, - starting_slot: 0, - audit_window: 200, - submission_window: 200, - distribution_rewards_submission: {}, - distributions_audit_trigger: {}, - }; - } - } - async getRpcUrl() { - if (taskNodeAdministered) { - return await genericHandler('getRpcUrl'); - } else { - console.log('Cannot call getNodes in testing mode'); - } - } - async getNodes(url) { - if (taskNodeAdministered) { - return await genericHandler('getNodes', url); - } else { - console.log('Cannot call getNodes in testing mode'); - } - } - - async getDistributionList(publicKey, round) { - if (taskNodeAdministered) { - const response = await genericHandler( - 'getDistributionList', - publicKey, - round, - ); - if (response.error) { - return null; - } - return response; - } else { - const submissionValAcc = - this.#testingTaskState.distribution_rewards_submission[round][ - this.#testingStakingSystemAccount.publicKey.toBase58() - ].submission_value; - return this.#testingDistributionList[round][submissionValAcc]; - } - } - - async getTaskSubmissionInfo(round) { - if (taskNodeAdministered) { - const taskSubmissionInfo = await genericHandler( - 'getTaskSubmissionInfo', - round, - ); - if (taskSubmissionInfo.error) { - return null; - } - return taskSubmissionInfo; - } else { - return this.#testingTaskState.submissions[round]; - } - } - - async validateAndVoteOnNodes(validate, round) { - console.log('******/ IN VOTING /******'); - let taskAccountDataJSON = null; - try { - taskAccountDataJSON = await this.getTaskSubmissionInfo(round); - } catch (error) { - console.error('Error in getting submissions for the round', error); - } - if (taskAccountDataJSON == null) { - console.log('No submissions found for the round', round); - return; - } - console.log( - `Fetching the submissions of round ${round}`, - taskAccountDataJSON.submissions[round], - ); - const submissions = taskAccountDataJSON.submissions[round]; - if (submissions == null) { - console.log(`No submisssions found in round ${round}`); - return `No submisssions found in round ${round}`; - } else { - const keys = Object.keys(submissions); - const values = Object.values(submissions); - const size = values.length; - console.log('Submissions from last round: ', keys, values, size); - let isValid; - const submitterAccountKeyPair = await this.getSubmitterAccount(); - const submitterPubkey = submitterAccountKeyPair.publicKey.toBase58(); - for (let i = 0; i < size; i++) { - let candidatePublicKey = keys[i]; - console.log('FOR CANDIDATE KEY', candidatePublicKey); - let candidateKeyPairPublicKey = new PublicKey(keys[i]); - if (candidatePublicKey == submitterPubkey) { - console.log('YOU CANNOT VOTE ON YOUR OWN SUBMISSIONS'); - } else { - try { - console.log( - 'SUBMISSION VALUE TO CHECK', - values[i].submission_value, - ); - isValid = await validate(values[i].submission_value, round); - console.log(`Voting ${isValid} to ${candidatePublicKey}`); - - if (isValid) { - // check for the submissions_audit_trigger , if it exists then vote true on that otherwise do nothing - const submissions_audit_trigger = - taskAccountDataJSON.submissions_audit_trigger[round]; - console.log('SUBMIT AUDIT TRIGGER', submissions_audit_trigger); - // console.log( - // "CANDIDATE PUBKEY CHECK IN AUDIT TRIGGER", - // submissions_audit_trigger[candidatePublicKey] - // ); - if ( - submissions_audit_trigger && - submissions_audit_trigger[candidatePublicKey] - ) { - console.log('VOTING TRUE ON AUDIT'); - const response = await this.auditSubmission( - candidateKeyPairPublicKey, - isValid, - submitterAccountKeyPair, - round, - ); - console.log('RESPONSE FROM AUDIT FUNCTION', response); - } - } else if (isValid == false) { - // Call auditSubmission function and isValid is passed as false - console.log('RAISING AUDIT / VOTING FALSE'); - const response = await this.auditSubmission( - candidateKeyPairPublicKey, - isValid, - submitterAccountKeyPair, - round, - ); - console.log('RESPONSE FROM AUDIT FUNCTION', response); - } - } catch (err) { - console.log('ERROR IN ELSE CONDITION', err); - } - } - } - } - } - - async getTaskDistributionInfo(round) { - if (taskNodeAdministered) { - const taskDistributionInfo = await genericHandler( - 'getTaskDistributionInfo', - round, - ); - if (taskDistributionInfo.error) { - return null; - } - return taskDistributionInfo; - } else { - return this.#testingTaskState.distribution_rewards_submission[round]; - } - } - - async validateAndVoteOnDistributionList(validateDistribution, round) { - // await this.checkVoteStatus(); - console.log('******/ IN VOTING OF DISTRIBUTION LIST /******'); - let taskAccountDataJSON = null; - try { - taskAccountDataJSON = await this.getTaskDistributionInfo(round); - } catch (error) { - console.error('Error in getting distributions for the round', error); - } - if (taskAccountDataJSON == null) { - console.log('No distribution submissions found for the round', round); - return; - } - console.log( - `Fetching the Distribution submissions of round ${round}`, - taskAccountDataJSON.distribution_rewards_submission[round], - ); - const submissions = - taskAccountDataJSON?.distribution_rewards_submission[round]; - if ( - submissions == null || - submissions == undefined || - submissions.length == 0 - ) { - console.log(`No submisssions found in round ${round}`); - return `No submisssions found in round ${round}`; - } else { - const keys = Object.keys(submissions); - const values = Object.values(submissions); - const size = values.length; - console.log( - 'Distribution Submissions from last round: ', - keys, - values, - size, - ); - let isValid; - const submitterAccountKeyPair = await this.getSubmitterAccount(); - const submitterPubkey = submitterAccountKeyPair.publicKey.toBase58(); - - for (let i = 0; i < size; i++) { - let candidatePublicKey = keys[i]; - console.log('FOR CANDIDATE KEY', candidatePublicKey); - let candidateKeyPairPublicKey = new PublicKey(keys[i]); - if (candidatePublicKey == submitterPubkey) { - console.log('YOU CANNOT VOTE ON YOUR OWN DISTRIBUTION SUBMISSIONS'); - } else { - try { - console.log( - 'DISTRIBUTION SUBMISSION VALUE TO CHECK', - values[i].submission_value, - ); - isValid = await validateDistribution( - values[i].submission_value, - round, - ); - console.log(`Voting ${isValid} to ${candidatePublicKey}`); - - if (isValid) { - // check for the submissions_audit_trigger , if it exists then vote true on that otherwise do nothing - const distributions_audit_trigger = - taskAccountDataJSON.distributions_audit_trigger[round]; - console.log( - 'SUBMIT DISTRIBUTION AUDIT TRIGGER', - distributions_audit_trigger, - ); - // console.log( - // "CANDIDATE PUBKEY CHECK IN AUDIT TRIGGER", - // distributions_audit_trigger[candidatePublicKey] - // ); - if ( - distributions_audit_trigger && - distributions_audit_trigger[candidatePublicKey] - ) { - console.log('VOTING TRUE ON DISTRIBUTION AUDIT'); - const response = await this.distributionListAuditSubmission( - candidateKeyPairPublicKey, - isValid, - submitterAccountKeyPair, - round, - ); - console.log( - 'RESPONSE FROM DISTRIBUTION AUDIT FUNCTION', - response, - ); - } - } else if (isValid == false) { - // Call auditSubmission function and isValid is passed as false - console.log('RAISING AUDIT / VOTING FALSE ON DISTRIBUTION'); - const response = await this.distributionListAuditSubmission( - candidateKeyPairPublicKey, - isValid, - submitterAccountKeyPair, - round, - ); - console.log( - 'RESPONSE FROM DISTRIBUTION AUDIT FUNCTION', - response, - ); - } - } catch (err) { - console.log('ERROR IN ELSE CONDITION FOR DISTRIBUTION', err); - } - } - } - } - } - async getTaskLevelDBPath() { - if (taskNodeAdministered) { - return await genericHandler('getTaskLevelDBPath'); - } else { - return './KOIIDB'; - } - } - async getBasePath() { - if (taskNodeAdministered) { - const basePath = (await namespaceWrapper.getTaskLevelDBPath()).replace( - '/KOIIDB', - '', - ); - return basePath; - } else { - return './'; - } - } - - async getAverageSlotTime() { - if (taskNodeAdministered) { - try { - return await genericHandler('getAverageSlotTime'); - } catch (error) { - console.error('Error getting average slot time', error); - return 400; - } - } else { - return 400; - } - } - - async nodeSelectionDistributionList(round, isPreviousFailed) { - let taskAccountDataJSON = null; - try { - taskAccountDataJSON = await this.getTaskSubmissionInfo(round); - } catch (error) { - console.error('Task submission not found', error); - return; - } - - if (taskAccountDataJSON == null) { - console.error('Task state not found'); - return; - } - console.log('EXPECTED ROUND', round); - - const submissions = taskAccountDataJSON.submissions[round]; - if (submissions == null) { - console.log('No submisssions found in N-1 round'); - return 'No submisssions found in N-1 round'; - } else { - // getting last 3 submissions for the rounds - let keys; - const latestRounds = [round, round - 1, round - 2].filter(r => r >= 0); - - const promises = latestRounds.map(async r => { - if (r == round) { - return new Set(Object.keys(submissions)); - } else { - let roundSubmissions = null; - try { - roundSubmissions = await this.getTaskSubmissionInfo(r); - if (roundSubmissions && roundSubmissions.submissions[r]) { - return new Set(Object.keys(roundSubmissions.submissions[r])); - } - } catch (error) { - console.error('Error in getting submissions for the round', error); - } - return new Set(); - } - }); - - const keySets = await Promise.all(promises); - - // Find the keys present in all the rounds - keys = - keySets.length > 0 - ? [...keySets[0]].filter(key => keySets.every(set => set.has(key))) - : []; - if (keys.length == 0) { - console.log('No common keys found in last 3 rounds'); - keys = Object.keys(submissions); - } - console.log('KEYS', keys.length); - const values = keys.map(key => submissions[key]); - - let size = keys.length; - console.log('Submissions from N-2 round: ', size); - - // Check the keys i.e if the submitter shall be excluded or not - try { - const distributionData = await this.getTaskDistributionInfo(round); - const audit_record = distributionData?.distributions_audit_record; - if (audit_record && audit_record[round] == 'PayoutFailed') { - console.log('ROUND DATA', audit_record[round]); - console.log( - 'SUBMITTER LIST', - distributionData.distribution_rewards_submission[round], - ); - const submitterList = - distributionData.distribution_rewards_submission[round]; - const submitterKeys = Object.keys(submitterList); - console.log('SUBMITTER KEYS', submitterKeys); - const submitterSize = submitterKeys.length; - console.log('SUBMITTER SIZE', submitterSize); - - for (let j = 0; j < submitterSize; j++) { - console.log('SUBMITTER KEY CANDIDATE', submitterKeys[j]); - const id = keys.indexOf(submitterKeys[j]); - console.log('ID', id); - if (id != -1) { - keys.splice(id, 1); - values.splice(id, 1); - size--; - } - } - - console.log('KEYS FOR HASH CALC', keys.length); - } - } catch (error) { - console.log('Error in getting distribution data', error); - } - - // calculating the digest - - const ValuesString = JSON.stringify(values); - - const hashDigest = createHash('sha256') - .update(ValuesString) - .digest('hex'); - - console.log('HASH DIGEST', hashDigest); - - // function to calculate the score - const calculateScore = (str = '') => { - return str.split('').reduce((acc, val) => { - return acc + val.charCodeAt(0); - }, 0); - }; - - // function to compare the ASCII values - - const compareASCII = (str1, str2) => { - const firstScore = calculateScore(str1); - const secondScore = calculateScore(str2); - return Math.abs(firstScore - secondScore); - }; - - // loop through the keys and select the one with higest score - - const selectedNode = { - score: 0, - pubkey: '', - }; - let score = 0; - if (isPreviousFailed) { - let leastScore = -Infinity; - let secondLeastScore = -Infinity; - for (let i = 0; i < size; i++) { - const candidateSubmissionJson = {}; - candidateSubmissionJson[keys[i]] = values[i]; - const candidateSubmissionString = JSON.stringify( - candidateSubmissionJson, - ); - const candidateSubmissionHash = createHash('sha256') - .update(candidateSubmissionString) - .digest('hex'); - const candidateScore = compareASCII( - hashDigest, - candidateSubmissionHash, - ); - if (candidateScore > leastScore) { - secondLeastScore = leastScore; - leastScore = candidateScore; - } else if (candidateScore > secondLeastScore) { - secondLeastScore = candidateScore; - selectedNode.score = candidateScore; - selectedNode.pubkey = keys[i]; - } - } - } else { - for (let i = 0; i < size; i++) { - const candidateSubmissionJson = {}; - candidateSubmissionJson[keys[i]] = values[i]; - const candidateSubmissionString = JSON.stringify( - candidateSubmissionJson, - ); - const candidateSubmissionHash = createHash('sha256') - .update(candidateSubmissionString) - .digest('hex'); - const candidateScore = compareASCII( - hashDigest, - candidateSubmissionHash, - ); - // console.log('CANDIDATE SCORE', candidateScore); - if (candidateScore > score) { - score = candidateScore; - selectedNode.score = candidateScore; - selectedNode.pubkey = keys[i]; - } - } - } - - console.log('SELECTED NODE OBJECT', selectedNode); - return selectedNode.pubkey; - } - } - - async selectAndGenerateDistributionList( - submitDistributionList, - round, - isPreviousRoundFailed, - ) { - console.log('SelectAndGenerateDistributionList called'); - const selectedNode = await this.nodeSelectionDistributionList( - round, - isPreviousRoundFailed, - ); - console.log('Selected Node', selectedNode); - const submitPubKey = await this.getSubmitterAccount(); - if ( - selectedNode == undefined || - selectedNode == '' || - submitPubKey == undefined - ) - return; - if (selectedNode == submitPubKey?.publicKey.toBase58()) { - await submitDistributionList(round); - const taskState = await this.getTaskState({}); - if (taskState == null) { - console.error('Task state not found'); - return; - } - const avgSlotTime = await this.getAverageSlotTime(); - if (avgSlotTime == null) { - console.error('Avg slot time not found'); - return; - } - setTimeout(async () => { - await this.payoutTrigger(round); - }, (taskState.audit_window + taskState.submission_window) * avgSlotTime); - } - } - - getMainAccountPubkey() { - if (taskNodeAdministered) { - return MAIN_ACCOUNT_PUBKEY; - } else { - return this.#testingMainSystemAccount.publicKey.toBase58(); - } - } -} - -async function genericHandler(...args) { - try { - let response = await axios.post(BASE_ROOT_URL, { - args, - taskId: TASK_ID, - secret: SECRET_KEY, - }); - if (response.status == 200) return response.data.response; - else { - console.error(response.status, response.data); - return null; - } - } catch (err) { - console.error(`Error in genericHandler: "${args[0]}"`, err.message); - console.error(err?.response?.data); - return { error: err }; - } -} - -const namespaceWrapper = new NamespaceWrapper(); -if (taskNodeAdministered) { - namespaceWrapper.getRpcUrl().then(rpcUrl => { - console.log(rpcUrl, 'RPC URL'); - connection = new Connection(rpcUrl, 'confirmed'); - }); -} -module.exports = { - namespaceWrapper, - taskNodeAdministered, // Boolean flag indicating that the task is being ran in active mode (Task node supervised), or development (testing) mode - app, // The initialized express app to be used to register endpoints - TASK_ID, // This will be the PORT on which the this task is expected to run the express server coming from the task node running this task. As all communication via the task node and this task will be done on this port. - MAIN_ACCOUNT_PUBKEY, // This will be the secret used to authenticate with task node running this task. - SECRET_KEY, // This will be the secret used by the task to authenticate with task node running this task. - K2_NODE_URL, // This will be K2 url being used by the task node, possible values are 'https://k2-testnet.koii.live' | 'https://k2-devnet.koii.live' | 'http://localhost:8899' - SERVICE_URL, // This will be public task node endpoint (Or local if it doesn't have any) of the task node running this task. - STAKE, // This will be stake of the task node running this task, can be double checked with the task state and staking public key. - TASK_NODE_PORT, // This will be the port used by task node as the express server port, so it can be used by the task for the communication with the task node - _server, // Express server object -}; diff --git a/Lesson 4/caesar-task/after/coreLogic.js b/Lesson 4/caesar-task/after/coreLogic.js index 7b09d08..b210551 100644 --- a/Lesson 4/caesar-task/after/coreLogic.js +++ b/Lesson 4/caesar-task/after/coreLogic.js @@ -1,4 +1,4 @@ -const { namespaceWrapper } = require('./_koiiNode/koiiNode'); +const { namespaceWrapper } = require('@_koii/namespace-wrapper'); const task = require('./task'); class CoreLogic { diff --git a/Lesson 4/caesar-task/after/index.js b/Lesson 4/caesar-task/after/index.js index 75723ec..17ac502 100644 --- a/Lesson 4/caesar-task/after/index.js +++ b/Lesson 4/caesar-task/after/index.js @@ -3,12 +3,12 @@ const { namespaceWrapper, taskNodeAdministered, app, -} = require('./_koiiNode/koiiNode'); +} = require('@_koii/namespace-wrapper'); if (app) { // Write your Express Endpoints here. // Ex. app.post('/accept-cid', async (req, res) => {}) - + // Sample API that return your task state app.get('/taskState', async (req, res) => { const state = await namespaceWrapper.getTaskState(); @@ -54,13 +54,13 @@ async function setup() { /* GUIDE TO CALLS K2 FUNCTIONS MANUALLY - If you wish to do the development by avoiding the timers then you can do the intended calls to K2 - directly using these function calls. + If you wish to do the development by avoiding the timers then you can do the intended calls to K2 + directly using these function calls. To disable timers please set the TIMERS flag in task-node ENV to disable NOTE : K2 will still have the windows to accept the submission value, audit, so you are expected - to make calls in the intended slots of your round time. + to make calls in the intended slots of your round time. */ diff --git a/Lesson 4/caesar-task/after/package.json b/Lesson 4/caesar-task/after/package.json index 0406a57..e1532ee 100644 --- a/Lesson 4/caesar-task/after/package.json +++ b/Lesson 4/caesar-task/after/package.json @@ -16,6 +16,8 @@ "license": "ISC", "dependencies": { "@_koi/web3.js": "^0.0.6", + "@_koii/namespace-wrapper": "^1.0.1", + "@_koii/storage-task-sdk": "^1.2.4", "axios": "^0.27.2", "body-parser": "^1.20.2", "cross-spawn": "^7.0.3", diff --git a/Lesson 4/caesar-task/after/task/audit.js b/Lesson 4/caesar-task/after/task/audit.js index e0b3f52..f1795fc 100644 --- a/Lesson 4/caesar-task/after/task/audit.js +++ b/Lesson 4/caesar-task/after/task/audit.js @@ -1,4 +1,4 @@ -const { namespaceWrapper } = require('../_koiiNode/koiiNode'); +const { namespaceWrapper } = require('@_koii/namespace-wrapper'); const CaesarCipher = require('../caesar-cipher/caesar-cipher'); class Audit { diff --git a/Lesson 4/caesar-task/after/task/distribution.js b/Lesson 4/caesar-task/after/task/distribution.js index c86be30..2192646 100644 --- a/Lesson 4/caesar-task/after/task/distribution.js +++ b/Lesson 4/caesar-task/after/task/distribution.js @@ -1,4 +1,4 @@ -const { namespaceWrapper } = require('../_koiiNode/koiiNode'); +const { namespaceWrapper } = require('@_koii/namespace-wrapper'); class Distribution { /** diff --git a/Lesson 4/caesar-task/after/task/submission.js b/Lesson 4/caesar-task/after/task/submission.js index c740e78..7710561 100644 --- a/Lesson 4/caesar-task/after/task/submission.js +++ b/Lesson 4/caesar-task/after/task/submission.js @@ -1,4 +1,4 @@ -const { namespaceWrapper } = require('../_koiiNode/koiiNode'); +const { namespaceWrapper } = require('@_koii/namespace-wrapper'); const CaesarCipher = require('../caesar-cipher/caesar-cipher'); class Submission { diff --git a/Lesson 4/caesar-task/after/tests/main.test.js b/Lesson 4/caesar-task/after/tests/main.test.js index 1eb8a52..05f5352 100644 --- a/Lesson 4/caesar-task/after/tests/main.test.js +++ b/Lesson 4/caesar-task/after/tests/main.test.js @@ -1,5 +1,5 @@ const { coreLogic } = require('../coreLogic'); -const { namespaceWrapper, _server } = require('../_koiiNode/koiiNode'); +const { namespaceWrapper, _server } = require('@_koii/namespace-wrapper'); const Joi = require('joi'); const axios = require('axios'); beforeAll(async () => { diff --git a/Lesson 4/caesar-task/before/_koiiNode/koiiNode.js b/Lesson 4/caesar-task/before/_koiiNode/koiiNode.js deleted file mode 100644 index 6f17a8d..0000000 --- a/Lesson 4/caesar-task/before/_koiiNode/koiiNode.js +++ /dev/null @@ -1,1155 +0,0 @@ -const { default: axios } = require('axios'); -const { createHash } = require('crypto'); - -const { Connection, PublicKey, Keypair } = require('@_koi/web3.js'); - -const Datastore = require('nedb-promises'); -const fsPromises = require('fs/promises'); -const bs58 = require('bs58'); -const nacl = require('tweetnacl'); - -/****************************************** init.js ***********************************/ - -const express = require('express'); -// Only used for testing purposes, in production the env will be injected by tasknode -require('dotenv').config(); -const bodyParser = require('body-parser'); -/** - * This will be the name of the current task as coming from the task node running this task. - */ -const TASK_NAME = process.argv[2] || 'Local'; -/** - * This will be the id of the current task as coming from the task node running this task. - */ -const TASK_ID = process.argv[3]; -/** - * This will be the PORT on which the this task is expected to run the express server coming from the task node running this task. - * As all communication via the task node and this task will be done on this port. - */ -const EXPRESS_PORT = process.argv[4] || 10000; - -const LogLevel = { - Log: 'log', - Warn: 'warn', - Error: 'error', -}; - -// Not used anymore -// const NODE_MODE = process.argv[5]; - -/** - * This will be the main account public key in string format of the task node running this task. - */ -const MAIN_ACCOUNT_PUBKEY = process.argv[6]; -/** - * This will be the secret used by the task to authenticate with task node running this task. - */ -const SECRET_KEY = process.argv[7]; -/** - * This will be K2 url being used by the task node, possible values are 'https://k2-testnet.koii.live' | 'https://k2-devnet.koii.live' | 'http://localhost:8899' - */ -const K2_NODE_URL = process.argv[8] || 'https://k2-testnet.koii.live'; -/** - * This will be public task node endpoint (Or local if it doesn't have any) of the task node running this task. - */ -const SERVICE_URL = process.argv[9]; -/** - * This will be stake of the task node running this task, can be double checked with the task state and staking public key. - */ -const STAKE = Number(process.argv[10]); -/** - * This will be the port used by task node as the express server port, so it can be used by the task for the communication with the task node - */ -const TASK_NODE_PORT = Number(process.argv[11]); - -const app = express(); - -console.log('SETTING UP EXPRESS'); - -app.use(bodyParser.urlencoded({ extended: false })); - -app.use(bodyParser.json()); - -app.use((req, res, next) => { - res.setHeader('Access-Control-Allow-Origin', '*'); - res.setHeader( - 'Access-Control-Allow-Methods', - 'GET, POST, PUT, PATCH, DELETE', - ); - res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); - res.setHeader('Access-Control-Allow-Credentials', false); - if (req.method === 'OPTIONS') - // if is preflight(OPTIONS) then response status 204(NO CONTENT) - return res.send(204); - next(); -}); - -app.get('/', (req, res) => { - res.send('Hello World!'); -}); - -const _server = app.listen(EXPRESS_PORT, () => { - console.log(`${TASK_NAME} listening on port ${EXPRESS_PORT}`); -}); - -/****************************************** NamespaceWrapper.js ***********************************/ - -const taskNodeAdministered = !!TASK_ID; -const BASE_ROOT_URL = `http://localhost:${TASK_NODE_PORT}/namespace-wrapper`; -let connection; - -class NamespaceWrapper { - #db; - #testingMainSystemAccount; - #testingStakingSystemAccount; - #testingTaskState; - #testingDistributionList; - - constructor() { - if (taskNodeAdministered) { - this.initializeDB(); - } else { - this.#db = Datastore.create('./localKOIIDB.db'); - this.defaultTaskSetup(); - } - } - - async initializeDB() { - if (this.#db) return; - try { - if (taskNodeAdministered) { - const path = await this.getTaskLevelDBPath(); - this.#db = Datastore.create(path); - } else { - this.#db = Datastore.create('./localKOIIDB.db'); - } - } catch (e) { - this.#db = Datastore.create(`../namespace/${TASK_ID}/KOIILevelDB.db`); - } - } - - async getDb() { - if (this.#db) return this.#db; - await this.initializeDB(); - return this.#db; - } - /** - * Namespace wrapper of storeGetAsync - * @param {string} key // Path to get - */ - async storeGet(key) { - try { - await this.initializeDB(); - const resp = await this.#db.findOne({ key: key }); - if (resp) { - return resp[key]; - } else { - return null; - } - } catch (e) { - console.error(e); - return null; - } - } - /** - * Namespace wrapper over storeSetAsync - * @param {string} key Path to set - * @param {*} value Data to set - */ - async storeSet(key, value) { - try { - await this.initializeDB(); - await this.#db.update( - { key: key }, - { [key]: value, key }, - { upsert: true }, - ); - } catch (e) { - console.error(e); - return undefined; - } - } - - /** - * Namespace wrapper over fsPromises methods - * @param {*} method The fsPromise method to call - * @param {*} path Path for the express call - * @param {...any} args Remaining parameters for the FS call - */ - async fs(method, path, ...args) { - if (taskNodeAdministered) { - return await genericHandler('fs', method, path, ...args); - } else { - return fsPromises[method](`${path}`, ...args); - } - } - async fsStaking(method, path, ...args) { - if (taskNodeAdministered) { - return await genericHandler('fsStaking', method, path, ...args); - } else { - return fsPromises[method](`${path}`, ...args); - } - } - - async fsWriteStream(imagepath) { - if (taskNodeAdministered) { - return await genericHandler('fsWriteStream', imagepath); - } else { - const writer = createWriteStream(imagepath); - return writer; - } - } - async fsReadStream(imagepath) { - if (taskNodeAdministered) { - return await genericHandler('fsReadStream', imagepath); - } else { - const file = readFileSync(imagepath); - return file; - } - } - - /** - * Namespace wrapper for getting current slots - */ - async getSlot() { - if (taskNodeAdministered) { - return await genericHandler('getCurrentSlot'); - } else { - return 100; - } - } - - async payloadSigning(body) { - if (taskNodeAdministered) { - return await genericHandler('signData', body); - } else { - const msg = new TextEncoder().encode(JSON.stringify(body)); - const signedMessage = nacl.sign( - msg, - this.#testingMainSystemAccount.secretKey, - ); - return await this.bs58Encode(signedMessage); - } - } - - async bs58Encode(data) { - return bs58.encode( - Buffer.from(data.buffer, data.byteOffset, data.byteLength), - ); - } - - async bs58Decode(data) { - return new Uint8Array(bs58.decode(data)); - } - - decodePayload(payload) { - return new TextDecoder().decode(payload); - } - - /** - * Namespace wrapper of storeGetAsync - * @param {string} signedMessage r // Path to get - */ - - async verifySignature(signedMessage, pubKey) { - if (taskNodeAdministered) { - return await genericHandler('verifySignedData', signedMessage, pubKey); - } else { - try { - const payload = nacl.sign.open( - await this.bs58Decode(signedMessage), - await this.bs58Decode(pubKey), - ); - if (!payload) return { error: 'Invalid signature' }; - return { data: this.decodePayload(payload) }; - } catch (e) { - console.error(e); - return { error: `Verification failed: ${e}` }; - } - } - } - - // async submissionOnChain(submitterKeypair, submission) { - // return await genericHandler( - // 'submissionOnChain', - // submitterKeypair, - // submission, - // ); - // } - - async stakeOnChain( - taskStateInfoPublicKey, - stakingAccKeypair, - stakePotAccount, - stakeAmount, - ) { - if (taskNodeAdministered) { - return await genericHandler( - 'stakeOnChain', - taskStateInfoPublicKey, - stakingAccKeypair, - stakePotAccount, - stakeAmount, - ); - } else { - this.#testingTaskState.stake_list[ - this.#testingStakingSystemAccount.publicKey.toBase58() - ] = stakeAmount; - } - } - async claimReward(stakePotAccount, beneficiaryAccount, claimerKeypair) { - if (!taskNodeAdministered) { - console.log('Cannot call sendTransaction in testing mode'); - return; - } - return await genericHandler( - 'claimReward', - stakePotAccount, - beneficiaryAccount, - claimerKeypair, - ); - } - async sendTransaction(serviceNodeAccount, beneficiaryAccount, amount) { - if (!taskNodeAdministered) { - console.log('Cannot call sendTransaction in testing mode'); - return; - } - return await genericHandler( - 'sendTransaction', - serviceNodeAccount, - beneficiaryAccount, - amount, - ); - } - - async getSubmitterAccount() { - if (taskNodeAdministered) { - const submitterAccountResp = await genericHandler('getSubmitterAccount'); - return Keypair.fromSecretKey( - Uint8Array.from(Object.values(submitterAccountResp._keypair.secretKey)), - ); - } else { - return this.#testingStakingSystemAccount; - } - } - - /** - * sendAndConfirmTransaction wrapper that injects mainSystemWallet as the first signer for paying the tx fees - * @param {connection} method // Receive method ["get", "post", "put", "delete"] - * @param {transaction} path // Endpoint path appended to namespace - * @param {Function} callback // Callback function on traffic receive - */ - async sendAndConfirmTransactionWrapper(transaction, signers) { - if (!taskNodeAdministered) { - console.log('Cannot call sendTransaction in testing mode'); - return; - } - const blockhash = (await connection.getRecentBlockhash('finalized')) - .blockhash; - transaction.recentBlockhash = blockhash; - transaction.feePayer = new PublicKey(MAIN_ACCOUNT_PUBKEY); - return await genericHandler( - 'sendAndConfirmTransactionWrapper', - transaction.serialize({ - requireAllSignatures: false, - verifySignatures: false, - }), - signers, - ); - } - - // async signArweave(transaction) { - // let tx = await genericHandler('signArweave', transaction.toJSON()); - // return arweave.transactions.fromRaw(tx); - // } - // async signEth(transaction) { - // return await genericHandler('signEth', transaction); - // } - async getTaskState(options) { - if (taskNodeAdministered) { - const response = await genericHandler('getTaskState', options); - if (response.error) { - console.log('Error in getting task state', response.error); - return null; - } - return response; - } else { - return this.#testingTaskState; - } - } - - async logMessage(level, message, action) { - switch (level) { - case LogLevel.Log: - console.log(message, action); - break; - case LogLevel.Warn: - console.warn(message, action); - break; - case LogLevel.Error: - console.error(message, action); - break; - default: - console.log( - `Invalid log level: ${level}. The log levels can be log, warn or error`, - ); - return false; - } - return true; - } - - /** - * This logger function is used to log the task erros , warnings and logs on desktop-node - * @param {level} enum // Receive method ["Log", "Warn", "Error"] - enum LogLevel { - Log = 'log', - Warn = 'warn', - Error = 'error', - } - * @param {message} string // log, error or warning message - * @returns {boolean} // true if the message is logged successfully otherwise false - */ - - async logger(level, message, action) { - if (taskNodeAdministered) { - return await genericHandler('logger', level, message, action); - } else { - return await this.logMessage(level, message, action); - } - } - - async auditSubmission(candidatePubkey, isValid, voterKeypair, round) { - if (taskNodeAdministered) { - return await genericHandler( - 'auditSubmission', - candidatePubkey, - isValid, - round, - ); - } else { - if ( - this.#testingTaskState.submissions_audit_trigger[round] && - this.#testingTaskState.submissions_audit_trigger[round][candidatePubkey] - ) { - this.#testingTaskState.submissions_audit_trigger[round][ - candidatePubkey - ].votes.push({ - is_valid: isValid, - voter: voterKeypair.pubKey.toBase58(), - slot: 100, - }); - } else { - this.#testingTaskState.submissions_audit_trigger[round] = { - [candidatePubkey]: { - trigger_by: this.#testingStakingSystemAccount.publicKey.toBase58(), - slot: 100, - votes: [], - }, - }; - } - } - } - - async distributionListAuditSubmission( - candidatePubkey, - isValid, - voterKeypair, - round, - ) { - if (taskNodeAdministered) { - return await genericHandler( - 'distributionListAuditSubmission', - candidatePubkey, - isValid, - round, - ); - } else { - if ( - this.#testingTaskState.distributions_audit_trigger[round] && - this.#testingTaskState.distributions_audit_trigger[round][ - candidatePubkey - ] - ) { - this.#testingTaskState.distributions_audit_trigger[round][ - candidatePubkey - ].votes.push({ - is_valid: isValid, - voter: voterKeypair.pubKey.toBase58(), - slot: 100, - }); - } else { - this.#testingTaskState.distributions_audit_trigger[round] = { - [candidatePubkey]: { - trigger_by: this.#testingStakingSystemAccount.publicKey.toBase58(), - slot: 100, - votes: [], - }, - }; - } - } - } - - async getRound() { - if (taskNodeAdministered) { - return await genericHandler('getRound'); - } else { - return 1; - } - } - - async payoutTrigger(round) { - if (taskNodeAdministered) { - return await genericHandler('payloadTrigger', round); - } else { - console.log( - 'Payout Trigger only handles possitive flows (Without audits)', - ); - let round = 1; - const submissionValAcc = - this.#testingDistributionList[round][ - this.#testingStakingSystemAccount.toBase58() - ].submission_value; - this.#testingTaskState.available_balances = - this.#testingDistributionList[round][submissionValAcc]; - } - } - - async uploadDistributionList(distributionList, round) { - if (taskNodeAdministered) { - return await genericHandler( - 'uploadDistributionList', - distributionList, - round, - ); - } else { - if (!this.#testingDistributionList[round]) - this.#testingDistributionList[round] = {}; - - this.#testingDistributionList[round][ - this.#testingStakingSystemAccount.publicKey.toBase58() - ] = Buffer.from(JSON.stringify(distributionList)); - return true; - } - } - - async distributionListSubmissionOnChain(round) { - if (taskNodeAdministered) { - return await genericHandler('distributionListSubmissionOnChain', round); - } else { - if (!this.#testingTaskState.distribution_rewards_submission[round]) - this.#testingTaskState.distribution_rewards_submission[round] = {}; - - this.#testingTaskState.distribution_rewards_submission[round][ - this.#testingStakingSystemAccount.publicKey.toBase58() - ] = { - submission_value: - this.#testingStakingSystemAccount.publicKey.toBase58(), - slot: 200, - round: 1, - }; - } - } - - async checkSubmissionAndUpdateRound(submissionValue = 'default', round) { - if (taskNodeAdministered) { - return await genericHandler( - 'checkSubmissionAndUpdateRound', - submissionValue, - round, - ); - } else { - if (!this.#testingTaskState.submissions[round]) - this.#testingTaskState.submissions[round] = {}; - this.#testingTaskState.submissions[round][ - this.#testingStakingSystemAccount.publicKey.toBase58() - ] = { - submission_value: submissionValue, - slot: 100, - round: 1, - }; - } - } - async getProgramAccounts() { - if (taskNodeAdministered) { - return await genericHandler('getProgramAccounts'); - } else { - console.log('Cannot call getProgramAccounts in testing mode'); - } - } - async defaultTaskSetup() { - if (taskNodeAdministered) { - return await genericHandler('defaultTaskSetup'); - } else { - if (this.#testingTaskState) return; - this.#testingMainSystemAccount = new Keypair(); - this.#testingStakingSystemAccount = new Keypair(); - this.#testingDistributionList = {}; - this.#testingTaskState = { - task_name: 'DummyTestState', - task_description: 'Dummy Task state for testing flow', - submissions: {}, - submissions_audit_trigger: {}, - total_bounty_amount: 10000000000, - bounty_amount_per_round: 1000000000, - total_stake_amount: 50000000000, - minimum_stake_amount: 5000000000, - available_balances: {}, - stake_list: {}, - round_time: 600, - starting_slot: 0, - audit_window: 200, - submission_window: 200, - distribution_rewards_submission: {}, - distributions_audit_trigger: {}, - }; - } - } - async getRpcUrl() { - if (taskNodeAdministered) { - return await genericHandler('getRpcUrl'); - } else { - console.log('Cannot call getNodes in testing mode'); - } - } - async getNodes(url) { - if (taskNodeAdministered) { - return await genericHandler('getNodes', url); - } else { - console.log('Cannot call getNodes in testing mode'); - } - } - - async getDistributionList(publicKey, round) { - if (taskNodeAdministered) { - const response = await genericHandler( - 'getDistributionList', - publicKey, - round, - ); - if (response.error) { - return null; - } - return response; - } else { - const submissionValAcc = - this.#testingTaskState.distribution_rewards_submission[round][ - this.#testingStakingSystemAccount.publicKey.toBase58() - ].submission_value; - return this.#testingDistributionList[round][submissionValAcc]; - } - } - - async getTaskSubmissionInfo(round) { - if (taskNodeAdministered) { - const taskSubmissionInfo = await genericHandler( - 'getTaskSubmissionInfo', - round, - ); - if (taskSubmissionInfo.error) { - return null; - } - return taskSubmissionInfo; - } else { - return this.#testingTaskState.submissions[round]; - } - } - - async validateAndVoteOnNodes(validate, round) { - console.log('******/ IN VOTING /******'); - let taskAccountDataJSON = null; - try { - taskAccountDataJSON = await this.getTaskSubmissionInfo(round); - } catch (error) { - console.error('Error in getting submissions for the round', error); - } - if (taskAccountDataJSON == null) { - console.log('No submissions found for the round', round); - return; - } - console.log( - `Fetching the submissions of round ${round}`, - taskAccountDataJSON.submissions[round], - ); - const submissions = taskAccountDataJSON.submissions[round]; - if (submissions == null) { - console.log(`No submisssions found in round ${round}`); - return `No submisssions found in round ${round}`; - } else { - const keys = Object.keys(submissions); - const values = Object.values(submissions); - const size = values.length; - console.log('Submissions from last round: ', keys, values, size); - let isValid; - const submitterAccountKeyPair = await this.getSubmitterAccount(); - const submitterPubkey = submitterAccountKeyPair.publicKey.toBase58(); - for (let i = 0; i < size; i++) { - let candidatePublicKey = keys[i]; - console.log('FOR CANDIDATE KEY', candidatePublicKey); - let candidateKeyPairPublicKey = new PublicKey(keys[i]); - if (candidatePublicKey == submitterPubkey) { - console.log('YOU CANNOT VOTE ON YOUR OWN SUBMISSIONS'); - } else { - try { - console.log( - 'SUBMISSION VALUE TO CHECK', - values[i].submission_value, - ); - isValid = await validate(values[i].submission_value, round); - console.log(`Voting ${isValid} to ${candidatePublicKey}`); - - if (isValid) { - // check for the submissions_audit_trigger , if it exists then vote true on that otherwise do nothing - const submissions_audit_trigger = - taskAccountDataJSON.submissions_audit_trigger[round]; - console.log('SUBMIT AUDIT TRIGGER', submissions_audit_trigger); - // console.log( - // "CANDIDATE PUBKEY CHECK IN AUDIT TRIGGER", - // submissions_audit_trigger[candidatePublicKey] - // ); - if ( - submissions_audit_trigger && - submissions_audit_trigger[candidatePublicKey] - ) { - console.log('VOTING TRUE ON AUDIT'); - const response = await this.auditSubmission( - candidateKeyPairPublicKey, - isValid, - submitterAccountKeyPair, - round, - ); - console.log('RESPONSE FROM AUDIT FUNCTION', response); - } - } else if (isValid == false) { - // Call auditSubmission function and isValid is passed as false - console.log('RAISING AUDIT / VOTING FALSE'); - const response = await this.auditSubmission( - candidateKeyPairPublicKey, - isValid, - submitterAccountKeyPair, - round, - ); - console.log('RESPONSE FROM AUDIT FUNCTION', response); - } - } catch (err) { - console.log('ERROR IN ELSE CONDITION', err); - } - } - } - } - } - - async getTaskDistributionInfo(round) { - if (taskNodeAdministered) { - const taskDistributionInfo = await genericHandler( - 'getTaskDistributionInfo', - round, - ); - if (taskDistributionInfo.error) { - return null; - } - return taskDistributionInfo; - } else { - return this.#testingTaskState.distribution_rewards_submission[round]; - } - } - - async validateAndVoteOnDistributionList(validateDistribution, round) { - // await this.checkVoteStatus(); - console.log('******/ IN VOTING OF DISTRIBUTION LIST /******'); - let taskAccountDataJSON = null; - try { - taskAccountDataJSON = await this.getTaskDistributionInfo(round); - } catch (error) { - console.error('Error in getting distributions for the round', error); - } - if (taskAccountDataJSON == null) { - console.log('No distribution submissions found for the round', round); - return; - } - console.log( - `Fetching the Distribution submissions of round ${round}`, - taskAccountDataJSON.distribution_rewards_submission[round], - ); - const submissions = - taskAccountDataJSON?.distribution_rewards_submission[round]; - if ( - submissions == null || - submissions == undefined || - submissions.length == 0 - ) { - console.log(`No submisssions found in round ${round}`); - return `No submisssions found in round ${round}`; - } else { - const keys = Object.keys(submissions); - const values = Object.values(submissions); - const size = values.length; - console.log( - 'Distribution Submissions from last round: ', - keys, - values, - size, - ); - let isValid; - const submitterAccountKeyPair = await this.getSubmitterAccount(); - const submitterPubkey = submitterAccountKeyPair.publicKey.toBase58(); - - for (let i = 0; i < size; i++) { - let candidatePublicKey = keys[i]; - console.log('FOR CANDIDATE KEY', candidatePublicKey); - let candidateKeyPairPublicKey = new PublicKey(keys[i]); - if (candidatePublicKey == submitterPubkey) { - console.log('YOU CANNOT VOTE ON YOUR OWN DISTRIBUTION SUBMISSIONS'); - } else { - try { - console.log( - 'DISTRIBUTION SUBMISSION VALUE TO CHECK', - values[i].submission_value, - ); - isValid = await validateDistribution( - values[i].submission_value, - round, - ); - console.log(`Voting ${isValid} to ${candidatePublicKey}`); - - if (isValid) { - // check for the submissions_audit_trigger , if it exists then vote true on that otherwise do nothing - const distributions_audit_trigger = - taskAccountDataJSON.distributions_audit_trigger[round]; - console.log( - 'SUBMIT DISTRIBUTION AUDIT TRIGGER', - distributions_audit_trigger, - ); - // console.log( - // "CANDIDATE PUBKEY CHECK IN AUDIT TRIGGER", - // distributions_audit_trigger[candidatePublicKey] - // ); - if ( - distributions_audit_trigger && - distributions_audit_trigger[candidatePublicKey] - ) { - console.log('VOTING TRUE ON DISTRIBUTION AUDIT'); - const response = await this.distributionListAuditSubmission( - candidateKeyPairPublicKey, - isValid, - submitterAccountKeyPair, - round, - ); - console.log( - 'RESPONSE FROM DISTRIBUTION AUDIT FUNCTION', - response, - ); - } - } else if (isValid == false) { - // Call auditSubmission function and isValid is passed as false - console.log('RAISING AUDIT / VOTING FALSE ON DISTRIBUTION'); - const response = await this.distributionListAuditSubmission( - candidateKeyPairPublicKey, - isValid, - submitterAccountKeyPair, - round, - ); - console.log( - 'RESPONSE FROM DISTRIBUTION AUDIT FUNCTION', - response, - ); - } - } catch (err) { - console.log('ERROR IN ELSE CONDITION FOR DISTRIBUTION', err); - } - } - } - } - } - async getTaskLevelDBPath() { - if (taskNodeAdministered) { - return await genericHandler('getTaskLevelDBPath'); - } else { - return './KOIIDB'; - } - } - async getBasePath() { - if (taskNodeAdministered) { - const basePath = (await namespaceWrapper.getTaskLevelDBPath()).replace( - '/KOIIDB', - '', - ); - return basePath; - } else { - return './'; - } - } - - async getAverageSlotTime() { - if (taskNodeAdministered) { - try { - return await genericHandler('getAverageSlotTime'); - } catch (error) { - console.error('Error getting average slot time', error); - return 400; - } - } else { - return 400; - } - } - - async nodeSelectionDistributionList(round, isPreviousFailed) { - let taskAccountDataJSON = null; - try { - taskAccountDataJSON = await this.getTaskSubmissionInfo(round); - } catch (error) { - console.error('Task submission not found', error); - return; - } - - if (taskAccountDataJSON == null) { - console.error('Task state not found'); - return; - } - console.log('EXPECTED ROUND', round); - - const submissions = taskAccountDataJSON.submissions[round]; - if (submissions == null) { - console.log('No submisssions found in N-1 round'); - return 'No submisssions found in N-1 round'; - } else { - // getting last 3 submissions for the rounds - let keys; - const latestRounds = [round, round - 1, round - 2].filter(r => r >= 0); - - const promises = latestRounds.map(async r => { - if (r == round) { - return new Set(Object.keys(submissions)); - } else { - let roundSubmissions = null; - try { - roundSubmissions = await this.getTaskSubmissionInfo(r); - if (roundSubmissions && roundSubmissions.submissions[r]) { - return new Set(Object.keys(roundSubmissions.submissions[r])); - } - } catch (error) { - console.error('Error in getting submissions for the round', error); - } - return new Set(); - } - }); - - const keySets = await Promise.all(promises); - - // Find the keys present in all the rounds - keys = - keySets.length > 0 - ? [...keySets[0]].filter(key => keySets.every(set => set.has(key))) - : []; - if (keys.length == 0) { - console.log('No common keys found in last 3 rounds'); - keys = Object.keys(submissions); - } - console.log('KEYS', keys.length); - const values = keys.map(key => submissions[key]); - - let size = keys.length; - console.log('Submissions from N-2 round: ', size); - - // Check the keys i.e if the submitter shall be excluded or not - try { - const distributionData = await this.getTaskDistributionInfo(round); - const audit_record = distributionData?.distributions_audit_record; - if (audit_record && audit_record[round] == 'PayoutFailed') { - console.log('ROUND DATA', audit_record[round]); - console.log( - 'SUBMITTER LIST', - distributionData.distribution_rewards_submission[round], - ); - const submitterList = - distributionData.distribution_rewards_submission[round]; - const submitterKeys = Object.keys(submitterList); - console.log('SUBMITTER KEYS', submitterKeys); - const submitterSize = submitterKeys.length; - console.log('SUBMITTER SIZE', submitterSize); - - for (let j = 0; j < submitterSize; j++) { - console.log('SUBMITTER KEY CANDIDATE', submitterKeys[j]); - const id = keys.indexOf(submitterKeys[j]); - console.log('ID', id); - if (id != -1) { - keys.splice(id, 1); - values.splice(id, 1); - size--; - } - } - - console.log('KEYS FOR HASH CALC', keys.length); - } - } catch (error) { - console.log('Error in getting distribution data', error); - } - - // calculating the digest - - const ValuesString = JSON.stringify(values); - - const hashDigest = createHash('sha256') - .update(ValuesString) - .digest('hex'); - - console.log('HASH DIGEST', hashDigest); - - // function to calculate the score - const calculateScore = (str = '') => { - return str.split('').reduce((acc, val) => { - return acc + val.charCodeAt(0); - }, 0); - }; - - // function to compare the ASCII values - - const compareASCII = (str1, str2) => { - const firstScore = calculateScore(str1); - const secondScore = calculateScore(str2); - return Math.abs(firstScore - secondScore); - }; - - // loop through the keys and select the one with higest score - - const selectedNode = { - score: 0, - pubkey: '', - }; - let score = 0; - if (isPreviousFailed) { - let leastScore = -Infinity; - let secondLeastScore = -Infinity; - for (let i = 0; i < size; i++) { - const candidateSubmissionJson = {}; - candidateSubmissionJson[keys[i]] = values[i]; - const candidateSubmissionString = JSON.stringify( - candidateSubmissionJson, - ); - const candidateSubmissionHash = createHash('sha256') - .update(candidateSubmissionString) - .digest('hex'); - const candidateScore = compareASCII( - hashDigest, - candidateSubmissionHash, - ); - if (candidateScore > leastScore) { - secondLeastScore = leastScore; - leastScore = candidateScore; - } else if (candidateScore > secondLeastScore) { - secondLeastScore = candidateScore; - selectedNode.score = candidateScore; - selectedNode.pubkey = keys[i]; - } - } - } else { - for (let i = 0; i < size; i++) { - const candidateSubmissionJson = {}; - candidateSubmissionJson[keys[i]] = values[i]; - const candidateSubmissionString = JSON.stringify( - candidateSubmissionJson, - ); - const candidateSubmissionHash = createHash('sha256') - .update(candidateSubmissionString) - .digest('hex'); - const candidateScore = compareASCII( - hashDigest, - candidateSubmissionHash, - ); - // console.log('CANDIDATE SCORE', candidateScore); - if (candidateScore > score) { - score = candidateScore; - selectedNode.score = candidateScore; - selectedNode.pubkey = keys[i]; - } - } - } - - console.log('SELECTED NODE OBJECT', selectedNode); - return selectedNode.pubkey; - } - } - - async selectAndGenerateDistributionList( - submitDistributionList, - round, - isPreviousRoundFailed, - ) { - console.log('SelectAndGenerateDistributionList called'); - const selectedNode = await this.nodeSelectionDistributionList( - round, - isPreviousRoundFailed, - ); - console.log('Selected Node', selectedNode); - const submitPubKey = await this.getSubmitterAccount(); - if ( - selectedNode == undefined || - selectedNode == '' || - submitPubKey == undefined - ) - return; - if (selectedNode == submitPubKey?.publicKey.toBase58()) { - await submitDistributionList(round); - const taskState = await this.getTaskState({}); - if (taskState == null) { - console.error('Task state not found'); - return; - } - const avgSlotTime = await this.getAverageSlotTime(); - if (avgSlotTime == null) { - console.error('Avg slot time not found'); - return; - } - setTimeout(async () => { - await this.payoutTrigger(round); - }, (taskState.audit_window + taskState.submission_window) * avgSlotTime); - } - } - - getMainAccountPubkey() { - if (taskNodeAdministered) { - return MAIN_ACCOUNT_PUBKEY; - } else { - return this.#testingMainSystemAccount.publicKey.toBase58(); - } - } -} - -async function genericHandler(...args) { - try { - let response = await axios.post(BASE_ROOT_URL, { - args, - taskId: TASK_ID, - secret: SECRET_KEY, - }); - if (response.status == 200) return response.data.response; - else { - console.error(response.status, response.data); - return null; - } - } catch (err) { - console.error(`Error in genericHandler: "${args[0]}"`, err.message); - console.error(err?.response?.data); - return { error: err }; - } -} - -const namespaceWrapper = new NamespaceWrapper(); -if (taskNodeAdministered) { - namespaceWrapper.getRpcUrl().then(rpcUrl => { - console.log(rpcUrl, 'RPC URL'); - connection = new Connection(rpcUrl, 'confirmed'); - }); -} -module.exports = { - namespaceWrapper, - taskNodeAdministered, // Boolean flag indicating that the task is being ran in active mode (Task node supervised), or development (testing) mode - app, // The initialized express app to be used to register endpoints - TASK_ID, // This will be the PORT on which the this task is expected to run the express server coming from the task node running this task. As all communication via the task node and this task will be done on this port. - MAIN_ACCOUNT_PUBKEY, // This will be the secret used to authenticate with task node running this task. - SECRET_KEY, // This will be the secret used by the task to authenticate with task node running this task. - K2_NODE_URL, // This will be K2 url being used by the task node, possible values are 'https://k2-testnet.koii.live' | 'https://k2-devnet.koii.live' | 'http://localhost:8899' - SERVICE_URL, // This will be public task node endpoint (Or local if it doesn't have any) of the task node running this task. - STAKE, // This will be stake of the task node running this task, can be double checked with the task state and staking public key. - TASK_NODE_PORT, // This will be the port used by task node as the express server port, so it can be used by the task for the communication with the task node - _server, // Express server object -}; diff --git a/Lesson 4/caesar-task/before/coreLogic.js b/Lesson 4/caesar-task/before/coreLogic.js index 7b09d08..b210551 100644 --- a/Lesson 4/caesar-task/before/coreLogic.js +++ b/Lesson 4/caesar-task/before/coreLogic.js @@ -1,4 +1,4 @@ -const { namespaceWrapper } = require('./_koiiNode/koiiNode'); +const { namespaceWrapper } = require('@_koii/namespace-wrapper'); const task = require('./task'); class CoreLogic { diff --git a/Lesson 4/caesar-task/before/index.js b/Lesson 4/caesar-task/before/index.js index 75723ec..17ac502 100644 --- a/Lesson 4/caesar-task/before/index.js +++ b/Lesson 4/caesar-task/before/index.js @@ -3,12 +3,12 @@ const { namespaceWrapper, taskNodeAdministered, app, -} = require('./_koiiNode/koiiNode'); +} = require('@_koii/namespace-wrapper'); if (app) { // Write your Express Endpoints here. // Ex. app.post('/accept-cid', async (req, res) => {}) - + // Sample API that return your task state app.get('/taskState', async (req, res) => { const state = await namespaceWrapper.getTaskState(); @@ -54,13 +54,13 @@ async function setup() { /* GUIDE TO CALLS K2 FUNCTIONS MANUALLY - If you wish to do the development by avoiding the timers then you can do the intended calls to K2 - directly using these function calls. + If you wish to do the development by avoiding the timers then you can do the intended calls to K2 + directly using these function calls. To disable timers please set the TIMERS flag in task-node ENV to disable NOTE : K2 will still have the windows to accept the submission value, audit, so you are expected - to make calls in the intended slots of your round time. + to make calls in the intended slots of your round time. */ diff --git a/Lesson 4/caesar-task/before/package.json b/Lesson 4/caesar-task/before/package.json index cbc9f5d..cbbe08b 100644 --- a/Lesson 4/caesar-task/before/package.json +++ b/Lesson 4/caesar-task/before/package.json @@ -15,6 +15,8 @@ "license": "ISC", "dependencies": { "@_koi/web3.js": "^0.0.6", + "@_koii/namespace-wrapper": "^1.0.1", + "@_koii/storage-task-sdk": "^1.2.4", "axios": "^0.27.2", "body-parser": "^1.20.2", "cross-spawn": "^7.0.3", diff --git a/Lesson 4/caesar-task/before/task/audit.js b/Lesson 4/caesar-task/before/task/audit.js index 93001c4..67dff86 100644 --- a/Lesson 4/caesar-task/before/task/audit.js +++ b/Lesson 4/caesar-task/before/task/audit.js @@ -1,4 +1,4 @@ -const { namespaceWrapper } = require('../_koiiNode/koiiNode'); +const { namespaceWrapper } = require('@_koii/namespace-wrapper'); const CaesarCipher = require('../caesar-cipher/caesar-cipher'); class Audit { diff --git a/Lesson 4/caesar-task/before/task/distribution.js b/Lesson 4/caesar-task/before/task/distribution.js index 11fddc7..bf6cef4 100644 --- a/Lesson 4/caesar-task/before/task/distribution.js +++ b/Lesson 4/caesar-task/before/task/distribution.js @@ -1,4 +1,4 @@ -const { namespaceWrapper } = require('../_koiiNode/koiiNode'); +const { namespaceWrapper } = require('@_koii/namespace-wrapper'); class Distribution { /** diff --git a/Lesson 4/caesar-task/before/task/submission.js b/Lesson 4/caesar-task/before/task/submission.js index c740e78..7710561 100644 --- a/Lesson 4/caesar-task/before/task/submission.js +++ b/Lesson 4/caesar-task/before/task/submission.js @@ -1,4 +1,4 @@ -const { namespaceWrapper } = require('../_koiiNode/koiiNode'); +const { namespaceWrapper } = require('@_koii/namespace-wrapper'); const CaesarCipher = require('../caesar-cipher/caesar-cipher'); class Submission { diff --git a/Lesson 4/caesar-task/before/tests/main.test.js b/Lesson 4/caesar-task/before/tests/main.test.js index 1eb8a52..05f5352 100644 --- a/Lesson 4/caesar-task/before/tests/main.test.js +++ b/Lesson 4/caesar-task/before/tests/main.test.js @@ -1,5 +1,5 @@ const { coreLogic } = require('../coreLogic'); -const { namespaceWrapper, _server } = require('../_koiiNode/koiiNode'); +const { namespaceWrapper, _server } = require('@_koii/namespace-wrapper'); const Joi = require('joi'); const axios = require('axios'); beforeAll(async () => { diff --git a/Lesson 5/koiiNode.js b/Lesson 5/koiiNode.js deleted file mode 100644 index 852bdb6..0000000 --- a/Lesson 5/koiiNode.js +++ /dev/null @@ -1,1156 +0,0 @@ -const { default: axios } = require('axios'); -const { createHash } = require('crypto'); - -const { Connection, PublicKey, Keypair } = require('@_koi/web3.js'); - -const Datastore = require('nedb-promises'); -const fsPromises = require('fs/promises'); -const bs58 = require('bs58'); -const nacl = require('tweetnacl'); - -/****************************************** init.js ***********************************/ - -const express = require('express'); -// Only used for testing purposes, in production the env will be injected by tasknode -require('dotenv').config(); -const bodyParser = require('body-parser'); -/** - * This will be the name of the current task as coming from the task node running this task. - */ -const TASK_NAME = process.argv[2] || 'Local'; -/** - * This will be the id of the current task as coming from the task node running this task. - */ -const TASK_ID = process.argv[3]; -/** - * This will be the PORT on which the this task is expected to run the express server coming from the task node running this task. - * As all communication via the task node and this task will be done on this port. - */ -const EXPRESS_PORT = process.argv[4] || 10000; - -const LogLevel = { - Log: 'log', - Warn: 'warn', - Error: 'error', -}; - -// Not used anymore -// const NODE_MODE = process.argv[5]; - -/** - * This will be the main account public key in string format of the task node running this task. - */ -const MAIN_ACCOUNT_PUBKEY = process.argv[6]; -/** - * This will be the secret used by the task to authenticate with task node running this task. - */ -const SECRET_KEY = process.argv[7]; -/** - * This will be K2 url being used by the task node, possible values are 'https://k2-testnet.koii.live' | 'https://k2-devnet.koii.live' | 'http://localhost:8899' - */ -const K2_NODE_URL = process.argv[8] || 'https://k2-testnet.koii.live'; -/** - * This will be public task node endpoint (Or local if it doesn't have any) of the task node running this task. - */ -const SERVICE_URL = process.argv[9]; -/** - * This will be stake of the task node running this task, can be double checked with the task state and staking public key. - */ -const STAKE = Number(process.argv[10]); -/** - * This will be the port used by task node as the express server port, so it can be used by the task for the communication with the task node - */ -const TASK_NODE_PORT = Number(process.argv[11]); - -const app = express(); - -console.log('SETTING UP EXPRESS'); - -app.use(bodyParser.urlencoded({ extended: false })); - -app.use(bodyParser.json()); - -app.use((req, res, next) => { - res.setHeader('Access-Control-Allow-Origin', '*'); - res.setHeader( - 'Access-Control-Allow-Methods', - 'GET, POST, PUT, PATCH, DELETE', - ); - res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); - res.setHeader('Access-Control-Allow-Credentials', false); - if (req.method === 'OPTIONS') - // if is preflight(OPTIONS) then response status 204(NO CONTENT) - return res.send(204); - next(); -}); - -app.get('/', (req, res) => { - res.send('Hello World!'); -}); - -const _server = app.listen(EXPRESS_PORT, () => { - console.log(`${TASK_NAME} listening on port ${EXPRESS_PORT}`); -}); - -/****************************************** NamespaceWrapper.js ***********************************/ - -const taskNodeAdministered = !!TASK_ID; -const BASE_ROOT_URL = `http://localhost:${TASK_NODE_PORT}/namespace-wrapper`; -let connection; - -class NamespaceWrapper { - #db; - #testingMainSystemAccount; - #testingStakingSystemAccount; - #testingTaskState; - #testingDistributionList; - - constructor() { - if (taskNodeAdministered) { - this.initializeDB(); - } else { - this.#db = Datastore.create('./localKOIIDB.db'); - this.defaultTaskSetup(); - } - } - - async initializeDB() { - if (this.#db) return; - try { - if (taskNodeAdministered) { - const path = await this.getTaskLevelDBPath(); - this.#db = Datastore.create(path); - } else { - this.#db = Datastore.create('./localKOIIDB.db'); - } - } catch (e) { - this.#db = Datastore.create(`../namespace/${TASK_ID}/KOIILevelDB.db`); - } - } - - async getDb() { - if (this.#db) return this.#db; - await this.initializeDB(); - return this.#db; - } - /** - * Namespace wrapper of storeGetAsync - * @param {string} key // Path to get - */ - async storeGet(key) { - try { - await this.initializeDB(); - const resp = await this.#db.findOne({ key: key }); - if (resp) { - return resp[key]; - } else { - return null; - } - } catch (e) { - console.error(e); - return null; - } - } - /** - * Namespace wrapper over storeSetAsync - * @param {string} key Path to set - * @param {*} value Data to set - */ - async storeSet(key, value) { - try { - await this.initializeDB(); - await this.#db.update( - { key: key }, - { [key]: value, key }, - { upsert: true }, - ); - } catch (e) { - console.error(e); - return undefined; - } - } - - /** - * Namespace wrapper over fsPromises methods - * @param {*} method The fsPromise method to call - * @param {*} path Path for the express call - * @param {...any} args Remaining parameters for the FS call - */ - async fs(method, path, ...args) { - if (taskNodeAdministered) { - return await genericHandler('fs', method, path, ...args); - } else { - return fsPromises[method](`${path}`, ...args); - } - } - async fsStaking(method, path, ...args) { - if (taskNodeAdministered) { - return await genericHandler('fsStaking', method, path, ...args); - } else { - return fsPromises[method](`${path}`, ...args); - } - } - - async fsWriteStream(imagepath) { - if (taskNodeAdministered) { - return await genericHandler('fsWriteStream', imagepath); - } else { - const writer = createWriteStream(imagepath); - return writer; - } - } - async fsReadStream(imagepath) { - if (taskNodeAdministered) { - return await genericHandler('fsReadStream', imagepath); - } else { - const file = readFileSync(imagepath); - return file; - } - } - - /** - * Namespace wrapper for getting current slots - */ - async getSlot() { - if (taskNodeAdministered) { - return await genericHandler('getCurrentSlot'); - } else { - return 100; - } - } - - async payloadSigning(body) { - if (taskNodeAdministered) { - return await genericHandler('signData', body); - } else { - const msg = new TextEncoder().encode(JSON.stringify(body)); - const signedMessage = nacl.sign( - msg, - this.#testingMainSystemAccount.secretKey, - ); - return await this.bs58Encode(signedMessage); - } - } - - async bs58Encode(data) { - return bs58.encode( - Buffer.from(data.buffer, data.byteOffset, data.byteLength), - ); - } - - async bs58Decode(data) { - return new Uint8Array(bs58.decode(data)); - } - - decodePayload(payload) { - return new TextDecoder().decode(payload); - } - - /** - * Namespace wrapper of storeGetAsync - * @param {string} signedMessage r // Path to get - */ - - async verifySignature(signedMessage, pubKey) { - if (taskNodeAdministered) { - return await genericHandler('verifySignedData', signedMessage, pubKey); - } else { - try { - const payload = nacl.sign.open( - await this.bs58Decode(signedMessage), - await this.bs58Decode(pubKey), - ); - if (!payload) return { error: 'Invalid signature' }; - return { data: this.decodePayload(payload) }; - } catch (e) { - console.error(e); - return { error: `Verification failed: ${e}` }; - } - } - } - - // async submissionOnChain(submitterKeypair, submission) { - // return await genericHandler( - // 'submissionOnChain', - // submitterKeypair, - // submission, - // ); - // } - - async stakeOnChain( - taskStateInfoPublicKey, - stakingAccKeypair, - stakePotAccount, - stakeAmount, - ) { - if (taskNodeAdministered) { - return await genericHandler( - 'stakeOnChain', - taskStateInfoPublicKey, - stakingAccKeypair, - stakePotAccount, - stakeAmount, - ); - } else { - this.#testingTaskState.stake_list[ - this.#testingStakingSystemAccount.publicKey.toBase58() - ] = stakeAmount; - } - } - async claimReward(stakePotAccount, beneficiaryAccount, claimerKeypair) { - if (!taskNodeAdministered) { - console.log('Cannot call sendTransaction in testing mode'); - return; - } - return await genericHandler( - 'claimReward', - stakePotAccount, - beneficiaryAccount, - claimerKeypair, - ); - } - async sendTransaction(serviceNodeAccount, beneficiaryAccount, amount) { - if (!taskNodeAdministered) { - console.log('Cannot call sendTransaction in testing mode'); - return; - } - return await genericHandler( - 'sendTransaction', - serviceNodeAccount, - beneficiaryAccount, - amount, - ); - } - - async getSubmitterAccount() { - if (taskNodeAdministered) { - const submitterAccountResp = await genericHandler('getSubmitterAccount'); - return Keypair.fromSecretKey( - Uint8Array.from(Object.values(submitterAccountResp._keypair.secretKey)), - ); - } else { - return this.#testingStakingSystemAccount; - } - } - - /** - * sendAndConfirmTransaction wrapper that injects mainSystemWallet as the first signer for paying the tx fees - * @param {connection} method // Receive method ["get", "post", "put", "delete"] - * @param {transaction} path // Endpoint path appended to namespace - * @param {Function} callback // Callback function on traffic receive - */ - async sendAndConfirmTransactionWrapper(transaction, signers) { - if (!taskNodeAdministered) { - console.log('Cannot call sendTransaction in testing mode'); - return; - } - const blockhash = (await connection.getRecentBlockhash('finalized')) - .blockhash; - transaction.recentBlockhash = blockhash; - transaction.feePayer = new PublicKey(MAIN_ACCOUNT_PUBKEY); - return await genericHandler( - 'sendAndConfirmTransactionWrapper', - transaction.serialize({ - requireAllSignatures: false, - verifySignatures: false, - }), - signers, - ); - } - - // async signArweave(transaction) { - // let tx = await genericHandler('signArweave', transaction.toJSON()); - // return arweave.transactions.fromRaw(tx); - // } - // async signEth(transaction) { - // return await genericHandler('signEth', transaction); - // } - async getTaskState(options) { - if (taskNodeAdministered) { - const response = await genericHandler('getTaskState', options); - if (response.error) { - console.log('Error in getting task state', response.error); - return null; - } - return response; - } else { - return this.#testingTaskState; - } - } - - async logMessage(level, message, action) { - switch (level) { - case LogLevel.Log: - console.log(message, action); - break; - case LogLevel.Warn: - console.warn(message, action); - break; - case LogLevel.Error: - console.error(message, action); - break; - default: - console.log( - `Invalid log level: ${level}. The log levels can be log, warn or error`, - ); - return false; - } - return true; - } - - /** - * This logger function is used to log the task erros , warnings and logs on desktop-node - * @param {level} enum // Receive method ["Log", "Warn", "Error"] - enum LogLevel { - Log = 'log', - Warn = 'warn', - Error = 'error', - } - * @param {message} string // log, error or warning message - * @returns {boolean} // true if the message is logged successfully otherwise false - */ - - async logger(level, message, action) { - if (taskNodeAdministered) { - return await genericHandler('logger', level, message, action); - } else { - return await this.logMessage(level, message, action); - } - } - - async auditSubmission(candidatePubkey, isValid, voterKeypair, round) { - if (taskNodeAdministered) { - return await genericHandler( - 'auditSubmission', - candidatePubkey, - isValid, - round, - ); - } else { - if ( - this.#testingTaskState.submissions_audit_trigger[round] && - this.#testingTaskState.submissions_audit_trigger[round][candidatePubkey] - ) { - this.#testingTaskState.submissions_audit_trigger[round][ - candidatePubkey - ].votes.push({ - is_valid: isValid, - voter: voterKeypair.pubKey.toBase58(), - slot: 100, - }); - } else { - this.#testingTaskState.submissions_audit_trigger[round] = { - [candidatePubkey]: { - trigger_by: this.#testingStakingSystemAccount.publicKey.toBase58(), - slot: 100, - votes: [], - }, - }; - } - } - } - - async distributionListAuditSubmission( - candidatePubkey, - isValid, - voterKeypair, - round, - ) { - if (taskNodeAdministered) { - return await genericHandler( - 'distributionListAuditSubmission', - candidatePubkey, - isValid, - round, - ); - } else { - if ( - this.#testingTaskState.distributions_audit_trigger[round] && - this.#testingTaskState.distributions_audit_trigger[round][ - candidatePubkey - ] - ) { - this.#testingTaskState.distributions_audit_trigger[round][ - candidatePubkey - ].votes.push({ - is_valid: isValid, - voter: voterKeypair.pubKey.toBase58(), - slot: 100, - }); - } else { - this.#testingTaskState.distributions_audit_trigger[round] = { - [candidatePubkey]: { - trigger_by: this.#testingStakingSystemAccount.publicKey.toBase58(), - slot: 100, - votes: [], - }, - }; - } - } - } - - async getRound() { - if (taskNodeAdministered) { - return await genericHandler('getRound'); - } else { - return 1; - } - } - - async payoutTrigger(round) { - if (taskNodeAdministered) { - return await genericHandler('payloadTrigger', round); - } else { - console.log( - 'Payout Trigger only handles possitive flows (Without audits)', - ); - let round = 1; - const submissionValAcc = - this.#testingDistributionList[round][ - this.#testingStakingSystemAccount.toBase58() - ].submission_value; - this.#testingTaskState.available_balances = - this.#testingDistributionList[round][submissionValAcc]; - } - } - - async uploadDistributionList(distributionList, round) { - if (taskNodeAdministered) { - return await genericHandler( - 'uploadDistributionList', - distributionList, - round, - ); - } else { - if (!this.#testingDistributionList[round]) - this.#testingDistributionList[round] = {}; - - this.#testingDistributionList[round][ - this.#testingStakingSystemAccount.publicKey.toBase58() - ] = Buffer.from(JSON.stringify(distributionList)); - return true; - } - } - - async distributionListSubmissionOnChain(round) { - if (taskNodeAdministered) { - return await genericHandler('distributionListSubmissionOnChain', round); - } else { - if (!this.#testingTaskState.distribution_rewards_submission[round]) - this.#testingTaskState.distribution_rewards_submission[round] = {}; - - this.#testingTaskState.distribution_rewards_submission[round][ - this.#testingStakingSystemAccount.publicKey.toBase58() - ] = { - submission_value: - this.#testingStakingSystemAccount.publicKey.toBase58(), - slot: 200, - round: 1, - }; - } - } - - async checkSubmissionAndUpdateRound(submissionValue = 'default', round) { - if (taskNodeAdministered) { - return await genericHandler( - 'checkSubmissionAndUpdateRound', - submissionValue, - round, - ); - } else { - if (!this.#testingTaskState.submissions[round]) - this.#testingTaskState.submissions[round] = {}; - this.#testingTaskState.submissions[round][ - this.#testingStakingSystemAccount.publicKey.toBase58() - ] = { - submission_value: submissionValue, - slot: 100, - round, - }; - } - } - async getProgramAccounts() { - if (taskNodeAdministered) { - return await genericHandler('getProgramAccounts'); - } else { - console.log('Cannot call getProgramAccounts in testing mode'); - } - } - async defaultTaskSetup() { - if (taskNodeAdministered) { - return await genericHandler('defaultTaskSetup'); - } else { - if (this.#testingTaskState) return; - this.#testingMainSystemAccount = new Keypair(); - this.#testingStakingSystemAccount = new Keypair(); - this.#testingDistributionList = {}; - this.#testingTaskState = { - task_name: 'DummyTestState', - task_description: 'Dummy Task state for testing flow', - submissions: {}, - submissions_audit_trigger: {}, - total_bounty_amount: 10000000000, - bounty_amount_per_round: 1000000000, - total_stake_amount: 50000000000, - minimum_stake_amount: 5000000000, - available_balances: {}, - stake_list: {}, - round_time: 600, - starting_slot: 0, - audit_window: 200, - submission_window: 200, - distribution_rewards_submission: {}, - distributions_audit_trigger: {}, - }; - } - } - async getRpcUrl() { - if (taskNodeAdministered) { - return await genericHandler('getRpcUrl'); - } else { - console.log('Cannot call getNodes in testing mode'); - } - } - async getNodes(url) { - if (taskNodeAdministered) { - return await genericHandler('getNodes', url); - } else { - console.log('Cannot call getNodes in testing mode'); - } - } - - async getDistributionList(publicKey, round) { - if (taskNodeAdministered) { - const response = await genericHandler( - 'getDistributionList', - publicKey, - round, - ); - if (response.error) { - return null; - } - return response; - } else { - const submissionValAcc = - this.#testingTaskState.distribution_rewards_submission[round][ - this.#testingStakingSystemAccount.publicKey.toBase58() - ].submission_value; - return this.#testingDistributionList[round][submissionValAcc]; - } - } - - async getTaskSubmissionInfo(round) { - if (taskNodeAdministered) { - const taskSubmissionInfo = await genericHandler( - 'getTaskSubmissionInfo', - round, - ); - if (taskSubmissionInfo.error) { - return null; - } - return taskSubmissionInfo; - } else { - // console.log(this.#testingTaskState) - return this.#testingTaskState; - } - } - - async validateAndVoteOnNodes(validate, round) { - console.log('******/ IN VOTING /******'); - let taskAccountDataJSON = null; - try { - taskAccountDataJSON = await this.getTaskSubmissionInfo(round); - } catch (error) { - console.error('Error in getting submissions for the round', error); - } - if (taskAccountDataJSON == null) { - console.log('No submissions found for the round', round); - return; - } - console.log( - `Fetching the submissions of round ${round}`, - taskAccountDataJSON.submissions[round], - ); - const submissions = taskAccountDataJSON.submissions[round]; - if (submissions == null) { - console.log(`No submisssions found in round ${round}`); - return `No submisssions found in round ${round}`; - } else { - const keys = Object.keys(submissions); - const values = Object.values(submissions); - const size = values.length; - console.log('Submissions from last round: ', keys, values, size); - let isValid; - const submitterAccountKeyPair = await this.getSubmitterAccount(); - const submitterPubkey = submitterAccountKeyPair.publicKey.toBase58(); - for (let i = 0; i < size; i++) { - let candidatePublicKey = keys[i]; - console.log('FOR CANDIDATE KEY', candidatePublicKey); - let candidateKeyPairPublicKey = new PublicKey(keys[i]); - if (candidatePublicKey == submitterPubkey && taskNodeAdministered) { - console.log('YOU CANNOT VOTE ON YOUR OWN SUBMISSIONS'); - } else { - try { - console.log( - 'SUBMISSION VALUE TO CHECK', - values[i].submission_value, - ); - isValid = await validate(values[i].submission_value, round); - console.log(`Voting ${isValid} to ${candidatePublicKey}`); - - if (isValid) { - // check for the submissions_audit_trigger , if it exists then vote true on that otherwise do nothing - const submissions_audit_trigger = - taskAccountDataJSON.submissions_audit_trigger[round]; - console.log('SUBMIT AUDIT TRIGGER', submissions_audit_trigger); - // console.log( - // "CANDIDATE PUBKEY CHECK IN AUDIT TRIGGER", - // submissions_audit_trigger[candidatePublicKey] - // ); - if ( - submissions_audit_trigger && - submissions_audit_trigger[candidatePublicKey] - ) { - console.log('VOTING TRUE ON AUDIT'); - const response = await this.auditSubmission( - candidateKeyPairPublicKey, - isValid, - submitterAccountKeyPair, - round, - ); - console.log('RESPONSE FROM AUDIT FUNCTION', response); - } - } else if (isValid == false) { - // Call auditSubmission function and isValid is passed as false - console.log('RAISING AUDIT / VOTING FALSE'); - const response = await this.auditSubmission( - candidateKeyPairPublicKey, - isValid, - submitterAccountKeyPair, - round, - ); - console.log('RESPONSE FROM AUDIT FUNCTION', response); - } - } catch (err) { - console.log('ERROR IN ELSE CONDITION', err); - } - } - } - } - } - - async getTaskDistributionInfo(round) { - if (taskNodeAdministered) { - const taskDistributionInfo = await genericHandler( - 'getTaskDistributionInfo', - round, - ); - if (taskDistributionInfo.error) { - return null; - } - return taskDistributionInfo; - } else { - return this.#testingTaskState; - } - } - - async validateAndVoteOnDistributionList(validateDistribution, round) { - // await this.checkVoteStatus(); - console.log('******/ IN VOTING OF DISTRIBUTION LIST /******'); - let taskAccountDataJSON = null; - try { - taskAccountDataJSON = await this.getTaskDistributionInfo(round); - } catch (error) { - console.error('Error in getting distributions for the round', error); - } - if (taskAccountDataJSON == null) { - console.log('No distribution submissions found for the round', round); - return; - } - console.log( - `Fetching the Distribution submissions of round ${round}`, - taskAccountDataJSON.distribution_rewards_submission[round], - ); - const submissions = - taskAccountDataJSON?.distribution_rewards_submission[round]; - if ( - submissions == null || - submissions == undefined || - submissions.length == 0 - ) { - console.log(`No submisssions found in round ${round}`); - return `No submisssions found in round ${round}`; - } else { - const keys = Object.keys(submissions); - const values = Object.values(submissions); - const size = values.length; - console.log( - 'Distribution Submissions from last round: ', - keys, - values, - size, - ); - let isValid; - const submitterAccountKeyPair = await this.getSubmitterAccount(); - const submitterPubkey = submitterAccountKeyPair.publicKey.toBase58(); - - for (let i = 0; i < size; i++) { - let candidatePublicKey = keys[i]; - console.log('FOR CANDIDATE KEY', candidatePublicKey); - let candidateKeyPairPublicKey = new PublicKey(keys[i]); - if (candidatePublicKey == submitterPubkey) { - console.log('YOU CANNOT VOTE ON YOUR OWN DISTRIBUTION SUBMISSIONS'); - } else { - try { - console.log( - 'DISTRIBUTION SUBMISSION VALUE TO CHECK', - values[i].submission_value, - ); - isValid = await validateDistribution( - values[i].submission_value, - round, - ); - console.log(`Voting ${isValid} to ${candidatePublicKey}`); - - if (isValid) { - // check for the submissions_audit_trigger , if it exists then vote true on that otherwise do nothing - const distributions_audit_trigger = - taskAccountDataJSON.distributions_audit_trigger[round]; - console.log( - 'SUBMIT DISTRIBUTION AUDIT TRIGGER', - distributions_audit_trigger, - ); - // console.log( - // "CANDIDATE PUBKEY CHECK IN AUDIT TRIGGER", - // distributions_audit_trigger[candidatePublicKey] - // ); - if ( - distributions_audit_trigger && - distributions_audit_trigger[candidatePublicKey] - ) { - console.log('VOTING TRUE ON DISTRIBUTION AUDIT'); - const response = await this.distributionListAuditSubmission( - candidateKeyPairPublicKey, - isValid, - submitterAccountKeyPair, - round, - ); - console.log( - 'RESPONSE FROM DISTRIBUTION AUDIT FUNCTION', - response, - ); - } - } else if (isValid == false) { - // Call auditSubmission function and isValid is passed as false - console.log('RAISING AUDIT / VOTING FALSE ON DISTRIBUTION'); - const response = await this.distributionListAuditSubmission( - candidateKeyPairPublicKey, - isValid, - submitterAccountKeyPair, - round, - ); - console.log( - 'RESPONSE FROM DISTRIBUTION AUDIT FUNCTION', - response, - ); - } - } catch (err) { - console.log('ERROR IN ELSE CONDITION FOR DISTRIBUTION', err); - } - } - } - } - } - async getTaskLevelDBPath() { - if (taskNodeAdministered) { - return await genericHandler('getTaskLevelDBPath'); - } else { - return './KOIIDB'; - } - } - async getBasePath() { - if (taskNodeAdministered) { - const basePath = (await namespaceWrapper.getTaskLevelDBPath()).replace( - '/KOIIDB', - '', - ); - return basePath; - } else { - return './'; - } - } - - async getAverageSlotTime() { - if (taskNodeAdministered) { - try { - return await genericHandler('getAverageSlotTime'); - } catch (error) { - console.error('Error getting average slot time', error); - return 400; - } - } else { - return 400; - } - } - - async nodeSelectionDistributionList(round, isPreviousFailed) { - let taskAccountDataJSON = null; - try { - taskAccountDataJSON = await this.getTaskSubmissionInfo(round); - } catch (error) { - console.error('Task submission not found', error); - return; - } - - if (taskAccountDataJSON == null) { - console.error('Task state not found'); - return; - } - console.log('EXPECTED ROUND', round); - - const submissions = taskAccountDataJSON.submissions[round]; - if (submissions == null) { - console.log('No submisssions found in N-1 round'); - return 'No submisssions found in N-1 round'; - } else { - // getting last 3 submissions for the rounds - let keys; - const latestRounds = [round, round - 1, round - 2].filter(r => r >= 0); - - const promises = latestRounds.map(async r => { - if (r == round) { - return new Set(Object.keys(submissions)); - } else { - let roundSubmissions = null; - try { - roundSubmissions = await this.getTaskSubmissionInfo(r); - if (roundSubmissions && roundSubmissions.submissions[r]) { - return new Set(Object.keys(roundSubmissions.submissions[r])); - } - } catch (error) { - console.error('Error in getting submissions for the round', error); - } - return new Set(); - } - }); - - const keySets = await Promise.all(promises); - - // Find the keys present in all the rounds - keys = - keySets.length > 0 - ? [...keySets[0]].filter(key => keySets.every(set => set.has(key))) - : []; - if (keys.length == 0) { - console.log('No common keys found in last 3 rounds'); - keys = Object.keys(submissions); - } - console.log('KEYS', keys.length); - const values = keys.map(key => submissions[key]); - - let size = keys.length; - console.log('Submissions from N-2 round: ', size); - - // Check the keys i.e if the submitter shall be excluded or not - try { - const distributionData = await this.getTaskDistributionInfo(round); - const audit_record = distributionData?.distributions_audit_record; - if (audit_record && audit_record[round] == 'PayoutFailed') { - console.log('ROUND DATA', audit_record[round]); - console.log( - 'SUBMITTER LIST', - distributionData.distribution_rewards_submission[round], - ); - const submitterList = - distributionData.distribution_rewards_submission[round]; - const submitterKeys = Object.keys(submitterList); - console.log('SUBMITTER KEYS', submitterKeys); - const submitterSize = submitterKeys.length; - console.log('SUBMITTER SIZE', submitterSize); - - for (let j = 0; j < submitterSize; j++) { - console.log('SUBMITTER KEY CANDIDATE', submitterKeys[j]); - const id = keys.indexOf(submitterKeys[j]); - console.log('ID', id); - if (id != -1) { - keys.splice(id, 1); - values.splice(id, 1); - size--; - } - } - - console.log('KEYS FOR HASH CALC', keys.length); - } - } catch (error) { - console.log('Error in getting distribution data', error); - } - - // calculating the digest - - const ValuesString = JSON.stringify(values); - - const hashDigest = createHash('sha256') - .update(ValuesString) - .digest('hex'); - - console.log('HASH DIGEST', hashDigest); - - // function to calculate the score - const calculateScore = (str = '') => { - return str.split('').reduce((acc, val) => { - return acc + val.charCodeAt(0); - }, 0); - }; - - // function to compare the ASCII values - - const compareASCII = (str1, str2) => { - const firstScore = calculateScore(str1); - const secondScore = calculateScore(str2); - return Math.abs(firstScore - secondScore); - }; - - // loop through the keys and select the one with higest score - - const selectedNode = { - score: 0, - pubkey: '', - }; - let score = 0; - if (isPreviousFailed) { - let leastScore = -Infinity; - let secondLeastScore = -Infinity; - for (let i = 0; i < size; i++) { - const candidateSubmissionJson = {}; - candidateSubmissionJson[keys[i]] = values[i]; - const candidateSubmissionString = JSON.stringify( - candidateSubmissionJson, - ); - const candidateSubmissionHash = createHash('sha256') - .update(candidateSubmissionString) - .digest('hex'); - const candidateScore = compareASCII( - hashDigest, - candidateSubmissionHash, - ); - if (candidateScore > leastScore) { - secondLeastScore = leastScore; - leastScore = candidateScore; - } else if (candidateScore > secondLeastScore) { - secondLeastScore = candidateScore; - selectedNode.score = candidateScore; - selectedNode.pubkey = keys[i]; - } - } - } else { - for (let i = 0; i < size; i++) { - const candidateSubmissionJson = {}; - candidateSubmissionJson[keys[i]] = values[i]; - const candidateSubmissionString = JSON.stringify( - candidateSubmissionJson, - ); - const candidateSubmissionHash = createHash('sha256') - .update(candidateSubmissionString) - .digest('hex'); - const candidateScore = compareASCII( - hashDigest, - candidateSubmissionHash, - ); - // console.log('CANDIDATE SCORE', candidateScore); - if (candidateScore > score) { - score = candidateScore; - selectedNode.score = candidateScore; - selectedNode.pubkey = keys[i]; - } - } - } - - console.log('SELECTED NODE OBJECT', selectedNode); - return selectedNode.pubkey; - } - } - - async selectAndGenerateDistributionList( - submitDistributionList, - round, - isPreviousRoundFailed, - ) { - console.log('SelectAndGenerateDistributionList called'); - const selectedNode = await this.nodeSelectionDistributionList( - round, - isPreviousRoundFailed, - ); - console.log('Selected Node', selectedNode); - const submitPubKey = await this.getSubmitterAccount(); - if ( - selectedNode == undefined || - selectedNode == '' || - submitPubKey == undefined - ) - return; - if (selectedNode == submitPubKey?.publicKey.toBase58()) { - await submitDistributionList(round); - const taskState = await this.getTaskState({}); - if (taskState == null) { - console.error('Task state not found'); - return; - } - const avgSlotTime = await this.getAverageSlotTime(); - if (avgSlotTime == null) { - console.error('Avg slot time not found'); - return; - } - setTimeout(async () => { - await this.payoutTrigger(round); - }, (taskState.audit_window + taskState.submission_window) * avgSlotTime); - } - } - - getMainAccountPubkey() { - if (taskNodeAdministered) { - return MAIN_ACCOUNT_PUBKEY; - } else { - return this.#testingMainSystemAccount.publicKey.toBase58(); - } - } -} - -async function genericHandler(...args) { - try { - let response = await axios.post(BASE_ROOT_URL, { - args, - taskId: TASK_ID, - secret: SECRET_KEY, - }); - if (response.status == 200) return response.data.response; - else { - console.error(response.status, response.data); - return null; - } - } catch (err) { - console.error(`Error in genericHandler: "${args[0]}"`, err.message); - console.error(err?.response?.data); - return { error: err }; - } -} - -const namespaceWrapper = new NamespaceWrapper(); -if (taskNodeAdministered) { - namespaceWrapper.getRpcUrl().then(rpcUrl => { - console.log(rpcUrl, 'RPC URL'); - connection = new Connection(rpcUrl, 'confirmed'); - }); -} -module.exports = { - namespaceWrapper, - taskNodeAdministered, // Boolean flag indicating that the task is being ran in active mode (Task node supervised), or development (testing) mode - app, // The initialized express app to be used to register endpoints - TASK_ID, // This will be the PORT on which the this task is expected to run the express server coming from the task node running this task. As all communication via the task node and this task will be done on this port. - MAIN_ACCOUNT_PUBKEY, // This will be the secret used to authenticate with task node running this task. - SECRET_KEY, // This will be the secret used by the task to authenticate with task node running this task. - K2_NODE_URL, // This will be K2 url being used by the task node, possible values are 'https://k2-testnet.koii.live' | 'https://k2-devnet.koii.live' | 'http://localhost:8899' - SERVICE_URL, // This will be public task node endpoint (Or local if it doesn't have any) of the task node running this task. - STAKE, // This will be stake of the task node running this task, can be double checked with the task state and staking public key. - TASK_NODE_PORT, // This will be the port used by task node as the express server port, so it can be used by the task for the communication with the task node - _server, // Express server object -};