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' diff --git a/hapi/src/services/fio.service.js b/hapi/src/services/fio.service.js index 6a0e16cd..225251d1 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), @@ -187,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 => { @@ -251,6 +258,115 @@ 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) + + if (!producers?.length) { + setTimeout(syncRewards, 2 * 60 * 1000) + } else { + await updateRewards(producers) + + const scheduleTime = await getLastPaidScheduleTime() + const scheduleTimeMs = scheduleTime?.getTime() || 0 + + if (scheduleTimeMs > 0) { + scheduleTime.setSeconds(scheduleTime.getSeconds() + 86400) + + const nextScheduleUpdate = Math.ceil((scheduleTime.getTime() - (new Date()).getTime())) + + if (nextScheduleUpdate > 0) { + console.log(`SYNCING FIO REWARDS - sync completed, next sync on ${scheduleTime.toISOString()}`) + setTimeout(syncRewards, nextScheduleUpdate) + } else { + setTimeout(syncRewards, 5 * 60 * 1000) + } + } else { + setTimeout(syncRewards, 60 * 1000) + } + } +} + 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) diff --git a/webapp/src/hooks/customHooks/useBlockProducerState.js b/webapp/src/hooks/customHooks/useBlockProducerState.js index bba19d8d..e7026fa7 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..1c5cc98b 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..2bcbf1e2 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