From c7d8ad0d458229c8f01c5a7e662885a08aca59e8 Mon Sep 17 00:00:00 2001 From: Torresmorah Date: Fri, 26 Jul 2024 16:05:37 -0600 Subject: [PATCH 1/7] feat(hapi): add a worker to retrieve the rewards in fio blockchain --- hapi/src/services/fio.service.js | 120 ++++++++++++++++++++++++-- hapi/src/services/index.js | 2 + hapi/src/services/producer.service.js | 4 +- hapi/src/utils/producer.util.js | 7 -- hapi/src/workers/producers.worker.js | 10 ++- 5 files changed, 125 insertions(+), 18 deletions(-) diff --git a/hapi/src/services/fio.service.js b/hapi/src/services/fio.service.js index 6a0e16cd..70d56d54 100644 --- a/hapi/src/services/fio.service.js +++ b/hapi/src/services/fio.service.js @@ -1,8 +1,22 @@ const { StatusCodes } = require('http-status-codes') -const { axiosUtil, eosUtil, sequelizeUtil, producerUtil } = require('../utils') +const { axiosUtil, hasuraUtil, eosUtil, sequelizeUtil, producerUtil } = require('../utils') const { eosConfig } = require('../config') +const MAX_PAID_PRODUCERS = 42 + +const updateRewards = async (producers = []) => { + const upsertMutation = ` + mutation ($producers: [producer_insert_input!]!) { + insert_producer(objects: $producers, on_conflict: {constraint: producer_owner_key, update_columns: [ vote_rewards, block_rewards, total_rewards ]}) { + affected_rows, + } + } + ` + + await hasuraUtil.request(upsertMutation, { producers }) +} + const getProducers = async () => { let producers = [] let totalVoteWeight @@ -60,16 +74,9 @@ const getProducers = async () => { return 0 }) - const rewards = await producerUtil.getExpectedRewards( - producers, - totalVoteWeight - ) - const nonPaidStandby = { vote_rewards: 0, block_rewards: 0, total_rewards: 0 } - producers = producers.map((producer, index) => { return { owner: producer.owner, - ...(rewards[producer.owner] || nonPaidStandby), total_votes: producer.total_votes, total_votes_percent: producer.total_votes / totalVoteWeight, total_votes_eos: producerUtil.getVotes(producer.total_votes), @@ -251,6 +258,101 @@ const getProducersFromDB = async () => { return producers } +const getVoteShares = async () => { + try { + const { rows } = await eosUtil.getTableRows({ + code: 'fio.treasury', + scope: 'fio.treasury', + table: 'voteshares', + reverse: false, + limit: MAX_PAID_PRODUCERS, + json: true, + lower_bound: null + }) + + if (!Array.isArray(rows)) return [] + + return rows + } catch (error) { + console.warn('SYNC FIO REWARDS', error.message || error) + + return [] + } +} + +const getFioRewards = async (producers, voteShares) => { + const activeProducers = producers.slice(0, MAX_PAID_PRODUCERS) + + return voteShares.flatMap(producer => { + if (activeProducers.find(bp => bp.owner === producer.owner)) { + const expectedVoteReward = producer.abpayshare / 10 ** 9 + const expectedBlockReward = producer.sbpayshare / 10 ** 9 + + return ({ + owner: producer.owner, + vote_rewards: expectedVoteReward, + block_rewards: expectedBlockReward, + total_rewards: expectedVoteReward + expectedBlockReward + }) + } + + return [] + }) +} + +const getProducersWithRewards = async (voteShares) => { + const producers = await getProducersFromDB() + const paidProducers = await getFioRewards(producers.slice(0, MAX_PAID_PRODUCERS), voteShares) + const nonPaidStandby = + producers.slice(MAX_PAID_PRODUCERS).map(producer => ({ + owner: producer.owner, + vote_rewards: 0, + block_rewards: 0, + total_rewards: 0 + })) + + return paidProducers.concat(nonPaidStandby) +} + +const getLastPaidScheduleTime = async () => { + try { + const { rows } = await eosUtil.getTableRows({ + code: 'fio.treasury', + scope: 'fio.treasury', + table: 'clockstate', + reverse: false, + limit: 1, + json: true, + lower_bound: null + }) + + if (!rows?.at(0)?.payschedtimer) return + + return new Date(rows?.at(0)?.payschedtimer * 1000) + } catch (error) { + console.warn('SYNC FIO REWARDS', error.message || error) + } +} + +// Every day the `voteshare` table is populated with the rewards the BP can claim that day. +const syncRewards = async () => { + console.log('SYNCING FIO REWARDS') + + const voteShares = await getVoteShares() + const producers = await getProducersWithRewards(voteShares) + + await updateRewards(producers) + + const scheduleTime = await getLastPaidScheduleTime() + + scheduleTime.setSeconds(scheduleTime.getSeconds() + 86400) + + const nextScheduleUpdate = Math.ceil((scheduleTime - new Date())) + + setTimeout(syncRewards, nextScheduleUpdate) +} + module.exports = { - getProducers + getProducers, + syncRewards } diff --git a/hapi/src/services/index.js b/hapi/src/services/index.js index 42889a26..e69c50ca 100644 --- a/hapi/src/services/index.js +++ b/hapi/src/services/index.js @@ -1,6 +1,7 @@ const cpuService = require('./cpu.service') const missedBlocksService = require('./missed-blocks.service') const producerService = require('./producer.service') +const fioService = require('./fio.service') const nodeService = require('./node.service') const healthCheckHistoryService = require('./health-check-history.service') const settingService = require('./setting.service') @@ -14,6 +15,7 @@ module.exports = { cpuService, missedBlocksService, producerService, + fioService, nodeService, healthCheckHistoryService, settingService, diff --git a/hapi/src/services/producer.service.js b/hapi/src/services/producer.service.js index ec0244ad..e002a268 100644 --- a/hapi/src/services/producer.service.js +++ b/hapi/src/services/producer.service.js @@ -23,9 +23,11 @@ const updateBPJSONs = async (producers = []) => { } const updateProducers = async (producers = []) => { + const includeRewards = eosConfig.networkName !== eosConfig.knownNetworks.fio + const additionalFields = includeRewards ? 'vote_rewards, block_rewards, total_rewards' : '' const upsertMutation = ` mutation ($producers: [producer_insert_input!]!) { - insert_producer(objects: $producers, on_conflict: {constraint: producer_owner_key, update_columns: [ producer_key, unpaid_blocks,last_claim_time, url, location, producer_authority, is_active, total_votes, total_votes_percent, total_votes_eos, vote_rewards,block_rewards, total_rewards, endpoints, rank, bp_json_url, fio_address, addresshash, last_bpclaim]}) { + insert_producer(objects: $producers, on_conflict: {constraint: producer_owner_key, update_columns: [ producer_key, unpaid_blocks,last_claim_time, url, location, producer_authority, is_active, total_votes, total_votes_percent, total_votes_eos, endpoints, rank, bp_json_url, fio_address, addresshash, last_bpclaim, ${additionalFields}]}) { affected_rows, returning { id, diff --git a/hapi/src/utils/producer.util.js b/hapi/src/utils/producer.util.js index eb9a393d..b3e36070 100644 --- a/hapi/src/utils/producer.util.js +++ b/hapi/src/utils/producer.util.js @@ -127,7 +127,6 @@ const getExpectedRewards = async (producers, totalVotes) => { rewards = await getTelosRewards(producers) break case eosConfig.knownNetworks.fio: - rewards = await getFioRewards(producers) break default: rewards = await getEOSIORewards(producers, totalVotes) @@ -281,12 +280,6 @@ const getEOSIORewards = async (producers, totalVotes) => { return producersRewards } -const getFioRewards = async (producers) => { - const producersRewards = [] - // ToDo : Calculate producer Rewards Based on FIO System Contracts - return producersRewards -} - const getVotes = (votes) => { switch (eosConfig.networkName) { case eosConfig.knownNetworks.telos: diff --git a/hapi/src/workers/producers.worker.js b/hapi/src/workers/producers.worker.js index a1ff63a7..ea49a7a1 100644 --- a/hapi/src/workers/producers.worker.js +++ b/hapi/src/workers/producers.worker.js @@ -4,7 +4,8 @@ const { producerService, settingService, stateHistoryPluginService, - statsService + statsService, + fioService, } = require('../services') const { workersConfig, hasuraConfig, eosConfig } = require('../config') const { axiosUtil, sleepFor } = require('../utils') @@ -59,6 +60,13 @@ const start = async () => { workersConfig.syncExchangeRate ) + if (eosConfig.networkName === eosConfig.knownNetworks.fio) { + run( + 'SYNC FIO REWARDS', + fioService.syncRewards + ) + } + if (eosConfig.eosmechanics.account && eosConfig.eosmechanics.password) { run('CPU WORKER', cpuService.worker, workersConfig.cpuWorkerInterval) run('CPU WORKER CLEANUP', cpuService.cleanOldBenchmarks, 86400) From 4a0837cc645e3899c654f1f1ed56ad7ae36696c9 Mon Sep 17 00:00:00 2001 From: Torresmorah Date: Fri, 26 Jul 2024 16:06:43 -0600 Subject: [PATCH 2/7] ci(fio-testnet): fix network name in hapi --- .github/workflows/deploy-fio-testnet.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-fio-testnet.yaml b/.github/workflows/deploy-fio-testnet.yaml index a20cd483..e998f2b2 100644 --- a/.github/workflows/deploy-fio-testnet.yaml +++ b/.github/workflows/deploy-fio-testnet.yaml @@ -80,7 +80,7 @@ jobs: POSTGRES_DB: ${{ secrets.POSTGRES_DB }} POSTGRES_DATA: ${{ secrets.POSTGRES_DATA }} # hapi - HAPI_EOS_API_NETWORK_NAME: fio-testnet + HAPI_EOS_API_NETWORK_NAME: fio HAPI_EOS_API_ENDPOINTS: '["https://testnet.fioprotocol.io","https://fio-testnet.eosphere.io"]' HAPI_EOS_STATE_HISTORY_PLUGIN_ENDPOINT: '' HAPI_EOS_MISSED_BLOCKS_ENABLED: 'false' From c70a3f675631c9748ba6d2cc137b6a6c66098a69 Mon Sep 17 00:00:00 2001 From: Torresmorah Date: Fri, 26 Jul 2024 16:40:38 -0600 Subject: [PATCH 3/7] feat(hapi): wait bps synchronization to synchronize fio rewards --- hapi/src/services/fio.service.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/hapi/src/services/fio.service.js b/hapi/src/services/fio.service.js index 70d56d54..6edf0ead 100644 --- a/hapi/src/services/fio.service.js +++ b/hapi/src/services/fio.service.js @@ -341,15 +341,19 @@ const syncRewards = async () => { const voteShares = await getVoteShares() const producers = await getProducersWithRewards(voteShares) - await updateRewards(producers) + if (!producers?.length) { + setTimeout(syncRewards, 120 * 1000) + } else { + await updateRewards(producers) - const scheduleTime = await getLastPaidScheduleTime() + const scheduleTime = await getLastPaidScheduleTime() - scheduleTime.setSeconds(scheduleTime.getSeconds() + 86400) + scheduleTime.setSeconds(scheduleTime.getSeconds() + 86400) - const nextScheduleUpdate = Math.ceil((scheduleTime - new Date())) + const nextScheduleUpdate = Math.ceil((scheduleTime.getTime() - (new Date()).getTime())) - setTimeout(syncRewards, nextScheduleUpdate) + setTimeout(syncRewards, nextScheduleUpdate) + } } module.exports = { From 8d27488f8676f5f9e1533b30c8752675e3e86044 Mon Sep 17 00:00:00 2001 From: Torresmorah Date: Mon, 29 Jul 2024 09:30:26 -0600 Subject: [PATCH 4/7] fix(hapi): wait until new paid schedule is created --- hapi/src/services/fio.service.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/hapi/src/services/fio.service.js b/hapi/src/services/fio.service.js index 6edf0ead..fc86fe0c 100644 --- a/hapi/src/services/fio.service.js +++ b/hapi/src/services/fio.service.js @@ -342,7 +342,7 @@ const syncRewards = async () => { const producers = await getProducersWithRewards(voteShares) if (!producers?.length) { - setTimeout(syncRewards, 120 * 1000) + setTimeout(syncRewards, 2 * 60 * 1000) } else { await updateRewards(producers) @@ -352,7 +352,12 @@ const syncRewards = async () => { const nextScheduleUpdate = Math.ceil((scheduleTime.getTime() - (new Date()).getTime())) - setTimeout(syncRewards, nextScheduleUpdate) + if (nextScheduleUpdate > 0) { + console.log(`SYNCING FIO REWARDS - sync completed, next sync on ${scheduleTime.toISOString()}`) + setTimeout(syncRewards, nextScheduleUpdate) + } else { + setTimeout(syncRewards, 5 * 60 * 1000) + } } } From 3592f1163769e9c9cc11281cc4cead81a0582246 Mon Sep 17 00:00:00 2001 From: Torresmorah Date: Mon, 29 Jul 2024 10:06:31 -0600 Subject: [PATCH 5/7] fix(hapi): fix minimum rewards on FIO blockchain --- hapi/src/services/fio.service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hapi/src/services/fio.service.js b/hapi/src/services/fio.service.js index fc86fe0c..dfd0d8c6 100644 --- a/hapi/src/services/fio.service.js +++ b/hapi/src/services/fio.service.js @@ -194,7 +194,7 @@ const getChains = async producerUrl => { } const isNonCompliant = producer => { - return !Object.keys(producer.bpJson).length && producer.total_rewards >= 100 + return !Object.keys(producer.bpJson).length && producer.total_rewards > 0 } const getProducerHealthStatus = async producer => { From d6bbc4737059bfa1ebcc7b816b547e2cae011412 Mon Sep 17 00:00:00 2001 From: Torresmorah Date: Mon, 29 Jul 2024 10:07:06 -0600 Subject: [PATCH 6/7] fix(webapp): fix minimum rewards on FIO blockchain --- webapp/src/hooks/customHooks/useBlockProducerState.js | 4 +++- webapp/src/hooks/customHooks/useNonCompliantState.js | 8 ++++++-- .../hooks/customHooks/useRewardsDistributionState.js | 10 +++++++--- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/webapp/src/hooks/customHooks/useBlockProducerState.js b/webapp/src/hooks/customHooks/useBlockProducerState.js index bba19d8d..aec3d5ad 100644 --- a/webapp/src/hooks/customHooks/useBlockProducerState.js +++ b/webapp/src/hooks/customHooks/useBlockProducerState.js @@ -6,13 +6,15 @@ import { eosConfig } from '../../config' import useSearchState from './useBPSearchState' +const isFIO = eosConfig.networkName.replace('-testnet','') === 'fio' +const minimumRewards = isFIO ? 1 : 100 const CHIPS_FILTERS = [ { where: { owner: { _like: '%%' }, bp_json: { _is_null: false } } }, { where: { total_rewards: { _neq: 0 }, rank: { _lte: 21 } }, }, { - where: { total_rewards: { _gte: 100 }, rank: { _gte: 22 } }, + where: { total_rewards: { _gte: minimumRewards }, rank: { _gte: 22 } }, }, { where: { total_rewards: { _eq: 0 } }, diff --git a/webapp/src/hooks/customHooks/useNonCompliantState.js b/webapp/src/hooks/customHooks/useNonCompliantState.js index f9ff7667..6a8e4e3a 100644 --- a/webapp/src/hooks/customHooks/useNonCompliantState.js +++ b/webapp/src/hooks/customHooks/useNonCompliantState.js @@ -3,23 +3,27 @@ import { useLazyQuery } from '@apollo/client' import { PRODUCERS_QUERY, SETTING_QUERY } from '../../gql' +import { eosConfig } from 'config' + const useNonCompliantState = () => { const [loadProducers, { loading = true, data: { producers } = {} }] = useLazyQuery(PRODUCERS_QUERY) const [loadSettings, { data: { setting } = {} }] = useLazyQuery(SETTING_QUERY) const [items, setItems] = useState([]) const [stats, setStats] = useState() + const isFIO = eosConfig.networkName.replace('-testnet','') === 'fio' + const minimumRewards = isFIO ? 1 : 100 useEffect(() => { loadSettings({}) loadProducers({ variables: { - where: { total_rewards: { _gte: 100 } }, + where: { total_rewards: { _gte: minimumRewards } }, offset: 0, limit: 150, }, }) - }, [loadSettings, loadProducers]) + }, [loadSettings, loadProducers, minimumRewards]) useEffect(() => { if (!producers) return diff --git a/webapp/src/hooks/customHooks/useRewardsDistributionState.js b/webapp/src/hooks/customHooks/useRewardsDistributionState.js index 6483d2d1..7dc7827f 100644 --- a/webapp/src/hooks/customHooks/useRewardsDistributionState.js +++ b/webapp/src/hooks/customHooks/useRewardsDistributionState.js @@ -1,26 +1,30 @@ import { useState, useEffect } from 'react' import { useLazyQuery } from '@apollo/client' -import { countries } from 'utils/countries' import { PRODUCERS_QUERY, SETTING_QUERY } from '../../gql' +import { countries } from 'utils/countries' +import { eosConfig } from 'config' + const useRewardsDistributionState = () => { const [loadSettings, { data: { setting } = {} }] = useLazyQuery(SETTING_QUERY) const [loadProducers, { loading = true, data: { producers } = {} }] = useLazyQuery(PRODUCERS_QUERY) const [summary, setSummary] = useState() const [items, setItems] = useState([]) + const isFIO = eosConfig.networkName.replace('-testnet','') === 'fio' + const minimumRewards = isFIO ? 1 : 100 useEffect(() => { loadSettings({}) loadProducers({ variables: { - where: { total_rewards: { _gte: 100 } }, + where: { total_rewards: { _gte: minimumRewards } }, offset: 0, limit: 2100, }, }) - }, [loadSettings, loadProducers]) + }, [loadSettings, loadProducers, minimumRewards]) useEffect(() => { if (!producers) return From 844ebd82bcf6efee4c6a923580368c15cc440095 Mon Sep 17 00:00:00 2001 From: Torresmorah Date: Mon, 29 Jul 2024 10:10:45 -0600 Subject: [PATCH 7/7] chore(webapp): format code --- webapp/src/hooks/customHooks/useBlockProducerState.js | 2 +- webapp/src/hooks/customHooks/useNonCompliantState.js | 2 +- webapp/src/hooks/customHooks/useRewardsDistributionState.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/webapp/src/hooks/customHooks/useBlockProducerState.js b/webapp/src/hooks/customHooks/useBlockProducerState.js index aec3d5ad..e7026fa7 100644 --- a/webapp/src/hooks/customHooks/useBlockProducerState.js +++ b/webapp/src/hooks/customHooks/useBlockProducerState.js @@ -6,7 +6,7 @@ import { eosConfig } from '../../config' import useSearchState from './useBPSearchState' -const isFIO = eosConfig.networkName.replace('-testnet','') === 'fio' +const isFIO = eosConfig.networkName.replace('-testnet', '') === 'fio' const minimumRewards = isFIO ? 1 : 100 const CHIPS_FILTERS = [ { where: { owner: { _like: '%%' }, bp_json: { _is_null: false } } }, diff --git a/webapp/src/hooks/customHooks/useNonCompliantState.js b/webapp/src/hooks/customHooks/useNonCompliantState.js index 6a8e4e3a..1c5cc98b 100644 --- a/webapp/src/hooks/customHooks/useNonCompliantState.js +++ b/webapp/src/hooks/customHooks/useNonCompliantState.js @@ -11,7 +11,7 @@ const useNonCompliantState = () => { const [loadSettings, { data: { setting } = {} }] = useLazyQuery(SETTING_QUERY) const [items, setItems] = useState([]) const [stats, setStats] = useState() - const isFIO = eosConfig.networkName.replace('-testnet','') === 'fio' + const isFIO = eosConfig.networkName.replace('-testnet', '') === 'fio' const minimumRewards = isFIO ? 1 : 100 useEffect(() => { diff --git a/webapp/src/hooks/customHooks/useRewardsDistributionState.js b/webapp/src/hooks/customHooks/useRewardsDistributionState.js index 7dc7827f..2bcbf1e2 100644 --- a/webapp/src/hooks/customHooks/useRewardsDistributionState.js +++ b/webapp/src/hooks/customHooks/useRewardsDistributionState.js @@ -12,7 +12,7 @@ const useRewardsDistributionState = () => { useLazyQuery(PRODUCERS_QUERY) const [summary, setSummary] = useState() const [items, setItems] = useState([]) - const isFIO = eosConfig.networkName.replace('-testnet','') === 'fio' + const isFIO = eosConfig.networkName.replace('-testnet', '') === 'fio' const minimumRewards = isFIO ? 1 : 100 useEffect(() => {