diff --git a/hapi/src/services/node.service.js b/hapi/src/services/node.service.js index ac558fc4..b8a33ee8 100644 --- a/hapi/src/services/node.service.js +++ b/hapi/src/services/node.service.js @@ -42,7 +42,7 @@ const updateNodes = async (nodes = []) => { } const updateEndpointInfo = async endpoint => { - if (!endpoint.type || !['api', 'ssl'].includes(endpoint.type)) return + if (!endpoint.type || !['api', 'ssl', 'p2p'].includes(endpoint.type)) return const updateMutation = ` mutation ($id: uuid, $head_block_time: timestamptz, $response: jsonb, $updated_at: timestamptz,) { diff --git a/hapi/src/services/producer.service.js b/hapi/src/services/producer.service.js index a7418a09..7ce3168f 100644 --- a/hapi/src/services/producer.service.js +++ b/hapi/src/services/producer.service.js @@ -94,10 +94,7 @@ const syncProducers = async () => { await saveEstimateNextUpdate(new Date()) await syncNodes(producers.slice(0, eosConfig.eosTopLimit)) await syncEndpoints() - - if (!eosConfig.stateHistoryPluginEndpoint) { - await statsService.sync() - } + await statsService.sync() } } @@ -145,6 +142,10 @@ const syncNodes = async producers => { } const syncEndpoints = async () => { + Promise.all([syncP2PEndpoints(), syncAPIEndpoints()]) +} + +const syncAPIEndpoints = async () => { const query = ` { endpoint_aggregate(where: {type: {_in: ["api", "ssl"]}}) { @@ -193,6 +194,40 @@ const syncEndpoints = async () => { await healthCheckHistoryService.saveHealthRegister(endpoints.flat()) } +const syncP2PEndpoints = async () => { + const query = ` + { + endpoint_aggregate(where: {type: {_in: ["p2p"]}}) { + aggregate { + count + } + } + endpoints : endpoint(where: {type: {_in: ["p2p"]}}) { + id + value + type + } + } + ` + const { + endpoints, + endpoint_aggregate: { + aggregate: { count } + } + } = await hasuraUtil.request(query) + + if (!count) return + + await Promise.all(endpoints.map(async endpoint =>{ + const result = await producerUtil.isP2PResponding(endpoint.value) + + endpoint.response = {...result, isWorking: result.status === 'Success'} + endpoint.updated_at = new Date() + + await nodeService.updateEndpointInfo(endpoint) + })) +} + const endpointsHealth = async (endpoints, producerId) => { const checkedList = [] @@ -217,7 +252,8 @@ const endpointsHealth = async (endpoints, producerId) => { endpoint.time = (new Date() - startTime) / 1000 endpoint.response = { status: response?.status, - statusText: response?.statusText + statusText: response?.statusText, + isWorking: response?.status === StatusCodes.OK } endpoint.head_block_time = nodeInfo?.head_block_time || null endpoint.updated_at = new Date() @@ -229,7 +265,7 @@ const endpointsHealth = async (endpoints, producerId) => { checkedList.push({ ...endpoint, producer_id: producerId, - isWorking: Number(endpoint?.response?.status === StatusCodes.OK) + isWorking: Number(endpoint?.response?.isWorking) }) } } diff --git a/hapi/src/utils/eos.util.js b/hapi/src/utils/eos.util.js index acecdc33..0fc0d5f8 100644 --- a/hapi/src/utils/eos.util.js +++ b/hapi/src/utils/eos.util.js @@ -61,11 +61,14 @@ const callWithTimeout = async (promise, ms) => { timeoutID = setTimeout(() => reject(new Error(timeoutMessage)), ms) }) - return Promise.race([promise, timeoutPromise]).then((response) => { - clearTimeout(timeoutID) - - return response - }) + return Promise.race([promise, timeoutPromise]) + .then(response => response) + .catch(error => { + throw error + }) + .finally(() => { + clearTimeout(timeoutID) + }) } const newAccount = async accountName => { @@ -259,6 +262,7 @@ const getProducers = async options => const getInfo = options => eosApi.getInfo(options || {}) module.exports = { + callWithTimeout, newAccount, generateRandomAccountName, getAccount, diff --git a/hapi/src/utils/producer.util.js b/hapi/src/utils/producer.util.js index 996ddb4c..edaff8c6 100644 --- a/hapi/src/utils/producer.util.js +++ b/hapi/src/utils/producer.util.js @@ -1,6 +1,9 @@ const axiosUtil = require('./axios.util') const eosUtil = require('./eos.util') const hasuraUtil = require('./hasura.util') +const net = require('node:net') +const os = require('node:os') + const { eosConfig } = require('../config') const getUrlStatus = async (url, api = '') => { @@ -8,7 +11,9 @@ const getUrlStatus = async (url, api = '') => { url = url.replace(urlRegex, '') try { - const response = await axiosUtil.instance.get(`${url}${api}`, { timeout: 30000 }) + const response = await axiosUtil.instance.get(`${url}${api}`, { + timeout: 30000 + }) return response } catch (error) { @@ -16,6 +21,42 @@ const getUrlStatus = async (url, api = '') => { } } +const isP2PResponding = async (endpoint) => { + const splitted = endpoint?.split(':') || {} + const { 0: host, 1: port } = splitted + + if (splitted.length !== 2 || !host || !port) + return { status: 'Failed', statusText: 'Invalid endpoint format' } + + const isResponding = new Promise((resolve, _) => { + const client = net.createConnection({ host, port }, () => { + client.destroy() + resolve({ status: 'Success', statusText: 'Connection established' }) + }) + + client.on('error', (err) => { + let errorMessage = '' + + switch (Math.abs(err?.errno)) { + case os.constants.errno.ECONNREFUSED: + errorMessage = 'Connection refused' + break + case os.constants.errno.ETIMEDOUT: + errorMessage = 'Connection timeout exceeded' + break + default: + errorMessage = 'Connection error' + } + + resolve({ status: 'Failed', statusText: errorMessage }) + }) + }) + + return eosUtil.callWithTimeout(isResponding, 60000).catch((_) => { + return { status: 'Failed', statusText: 'Connection timeout exceeded' } + }) +} + const getNodeInfo = async (url, api = '/v1/chain/get_info') => { const response = await getUrlStatus(url, api) @@ -31,7 +72,8 @@ const getSupportedAPIs = async (api) => { try { const response = await axiosUtil.instance.get( - `${api}/v1/node/get_supported_apis`, { timeout: 30000 } + `${api}/v1/node/get_supported_apis`, + { timeout: 30000 } ) supportedAPIs = response.data?.apis @@ -263,6 +305,7 @@ const jsonParse = (string) => { } module.exports = { + isP2PResponding, getNodeInfo, getEndpoints, getExpectedRewards, diff --git a/hapi/src/workers/producers.worker.js b/hapi/src/workers/producers.worker.js index 99337e42..36863f46 100644 --- a/hapi/src/workers/producers.worker.js +++ b/hapi/src/workers/producers.worker.js @@ -66,7 +66,6 @@ const start = async () => { } if (eosConfig.stateHistoryPluginEndpoint) { - run('SYNC STATS INFO', statsService.sync, workersConfig.syncStatsInterval) run('SYNC BLOCK HISTORY', stateHistoryPluginService.init) run('SYNC MISSED BLOCKS', missedBlocksService.syncMissedBlocks) run('SYNC MISSED BLOCKS PER PRODUCER', statsService.getCurrentMissedBlock) diff --git a/webapp/src/components/EndpointsTable/index.js b/webapp/src/components/EndpointsTable/index.js index 9f05820f..a55b589e 100644 --- a/webapp/src/components/EndpointsTable/index.js +++ b/webapp/src/components/EndpointsTable/index.js @@ -52,11 +52,13 @@ const EndpointsTable = ({ producers }) => { const getStatus = endpoint => { if (endpoint.response.status === undefined) return + if (endpoint.response?.isWorking) { + return !endpoint.head_block_time || isSynchronized(endpoint) + ? 'greenLight' + : 'timerOff' + } + switch (Math.floor(endpoint.response?.status / 100)) { - case 2: - return !endpoint.head_block_time || isSynchronized(endpoint) - ? 'greenLight' - : 'timerOff' case 4: case 5: return 'yellowLight' @@ -77,11 +79,9 @@ const EndpointsTable = ({ producers }) => { key={`${producer?.name}-${endpointType}-${index}`} > {endpoint.value} - {endpointType !== 'p2p' && ( - - - - )} + + + )) )} diff --git a/webapp/src/components/EndpointsTextList/index.js b/webapp/src/components/EndpointsTextList/index.js index 9c11bbd7..4f70f6cf 100644 --- a/webapp/src/components/EndpointsTextList/index.js +++ b/webapp/src/components/EndpointsTextList/index.js @@ -17,7 +17,7 @@ const EndpointsTextList = ({ type }) => { // Gets all responding endpoints useEffect(() => { setPagination(prev => ({ ...prev, limit: null })) - handleFilter(type !== 'p2p') + handleFilter(true) }, [type, handleFilter, setPagination]) useEffect(() => { diff --git a/webapp/src/components/HealthCheck/HealthCheckInfo.js b/webapp/src/components/HealthCheck/HealthCheckInfo.js index e86fc776..b03d4555 100644 --- a/webapp/src/components/HealthCheck/HealthCheckInfo.js +++ b/webapp/src/components/HealthCheck/HealthCheckInfo.js @@ -9,7 +9,7 @@ const HealthCheckInfo = ({ healthCheck }) => { return ( <> - {t('status')}: {healthCheck.response?.status || t('error')} + {t('status')}: {t(healthCheck.response?.status) || t('error')} {t('response')}: {t(healthCheck.response?.statusText || 'noResponse')} diff --git a/webapp/src/components/NoResults/styles.js b/webapp/src/components/NoResults/styles.js index ca2bc707..1164aef3 100644 --- a/webapp/src/components/NoResults/styles.js +++ b/webapp/src/components/NoResults/styles.js @@ -6,5 +6,6 @@ export default (theme) => ({ marginBottom: theme.spacing(2), padding: theme.spacing(2), alignItems: 'center', + boxShadow: '0px 1px 5px rgba(0, 0, 0, 0.15) !important', }, }) diff --git a/webapp/src/components/NodeCard/EndpointsChips.js b/webapp/src/components/NodeCard/EndpointsChips.js index 602dee90..0cf7ff39 100644 --- a/webapp/src/components/NodeCard/EndpointsChips.js +++ b/webapp/src/components/NodeCard/EndpointsChips.js @@ -72,7 +72,6 @@ const EndpointsChips = ({ node }) => { ) } - const checkedEndpoints = node.endpoints.filter(endpoint => endpoint.type !== 'p2p') const status = { totalEndpoints: 0, failingEndpoints: [], @@ -80,14 +79,14 @@ const EndpointsChips = ({ node }) => { isLoading: false } - for (const endpoint of checkedEndpoints){ + for (const endpoint of node.endpoints){ if (endpoint?.response?.status === undefined) { status.isLoading = true break } - if (endpoint?.response?.status !== 200) { + if (!endpoint?.response?.isWorking) { status.failingEndpoints.push(endpoint.type) } @@ -99,7 +98,7 @@ const EndpointsChips = ({ node }) => { <>
{t('endpoints')} - {!!checkedEndpoints.length && } + {!!node.endpoints.length && }
{ diff --git a/webapp/src/gql/producer.gql.js b/webapp/src/gql/producer.gql.js index 7d522f64..1c204798 100644 --- a/webapp/src/gql/producer.gql.js +++ b/webapp/src/gql/producer.gql.js @@ -220,7 +220,7 @@ export const FASTEST_ENDPOINTS_QUERY = gql`query($today: date){ }` export const HISTORY_ENDPOINTS_BY_PRODUCER_QUERY = gql`query($id: Int){ - endpoints: check_history_by_endpoint(order_by: [{value: asc},{date: asc}], where: {producer_id: {_eq: $id}}) { + endpoints: check_history_by_endpoint(order_by: [{value: asc},{date: asc}], where: {producer_id: {_eq: $id}}, distinct_on: [value]) { value date avg_time diff --git a/webapp/src/hooks/customHooks/useEndpointsState.js b/webapp/src/hooks/customHooks/useEndpointsState.js index b321f560..ee1f8422 100644 --- a/webapp/src/hooks/customHooks/useEndpointsState.js +++ b/webapp/src/hooks/customHooks/useEndpointsState.js @@ -45,19 +45,17 @@ const useEndpointsState = () => { }, [data]) const handleFilter = useCallback(value => { - const filter = value - ? { response: { _contains: { status: 200 } } } - : { value: { _gt: '' } } + const filter = value + ? { response: { _contains: { isWorking: true } } } + : { value: { _gt: '' } } - setPagination(prev => ({ - ...prev, - page: 1, - where: { ...prev.where, nodes: { endpoints: filter } }, - endpointFilter: value - ? { _or: [{ type: { _eq: 'p2p' } }, filter] } - : undefined, - })) - }, [setPagination]) + setPagination(prev => ({ + ...prev, + page: 1, + where: { ...prev.where, nodes: { endpoints: filter } }, + endpointFilter: value ? filter : undefined, + })) + }, [setPagination]) return [ { loading, pagination, producers: items, filters }, diff --git a/webapp/src/language/en.json b/webapp/src/language/en.json index 4f197a50..8ab6f267 100644 --- a/webapp/src/language/en.json +++ b/webapp/src/language/en.json @@ -290,8 +290,8 @@ "cpuBenchmark": "CPU Benchmark Average", "isFull": "Is full", "supportedApis": "Supported APIs", - "allWorking": "All API and SSL endpoints are responding", - "noneWorking": "No API or SSL endpoints are responding", + "allWorking": "All endpoints are responding", + "noneWorking": "No endpoints are responding", "endpointPlural": "Endpoints not responding are the", "endpointSingular": "Endpoint not responding is the" }, diff --git a/webapp/src/language/es.json b/webapp/src/language/es.json index 08187e58..fff5d015 100644 --- a/webapp/src/language/es.json +++ b/webapp/src/language/es.json @@ -297,8 +297,8 @@ "cpuBenchmark": "Promedio del Benchmark de CPU", "isFull": "Es completo", "supportedApis": "APIs Soportadas", - "allWorking": "Todos los endpoints API y SSL están respondiendo", - "noneWorking": "Ningún endpoint API o SSL está respondiendo", + "allWorking": "Todos los endpoints están respondiendo", + "noneWorking": "Ningún endpoint está respondiendo", "endpointPlural": "Los endpoints que no responden son los", "endpointSingular": "El endpoint que no responde es el" }, @@ -392,7 +392,13 @@ "Payment Required": "La solicitud requiere un pago", "Forbidden": "La solicitud está prohibida", "Request Timeout": "Tiempo de la solicitud superado", - "Too Many Request": "Demasiadas solicitudes" + "Too Many Request": "Demasiadas solicitudes", + "Connection refused": "Conexión rechazada", + "Connection timeout exceeded": "Tiempo de espera de conexión excedido", + "Connection error": "Error de conexión", + "Connection established": "Conexión establecida", + "Success": "Éxitosa", + "Failed": "Fallida" }, "nodeSearchComponent": { "title": "Buscar Nodo", diff --git a/webapp/src/routes/EndpointsList/index.js b/webapp/src/routes/EndpointsList/index.js index 2ecaee41..57c8eecf 100644 --- a/webapp/src/routes/EndpointsList/index.js +++ b/webapp/src/routes/EndpointsList/index.js @@ -96,7 +96,9 @@ const EndpointsList = () => { {!!producers?.length ? ( ) : ( - +
+ +
)} {pagination.pages > 1 && (
diff --git a/webapp/src/routes/EndpointsList/styles.js b/webapp/src/routes/EndpointsList/styles.js index 70c1a80d..739ec412 100644 --- a/webapp/src/routes/EndpointsList/styles.js +++ b/webapp/src/routes/EndpointsList/styles.js @@ -46,4 +46,9 @@ export default (theme) => ({ cardShadow: { boxShadow: '0px 1px 5px rgba(0, 0, 0, 0.15) !important', }, + noShadow: { + '& .MuiPaper-root': { + boxShadow: 'none !important' + } + } })