diff --git a/hapi/src/config/eos.config.js b/hapi/src/config/eos.config.js
index 53d2a5ad..3e941363 100644
--- a/hapi/src/config/eos.config.js
+++ b/hapi/src/config/eos.config.js
@@ -46,6 +46,7 @@ module.exports = {
nodeTable: process.env.HAPI_EOS_BP_JSON_ON_CHAIN_TABLE2 || 'node'
},
knownNetworks: {
+ fio: 'fio',
lacchain: 'lacchain',
libre: 'libre',
telos: 'telos',
diff --git a/hapi/src/services/cpu.service.js b/hapi/src/services/cpu.service.js
index a3f22c23..8f935274 100644
--- a/hapi/src/services/cpu.service.js
+++ b/hapi/src/services/cpu.service.js
@@ -6,7 +6,7 @@ const {
} = require('../utils')
const { eosConfig } = require('../config')
-const saveBenchmark = async payload => {
+const saveBenchmark = async (payload) => {
const mutation = `
mutation ($account: String!, $usage: Int!) {
insert_cpu_one (object: {account: $account, usage: $usage}) {
@@ -39,7 +39,11 @@ const cleanOldBenchmarks = async () => {
}
const worker = async () => {
- if (!eosConfig.eosmechanics.account || !eosConfig.eosmechanics.password) {
+ if (
+ eosConfig.eosmechanics.account === ' ' ||
+ !eosConfig.eosmechanics.account ||
+ !eosConfig.eosmechanics.password
+ ) {
return
}
@@ -49,7 +53,7 @@ const worker = async () => {
await saveBenchmark({
account: block.producer,
usage: block?.transactions.find(
- trx => trx?.trx?.id === transaction.processed.id
+ (trx) => trx?.trx?.id === transaction.processed.id
).cpu_usage_us
})
} catch (error) {
diff --git a/hapi/src/services/fio.service.js b/hapi/src/services/fio.service.js
new file mode 100644
index 00000000..6a0e16cd
--- /dev/null
+++ b/hapi/src/services/fio.service.js
@@ -0,0 +1,256 @@
+const { StatusCodes } = require('http-status-codes')
+
+const { axiosUtil, eosUtil, sequelizeUtil, producerUtil } = require('../utils')
+const { eosConfig } = require('../config')
+
+const getProducers = async () => {
+ let producers = []
+ let totalVoteWeight
+ let hasMore = true
+ let nextKey
+
+ try {
+ while (hasMore) {
+ const {
+ rows,
+ more
+ } = await eosUtil.getTableRows({
+ code: 'eosio',
+ table: 'producers',
+ scope: 'eosio',
+ limit: 100,
+ json: true,
+ lower_bound: nextKey
+ })
+
+ hasMore = !!more
+ nextKey = more
+ producers.push(...rows)
+ }
+
+ const {
+ rows,
+ } = await eosUtil.getTableRows({
+ code: 'eosio',
+ table: 'global',
+ scope: 'eosio',
+ limit: 1,
+ json: true,
+ lower_bound: nextKey
+ })
+
+ totalVoteWeight = parseFloat(rows?.at(0)?.total_producer_vote_weight)
+ } catch (error) {
+ console.error('PRODUCER SYNC ERROR', error)
+ producers = await getProducersFromDB()
+
+ return await getBPJsons(producers)
+ }
+ producers = producers
+ .filter(producer => !!producer.is_active)
+ .sort((a, b) => {
+ if (parseInt(a.total_votes) > parseInt(b.total_votes)) {
+ return -1
+ }
+
+ if (parseInt(a.total_votes) < parseInt(b.total_votes)) {
+ return 1
+ }
+
+ 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),
+ rank: index + 1,
+ producer_key: producer.producer_public_key,
+ url: producer.url,
+ unpaid_blocks: producer.unpaid_blocks,
+ last_claim_time: producer.last_claim_time,
+ location: producer.location,
+ producer_authority: producer.producer_authority,
+ is_active: !!producer.is_active,
+ fio_address: producer.fio_address,
+ addresshash: producer.addresshash,
+ last_bpclaim: producer.last_bpclaim?.toString() || '0'
+ }
+ })
+ producers = await getBPJsons(producers)
+
+ return producers
+}
+
+const getBPJsons = async (producers = []) => {
+ const isEosNetwork = eosConfig.chainId === eosConfig.eosChainId
+ let topProducers = producers.slice(0, eosConfig.eosTopLimit)
+
+ topProducers = await Promise.all(
+ topProducers.map(async producer => {
+ let bpJson = {}
+ let bpJsonUrl = ''
+ let healthStatus = []
+
+ if (producer.url && producer.url.length > 3) {
+ const producerUrl = getProducerUrl(producer)
+ const chains = await getChains(producerUrl)
+ const chainUrl = chains[eosConfig.chainId]
+ bpJsonUrl = getBPJsonUrl(producerUrl, chainUrl || '/bp.json')
+
+ try {
+ bpJson = await getBPJson(bpJsonUrl)
+ } catch (error) {
+ if (error.code === 'ECONNABORTED') {
+ return {
+ ...producer,
+ bp_json_url: bpJsonUrl,
+ health_status: healthStatus,
+ bp_json: bpJson
+ }
+ }
+ }
+
+ if (bpJson && !chainUrl && !isEosNetwork) {
+ const { org, producer_account_name: name } = bpJson
+
+ bpJson = {
+ ...(org && { org }),
+ ...(name && { producer_account_name: name })
+ }
+ }
+
+ healthStatus = await getProducerHealthStatus({
+ ...producer,
+ producerUrl,
+ bpJson,
+ })
+ }
+
+ return {
+ ...producer,
+ bp_json_url: bpJsonUrl,
+ health_status: healthStatus,
+ bp_json: bpJson
+ }
+ })
+ )
+
+ return topProducers.concat(producers.slice(eosConfig.eosTopLimit))
+}
+
+const getBPJsonUrl = (producerUrl, chainUrl) => {
+ return `${producerUrl}/${chainUrl}`.replace(/(?<=:\/\/.*)((\/\/))/, '/')
+}
+
+const getBPJson = async bpJsonUrl => {
+ const { data: _bpJson } = await axiosUtil.instance.get(bpJsonUrl)
+ const bpJson = !!_bpJson && typeof _bpJson === 'object' ? _bpJson : {}
+
+ return bpJson
+}
+
+const getProducerUrl = producer => {
+ let producerUrl = producer?.url?.replace(/(“|'|”|")/g, '') || ''
+
+ if (!producerUrl.startsWith('http')) {
+ producerUrl = `http://${producerUrl}`
+ }
+
+ return producerUrl
+}
+
+const getChains = async producerUrl => {
+ const chainsUrl = `${producerUrl}/chains.json`.replace(
+ /(?<=:\/\/.*)((\/\/))/,
+ '/'
+ )
+
+ try {
+ const {
+ data: { chains }
+ } = await axiosUtil.instance.get(chainsUrl)
+
+ return chains ?? {}
+ } catch (error) {
+ return {}
+ }
+}
+
+const isNonCompliant = producer => {
+ return !Object.keys(producer.bpJson).length && producer.total_rewards >= 100
+}
+
+const getProducerHealthStatus = async producer => {
+ const healthStatus = []
+ const {bpJson, producerUrl} = producer
+
+ if (isNonCompliant(producer)) {
+ const response = await producerUtil.getUrlStatus(producerUrl)
+
+ healthStatus.push({ name: 'bpJson', valid: false })
+ healthStatus.push({
+ name: 'website',
+ valid: response?.status === StatusCodes.OK,
+ response: {
+ status: response?.status,
+ statusText: response?.statusText || 'No response'
+ }
+ })
+
+ return healthStatus
+ }
+
+ if (!bpJson || !Object.keys(bpJson).length) return []
+
+ healthStatus.push({
+ name: 'bpJson',
+ valid: true
+ })
+ healthStatus.push({
+ name: 'organization_name',
+ valid: !!bpJson.org?.candidate_name
+ })
+ healthStatus.push({
+ name: 'email',
+ valid: !!bpJson.org?.email
+ })
+ healthStatus.push({
+ name: 'website',
+ valid: !!bpJson.org?.website
+ })
+ healthStatus.push({
+ name: 'logo_256',
+ valid: !!bpJson?.org?.branding?.logo_256
+ })
+ healthStatus.push({
+ name: 'country',
+ valid: !!bpJson?.org?.location?.country
+ })
+
+ return healthStatus
+}
+
+const getProducersFromDB = async () => {
+ const [producers] = await sequelizeUtil.query(`
+ SELECT *
+ FROM producer
+ ORDER BY rank ASC
+ ;
+ `)
+
+ return producers
+}
+
+module.exports = {
+ getProducers
+}
diff --git a/hapi/src/services/fioProducers.js b/hapi/src/services/fioProducers.js
deleted file mode 100644
index 00605fba..00000000
--- a/hapi/src/services/fioProducers.js
+++ /dev/null
@@ -1,84 +0,0 @@
-const axios = require('axios');
-
-// FIO API endpoint (replace with the correct endpoint if needed)
-const FIO_API_URL = 'https://testnet.fioprotocol.io/v1/chain/get_table_rows';
-
-const getProducers = async () => {
- let producers = [];
- let totalVoteWeight;
- let hasMore = true;
- let nextKey;
-
- try {
- while (hasMore) {
- const response = await axios.post(FIO_API_URL, {
- code: 'fio.system', // Contract name
- table: 'producers', // Table name
- scope: 'fio', // Scope
- limit: 100,
- json: true,
- lower_bound: nextKey
- });
-
- if (response.status !== 200) {
- throw new Error(`Request failed with status code ${response.status}`);
- }
-
- const {
- rows,
- more,
- total_producer_vote_weight: _totalVoteWeight
- } = response.data;
-
- if (!rows) {
- throw new Error('Response data does not contain rows');
- }
-
- hasMore = !!more;
- nextKey = more;
- totalVoteWeight = parseFloat(_totalVoteWeight);
- producers.push(...rows);
- }
- } catch (error) {
- console.error('PRODUCER SYNC ERROR', error.message);
- return;
- }
-
- producers = producers
- .filter(producer => !!producer.is_active)
- .sort((a, b) => {
- if (parseFloat(a.total_votes) > parseFloat(b.total_votes)) {
- return -1;
- }
-
- if (parseFloat(a.total_votes) < parseFloat(b.total_votes)) {
- return 1;
- }
-
- return 0;
- });
-
- producers = producers.map((producer, index) => {
- return {
- id: producer.id,
- owner: producer.owner,
- fio_address: producer.fio_address,
- total_votes: producer.total_votes,
- total_votes_percent: producer.total_votes / totalVoteWeight,
- total_votes_eos: producer.total_votes,
- rank: index + 1,
- producer_public_key: producer.producer_public_key,
- url: producer.url,
- unpaid_blocks: producer.unpaid_blocks,
- last_claim_time: producer.last_claim_time,
- location: producer.location,
- is_active: !!producer.is_active
- };
- });
-
- console.log(producers);
- return producers;
-}
-
-// Call the function to test it
-getProducers();
diff --git a/hapi/src/services/producer.service.js b/hapi/src/services/producer.service.js
index 530b28ce..ec0244ad 100644
--- a/hapi/src/services/producer.service.js
+++ b/hapi/src/services/producer.service.js
@@ -4,6 +4,7 @@ const { hasuraUtil, sequelizeUtil, producerUtil } = require('../utils')
const { eosConfig, workersConfig } = require('../config')
const lacchainService = require('./lacchain.service')
+const fioService = require('./fio.service')
const eosioService = require('./eosio.service')
const nodeService = require('./node.service')
const statsService = require('./stats.service')
@@ -24,7 +25,7 @@ const updateBPJSONs = async (producers = []) => {
const updateProducers = async (producers = []) => {
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]}) {
+ 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]}) {
affected_rows,
returning {
id,
@@ -84,6 +85,9 @@ const syncProducers = async () => {
case eosConfig.knownNetworks.lacchain:
producers = await lacchainService.getProducers()
break
+ case eosConfig.knownNetworks.fio:
+ producers = await fioService.getProducers()
+ break
default:
producers = await eosioService.getProducers()
break
diff --git a/hapi/src/utils/eos.util.js b/hapi/src/utils/eos.util.js
index 0fc0d5f8..51b12f84 100644
--- a/hapi/src/utils/eos.util.js
+++ b/hapi/src/utils/eos.util.js
@@ -218,7 +218,7 @@ const getCurrencyBalance = (code, account, symbol) =>
eosApi.getCurrencyBalance(code, account, symbol)
const getTableRows = options =>
- eosApi.getTableRows({ json: true, ...options })
+ callEosApi('getTableRows', async eosApi => eosApi.getTableRows({ json: true, ...options }))
const getProducerSchedule = () => eosApi.getProducerSchedule({})
diff --git a/hapi/src/utils/producer.util.js b/hapi/src/utils/producer.util.js
index b0a10b23..eb9a393d 100644
--- a/hapi/src/utils/producer.util.js
+++ b/hapi/src/utils/producer.util.js
@@ -126,6 +126,9 @@ const getExpectedRewards = async (producers, totalVotes) => {
case eosConfig.knownNetworks.telos:
rewards = await getTelosRewards(producers)
break
+ case eosConfig.knownNetworks.fio:
+ rewards = await getFioRewards(producers)
+ break
default:
rewards = await getEOSIORewards(producers, totalVotes)
break
@@ -278,6 +281,12 @@ 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/hasura/migrations/default/1719953232361_alter_table_public_producer_add_column_fio_address/down.sql b/hasura/migrations/default/1719953232361_alter_table_public_producer_add_column_fio_address/down.sql
new file mode 100644
index 00000000..f780b3ee
--- /dev/null
+++ b/hasura/migrations/default/1719953232361_alter_table_public_producer_add_column_fio_address/down.sql
@@ -0,0 +1,4 @@
+-- Could not auto-generate a down migration.
+-- Please write an appropriate down migration for the SQL below:
+-- alter table "public"."producer" add column "fio_address" varchar
+-- null unique;
diff --git a/hasura/migrations/default/1719953232361_alter_table_public_producer_add_column_fio_address/up.sql b/hasura/migrations/default/1719953232361_alter_table_public_producer_add_column_fio_address/up.sql
new file mode 100644
index 00000000..411304c3
--- /dev/null
+++ b/hasura/migrations/default/1719953232361_alter_table_public_producer_add_column_fio_address/up.sql
@@ -0,0 +1,2 @@
+alter table "public"."producer" add column "fio_address" varchar
+ null unique;
diff --git a/hasura/migrations/default/1719953258027_alter_table_public_producer_add_column_addresshash/down.sql b/hasura/migrations/default/1719953258027_alter_table_public_producer_add_column_addresshash/down.sql
new file mode 100644
index 00000000..099a3d2c
--- /dev/null
+++ b/hasura/migrations/default/1719953258027_alter_table_public_producer_add_column_addresshash/down.sql
@@ -0,0 +1,4 @@
+-- Could not auto-generate a down migration.
+-- Please write an appropriate down migration for the SQL below:
+-- alter table "public"."producer" add column "addresshash" varchar
+-- null;
diff --git a/hasura/migrations/default/1719953258027_alter_table_public_producer_add_column_addresshash/up.sql b/hasura/migrations/default/1719953258027_alter_table_public_producer_add_column_addresshash/up.sql
new file mode 100644
index 00000000..7b6e801f
--- /dev/null
+++ b/hasura/migrations/default/1719953258027_alter_table_public_producer_add_column_addresshash/up.sql
@@ -0,0 +1,2 @@
+alter table "public"."producer" add column "addresshash" varchar
+ null;
diff --git a/hasura/migrations/default/1719953328741_alter_table_public_producer_add_column_last_bpclaim/down.sql b/hasura/migrations/default/1719953328741_alter_table_public_producer_add_column_last_bpclaim/down.sql
new file mode 100644
index 00000000..588d8388
--- /dev/null
+++ b/hasura/migrations/default/1719953328741_alter_table_public_producer_add_column_last_bpclaim/down.sql
@@ -0,0 +1,4 @@
+-- Could not auto-generate a down migration.
+-- Please write an appropriate down migration for the SQL below:
+-- alter table "public"."producer" add column "last_bpclaim" varchar
+-- null;
diff --git a/hasura/migrations/default/1719953328741_alter_table_public_producer_add_column_last_bpclaim/up.sql b/hasura/migrations/default/1719953328741_alter_table_public_producer_add_column_last_bpclaim/up.sql
new file mode 100644
index 00000000..b4d7099c
--- /dev/null
+++ b/hasura/migrations/default/1719953328741_alter_table_public_producer_add_column_last_bpclaim/up.sql
@@ -0,0 +1,2 @@
+alter table "public"."producer" add column "last_bpclaim" varchar
+ null;
diff --git a/makefile b/makefile
index dac1d2bb..11c7c659 100644
--- a/makefile
+++ b/makefile
@@ -90,7 +90,7 @@ stop:
start:
make start-postgres
- make start-wallet
+# make start-wallet
make start-hapi
# make start-hapi-evm
make start-hasura
diff --git a/webapp/src/components/ProducersChart/index.js b/webapp/src/components/ProducersChart/index.js
index 0c61d5d6..eeff10f6 100644
--- a/webapp/src/components/ProducersChart/index.js
+++ b/webapp/src/components/ProducersChart/index.js
@@ -183,7 +183,7 @@ const CustomTooltip = memo(({ active, payload }) => {
{t('name')}:{' '}
{' '}
- {payload[0].payload.owner}
+ {payload[0].payload.name || payload[0].payload.owner}
{generalConfig.useRewards && (
diff --git a/webapp/src/gql/producer.gql.js b/webapp/src/gql/producer.gql.js
index cd0344a1..8e94134d 100644
--- a/webapp/src/gql/producer.gql.js
+++ b/webapp/src/gql/producer.gql.js
@@ -35,6 +35,7 @@ export const PRODUCERS_QUERY = gql`
type
value
}
+ fio_address
}
}
`
diff --git a/webapp/src/routes/Home/BlockProducerInfo.js b/webapp/src/routes/Home/BlockProducerInfo.js
index 7510fdbc..bae28dfe 100644
--- a/webapp/src/routes/Home/BlockProducerInfo.js
+++ b/webapp/src/routes/Home/BlockProducerInfo.js
@@ -48,7 +48,8 @@ const BlockProducerInfo = ({ t, classes }) => {
return {
logo: data?.bp_json?.org?.branding?.logo_256,
url: data?.url,
- owner: item.producer_name || data.owner,
+ name: data?.fio_address || data?.bp_json?.producer_account_name,
+ owner: item.producer_name || data.owner,
rewards: data.total_rewards || 0,
total_votes_percent: data.total_votes_percent * 100 || 0,
value: 20,
@@ -88,7 +89,7 @@ const BlockProducerInfo = ({ t, classes }) => {
header
lowercase
title={t('currentProducer')}
- value={info.head_block_producer}
+ value={schedule.producers?.find(p => p.owner === info.head_block_producer)?.name || info.head_block_producer}
/>