Compare commits

...

101 Commits

Author SHA1 Message Date
Dark Sun d3290d5b1b fix 2023-12-19 22:16:33 +03:00
Dark Sun 576ba19e76 fix 2023-12-19 22:12:26 +03:00
Dark Sun 09fb3c0eba update 2023-12-19 22:08:38 +03:00
Dark Sun 2303fcea9e rollback 2023-12-18 22:22:57 +03:00
Dark Sun e0bf1b534b update 2023-12-18 22:01:35 +03:00
Dark Sun 4481ebcbc0 update 2023-12-18 18:42:44 +03:00
Dark Sun f3ab699f83 update 2023-12-18 17:31:42 +03:00
Dark Sun 7bae572ec9 update 2023-12-18 14:16:00 +03:00
Dark Sun 5f1fb9490c push 2023-12-18 13:50:48 +03:00
Dark Sun 33ec53dc27 update 2023-12-09 20:29:20 +03:00
Dark Sun ad3ca3e988 update 2023-12-08 23:53:35 +03:00
Dark Sun a02917b922 update indexes 2023-12-08 23:25:38 +03:00
Dark Sun c49e6c20e7 update 2023-12-07 23:27:30 +03:00
Dark Sun e557e7d618 update 2023-12-07 17:48:31 +03:00
Dark Sun d84642e8be push 2023-12-07 16:58:28 +03:00
Dark Sun c0c857bc5b update 2023-12-07 16:55:00 +03:00
Dark Sun 20856ecec4 update 2023-12-07 16:48:37 +03:00
Dark Sun c9e821d2f4 update 2023-12-07 16:23:42 +03:00
Dark Sun fe48b146b3 fix typo 2023-12-07 14:39:24 +03:00
Dark Sun 1ef1514b15 update template 2023-12-07 14:02:05 +03:00
Igor Lins e Silva 32145b6e1c Update README.md 2023-10-10 21:21:35 -03:00
Bruno Leal 90d489b600 fix: repair fork index not found 2023-09-27 16:54:35 -03:00
Igor Lins e Silva 69646d9660 cleanup 2023-09-25 21:56:41 -03:00
Bruno Leal fb2fc20d1d Merge branch 'main' of https://github.com/eosrio/hyperion-history-api into main 2023-09-25 15:07:17 -03:00
Bruno Leal b7e17f9dc7 fix: fix progress status by amout of lines not chunks 2023-09-25 15:07:00 -03:00
Igor Lins e Silva b94f99d552 Merge pull request #142 from eosrio/large-tx-fix
Large tx-sig fix
2023-09-25 01:33:31 -03:00
Igor Lins e Silva 76807c6c86 cleanup 2023-09-25 01:29:53 -03:00
Igor Lins e Silva 69bcbaad18 flow control 2023-09-25 01:25:40 -03:00
Igor Lins e Silva e5d0126d08 Revert "include amqp-connection-manager"
This reverts commit 0579da66
2023-09-25 01:05:33 -03:00
Igor Lins e Silva e804aff53c log cleanup 2023-09-25 01:03:35 -03:00
Igor Lins e Silva 5d3b248bcd Revert "include amqp-connection-manager"
This reverts commit 4dcb607fcc.
2023-09-25 01:03:04 -03:00
Igor Lins e Silva d1eca214ae log cleanup 2023-09-25 00:54:41 -03:00
Igor Lins e Silva 4dcb607fcc include amqp-connection-manager 2023-09-25 00:45:23 -03:00
Igor Lins e Silva 0579da6613 include amqp-connection-manager 2023-09-25 00:29:07 -03:00
Igor Lins e Silva b8faf2ef70 package updates 2023-09-24 19:33:55 -03:00
Igor Lins e Silva bc98cecb52 send signatures on payload 2023-09-24 19:30:40 -03:00
Igor Lins e Silva 29ed9d372d add logs 2023-09-24 19:14:52 -03:00
Igor Lins e Silva d259008cb4 add logs 2023-09-24 19:13:08 -03:00
Igor Lins e Silva ce6f307bbe add logs 2023-09-24 18:32:15 -03:00
Igor Lins e Silva 44062fbe0e add logs 2023-09-24 18:28:07 -03:00
Igor Lins e Silva 94547ce8fd add logs 2023-09-24 18:02:49 -03:00
Igor Lins e Silva c59005bba0 make ingest clients public 2023-09-23 14:13:03 -03:00
Igor Lins e Silva bee1948f19 make ingest clients public 2023-09-23 14:12:45 -03:00
Bruno Leal 616133dab8 fix: repair chunk file 2023-09-13 18:34:04 -03:00
Igor Lins e Silva 28ade29d00 implement v2/stats/get_api_usage 2023-09-01 00:45:25 -03:00
Igor Lins e Silva 6fe3acce7e update v1/chain/abi_bin_to_json interface 2023-06-23 12:18:43 -03:00
Igor Lins e Silva 572b2b11e5 update socket.io to 4.7.0 2023-06-23 12:16:00 -03:00
Igor Lins e Silva 2fbee90b73 update v1/chain/abi_json_to_bin interface 2023-06-23 12:13:30 -03:00
Igor Lins e Silva e79cad82a4 update socket.io 2023-06-11 03:08:29 -03:00
Igor Lins e Silva e087c2971e Merge branch 'main' of https://github.com/eosrio/hyperion-history-api 2023-06-11 03:04:59 -03:00
Igor Lins e Silva 74072bd6cb prevent a conflict between disable_reading and the repair mode 2023-06-11 03:04:15 -03:00
Igor Lins e Silva ee3bf26263 package update 2023-05-26 04:54:38 +00:00
Igor Lins e Silva 367f37e989 hyperion repair cli v0.2.2 2023-05-26 04:53:07 +00:00
Igor Lins e Silva 65698c344d on-demand delta filtering for streams 2023-05-20 08:56:46 +00:00
Igor Lins e Silva b5f17b0133 cleanup 2023-05-20 07:26:07 +00:00
Igor Lins e Silva 5b65bd7d7b cleanup 2023-05-20 07:22:21 +00:00
Igor Lins e Silva b289036b0c - multiple streaming improvements
- stream-client v2.0.0 compatibility
- improved transitions on high volume streams
- added api.stream_scroll_limit (default -1 -> unlimited)
specify the maximum number of documents that can be requested during a history request in the streaming api
- added api.stream_scroll_batch (default 500)
specify the number of documents per streamed history request
2023-05-20 07:20:46 +00:00
Igor Lins e Silva a2a040ab55 allow date formats with space between date and time, replace %20 with T 2023-05-20 04:53:58 +00:00
Igor Lins e Silva 480e29f278 don't sort producers on get_schedule response 2023-05-15 16:19:27 -03:00
Igor Lins e Silva ce9dc8524e wait for ES to be available on api start 2023-05-11 06:16:23 -03:00
Igor Lins e Silva bc2d25394c wait for nodeos to be available on api start, prevent fastify plugin timeout 2023-05-11 03:01:29 -03:00
Igor Lins e Silva 88493248e1 wait for services to be available on indexer start 2023-05-11 02:10:35 -03:00
Igor Lins e Silva 7ceb009d31 wait for nodeos to be available on indexer start 2023-05-11 01:46:53 -03:00
Igor Lins e Silva 81606e6252 add indexing of proposed schedules on dedicated chain-schedule-* index, improve api endpoint (v2/history/get_schedule) 2023-05-08 04:42:55 -03:00
Igor Lins e Silva a9fb4b5b80 cleanup 2023-05-08 02:25:58 -03:00
Igor Lins e Silva f426a3dd4e use default control port 7002 if not defined on connections.json 2023-05-08 01:50:02 -03:00
Igor Lins e Silva 99c2772781 ignore repair tool build files 2023-05-08 01:42:44 -03:00
Igor Lins e Silva 8876213ecc update repair tool 2023-05-08 01:41:35 -03:00
Igor Lins e Silva 0798b08e2e fix lib event forwarding on shared api hosts 2023-05-07 19:42:58 -03:00
Igor Lins e Silva eb6fb9827a v18 is still supported 2023-05-05 08:31:07 -03:00
Igor Lins e Silva 44e487272a v3.3.9-5
- Node.js 20 compatibility
- added last_indexed_block/time on API responses
- fix index progress monitoring
2023-05-05 08:15:20 -03:00
Igor Lins e Silva bbbbf91f79 Update hyp-repair script and other deserializer cleanups 2023-05-05 03:54:04 +00:00
Igor Lins e Silva cd277b4f12 merge repair tool 2023-05-04 16:49:26 -03:00
Igor Lins e Silva 16e870135b merge repair tool 2023-05-04 16:48:44 -03:00
Igor Lins e Silva aa989dc5e6 merge repair tool 2023-05-04 16:45:11 -03:00
Igor Lins e Silva 4555112b9c merge repair tool 2023-05-04 16:24:32 -03:00
Igor Lins e Silva 89f0bf586b update packages 2023-05-04 05:48:10 -03:00
Igor Lins e Silva be13e81437 fix resource handler on deltas 2023-05-04 05:45:29 -03:00
Igor Lins e Silva 70bab7545b Merge pull request #140 from ankh2054/patch-1
Update fix-missing-blocks.py
2023-04-10 19:52:28 -03:00
Charles Holtzkampf 3c1d9fcc2f Update fix-missing-blocks.py
Updated to work with all versions of Hyperion
2023-03-27 11:18:11 +01:00
Igor Lins e Silva 498bc8ec75 Update example.config.json
remove `bypass_index_map`
2023-03-01 18:17:02 -03:00
Igor Lins e Silva 58f40531fe remove deprecated index mapping 2023-03-01 18:15:55 -03:00
Igor Lins e Silva 57e4492556 v3.3.9-4
- patch for https://github.com/advisories/GHSA-rc47-6667-2j5j
2023-03-01 18:09:02 -03:00
Igor Lins e Silva 832da15808 fix v1 get_actions to match receipts var 2023-03-01 18:02:45 -03:00
Igor Lins e Silva 4107daa621 ignore example chain config 2023-01-26 20:26:45 -03:00
Igor Lins e Silva b3adad54d8 Update package.json 2023-01-26 20:06:14 -03:00
Igor Lins e Silva 176932a78c avoid failed tsc builds 2023-01-26 20:05:05 -03:00
Igor Lins e Silva 3a5997ca98 set default health cache to 10s 2023-01-26 13:56:54 -03:00
Igor Lins e Silva ea8dd6d3f7 add cache to cat/health 2023-01-26 13:54:58 -03:00
Igor Lins e Silva 8c0be344a6 fix notified list on simple action output 2023-01-26 13:42:47 -03:00
Igor Lins e Silva fbd5904e25 fix act_digest output on v1 2023-01-26 12:57:15 -03:00
Igor Lins e Silva 05b7ab0f3a v.3.3.9-2 2023-01-25 22:11:20 -03:00
Igor Lins e Silva da34871f09 add settings.use_global_agent as optional
- global-agent was interfering with default self-signed certificates on secure elasticsearch setup
2023-01-25 22:09:00 -03:00
Igor Lins e Silva fa3c2cb878 Update hyp-config.ts 2023-01-25 20:49:14 -03:00
Igor Lins e Silva ea5f6d0be0 fix backup directory creation 2023-01-25 20:43:17 -03:00
Igor Lins e Silva 88598ea551 remove temp files 2023-01-25 20:43:05 -03:00
Igor Lins e Silva b06a422b54 Update .gitignore 2023-01-25 20:42:54 -03:00
Igor Lins e Silva 29ff9824c8 Update .gitignore 2023-01-25 20:42:00 -03:00
Igor Lins e Silva f2f0d0f41c fix paths 2023-01-25 20:38:35 -03:00
Igor Lins e Silva 7830b11eaf backport "hyp-config connections init" command 2023-01-25 20:26:46 -03:00
Igor Lins e Silva 55cb7ca74a re-include @timestamp 2023-01-20 03:10:48 -03:00
75 changed files with 5062 additions and 4820 deletions
+11 -5
View File
@@ -68,8 +68,8 @@ helpers/**/*.js.map
interfaces/**/*.js
interfaces/**/*.js.map
modules/**/*.js
modules/**/*.js.map
#modules/**/*.js
#modules/**/*.js.map
addons/**/*.js
addons/**/*.js.map
@@ -77,9 +77,6 @@ addons/**/*.js.map
scripts/hpm.js
scripts/hpm.js.map
scripts/hyp-config.js
scripts/hyp-config.js.map
plugins/.state.json
plugins/repos
plugins/*.js
@@ -91,3 +88,12 @@ docker/rabbitmq/data
docker/eosio/data
configuration_backups
scripts/hyp-config.js
scripts/hyp-config.js.map
scripts/hyp-repair.js
scripts/hyp-repair.js.map
scripts/repair-cli/*.js
scripts/repair-cli/*.js.map
+1 -1
View File
@@ -91,7 +91,7 @@ to collect action traces and state deltas. Provides data via websocket to the in
#### 2.7 Hyperion Stream Client (optional)
Web and Node.js client for real-time streaming on enabled hyperion
providers. [Documentation](https://hyperion.docs.eosrio.io/stream_client/)
providers. [Documentation](https://hyperion.docs.eosrio.io/dev/stream_client/)
#### 2.8 Hyperion Plugins (optional)
+5 -5
View File
@@ -28,19 +28,19 @@ export class CacheManager {
setInterval(() => {
try {
// remove expired entries
let removeCount = 0;
// let removeCount = 0;
const now = Date.now();
this.v1Caches.forEach((pathCacheMap: Map<string, CachedEntry>, pathKey: string) => {
pathCacheMap.forEach((cache: CachedEntry, entryKey: string, map: Map<string, CachedEntry>) => {
if (cache.exp < now) {
map.delete(entryKey);
removeCount++;
// removeCount++;
}
});
});
if (removeCount > 0) {
console.log(`${removeCount} expired cache entries removed`);
}
// if (removeCount > 0) {
// console.log(`${removeCount} expired cache entries removed`);
// }
} catch (e) {
console.log(e);
}
+202 -34
View File
@@ -2,7 +2,8 @@ import {createHash} from "crypto";
import * as _ from "lodash";
import {FastifyInstance, FastifyReply, FastifyRequest, FastifySchema, HTTPMethods} from "fastify";
import got from "got";
import {checkFilter, hLog} from "../../helpers/common_functions";
import {checkDeltaFilter, checkFilter, hLog} from "../../helpers/common_functions";
import {Socket} from "socket.io";
const deltaQueryFields = ['code', 'table', 'scope', 'payer'];
@@ -15,51 +16,141 @@ export async function streamPastDeltas(fastify: FastifyInstance, socket, data) {
deltaQueryFields.forEach(f => {
addTermMatch(data, search_body, f);
});
const onDemandDeltaFilters = [];
if (data.filters.length > 0) {
data.filters.forEach(f => {
if (f.field && f.value) {
if (f.field.startsWith('@') && !f.field.startsWith('data')) {
const _q = {};
_q[f.field] = f.value;
search_body.query.bool.must.push({'term': _q});
} else {
onDemandDeltaFilters.push(f);
}
}
});
}
const responseQueue = [];
let counter = 0;
let total = 0;
let totalFiltered = 0;
let longScroll = false;
// console.log(JSON.stringify(search_body, null, 2));
const init_response = await fastify.elastic.search({
index: fastify.manager.chain + '-delta-*',
scroll: '30s',
size: 20,
size: fastify.manager.config.api.stream_scroll_batch || 500,
body: search_body,
});
const scrollLimit = fastify.manager.config.api.stream_scroll_limit;
if (scrollLimit && scrollLimit !== -1 && init_response.body.hits.total.value > scrollLimit) {
const errorMsg = `Requested ${init_response.body.hits.total.value} deltas, limit is ${scrollLimit}.`;
socket.emit('message', {
reqUUID: socket.data.reqUUID,
type: 'delta_trace', mode: 'history', messages: [],
error: errorMsg
});
return {status: false, error: errorMsg};
}
if (init_response.body.hits.total.value > 10000) {
total = init_response.body.hits.total.value;
longScroll = true;
hLog(`Attention! Long scroll (deltas) is running!`);
}
// emit first block
if (init_response.body.hits.hits.length > 0) {
emitTraceInit(socket, init_response.body.hits.hits[0]._source.block_num, init_response.body.hits.total.value);
}
responseQueue.push(init_response);
let lastTransmittedBlock = 0;
let pendingScrollId = '';
while (responseQueue.length) {
let filterCount = 0;
const {body} = responseQueue.shift();
pendingScrollId = body['_scroll_id'];
const enqueuedMessages = [];
counter += body['hits']['hits'].length;
for (const doc of body['hits']['hits']) {
let allow = false;
if (onDemandDeltaFilters.length > 0) {
allow = onDemandDeltaFilters.every(filter => {
return checkDeltaFilter(filter, doc._source);
});
} else {
allow = true;
}
if (allow) {
enqueuedMessages.push(doc._source);
} else {
filterCount++;
}
// set last block
if (doc._source.block_num > lastTransmittedBlock) {
lastTransmittedBlock = doc._source.block_num;
}
}
totalFiltered += filterCount;
if (socket.connected) {
socket.emit('message', {
type: 'delta_trace',
mode: 'history',
messages: body['hits']['hits'].map(doc => doc._source),
});
if (enqueuedMessages.length > 0) {
socket.emit('message', {
reqUUID: socket.data.reqUUID,
type: 'delta_trace',
mode: 'history',
messages: enqueuedMessages,
filtered: filterCount
});
}
} else {
hLog('LOST CLIENT');
break;
}
if (longScroll) {
hLog(`Progress: ${counter + totalFiltered}/${total}`);
}
if (body['hits'].total.value === counter) {
hLog(`${counter} past deltas streamed to ${socket.id}`);
hLog(`${counter} past deltas streamed to ${socket.id} (${totalFiltered} filtered)`);
break;
}
const next_response = await fastify.elastic.scroll({
body: {
scroll_id: body['_scroll_id'],
scroll: '30s'
}
body: {scroll_id: body['_scroll_id'], scroll: '30s'}
});
responseQueue.push(next_response);
}
// destroy scroll context
await fastify.elastic.clearScroll({scroll_id: pendingScrollId});
return {status: true, lastTransmittedBlock};
}
export function emitTraceInit(socket: Socket, firstBlock: number, totalResults: number) {
socket.emit('message', {
reqUUID: socket.data.reqUUID,
type: 'trace_init',
mode: 'history',
first_block: firstBlock,
results: totalResults
});
}
export async function streamPastActions(fastify: FastifyInstance, socket, data) {
const search_body = {
query: {bool: {must: []}},
sort: {global_sequence: 'asc'},
};
const search_body = {query: {bool: {must: []}}, sort: {global_sequence: 'asc'}};
await addBlockRangeOpts(data, search_body, fastify);
if (data.account !== '') {
search_body.query.bool.must.push({
bool: {
@@ -96,18 +187,50 @@ export async function streamPastActions(fastify: FastifyInstance, socket, data)
const responseQueue = [];
let counter = 0;
let total = 0;
let totalFiltered = 0;
let longScroll = false;
const init_response = await fastify.elastic.search({
index: fastify.manager.chain + '-action-*',
scroll: '30s',
size: 20,
size: fastify.manager.config.api.stream_scroll_batch || 500,
body: search_body,
});
const scrollLimit = fastify.manager.config.api.stream_scroll_limit;
if (scrollLimit && scrollLimit !== -1 && init_response.body.hits.total.value > scrollLimit) {
const errorMsg = `Requested ${init_response.body.hits.total.value} actions, limit is ${scrollLimit}.`;
socket.emit('message', {
reqUUID: socket.data.reqUUID,
type: 'action_trace', mode: 'history', messages: [],
error: `Requested ${init_response.body.hits.total.value} actions, limit is ${scrollLimit}.`
});
return {status: false, error: errorMsg};
}
if (init_response.body.hits.total.value > 10000) {
total = init_response.body.hits.total.value;
longScroll = true;
hLog(`Attention! Long scroll (actions) is running!`);
}
// emit first block
if (init_response.body.hits.hits.length > 0) {
emitTraceInit(socket, init_response.body.hits.hits[0]._source.block_num, init_response.body.hits.total.value);
}
responseQueue.push(init_response);
let lastTransmittedBlock = 0;
let pendingScrollId = '';
while (responseQueue.length) {
let filterCount = 0;
const {body} = responseQueue.shift();
pendingScrollId = body['_scroll_id'];
const enqueuedMessages = [];
counter += body['hits']['hits'].length;
for (const doc of body['hits']['hits']) {
let allow = false;
if (onDemandFilters.length > 0) {
@@ -119,32 +242,55 @@ export async function streamPastActions(fastify: FastifyInstance, socket, data)
}
if (allow) {
enqueuedMessages.push(doc._source);
} else {
filterCount++;
}
// set last block
if (doc._source.block_num > lastTransmittedBlock) {
lastTransmittedBlock = doc._source.block_num;
}
}
totalFiltered += filterCount;
if (socket.connected) {
socket.emit('message', {type: 'action_trace', mode: 'history', messages: enqueuedMessages});
if (enqueuedMessages.length > 0) {
socket.emit('message', {
reqUUID: socket.data.reqUUID,
type: 'action_trace',
mode: 'history',
messages: enqueuedMessages,
filtered: filterCount
});
}
} else {
hLog('LOST CLIENT');
break;
}
if (longScroll) {
hLog(`Progress: ${counter + totalFiltered}/${total}`);
}
if (body['hits'].total.value === counter) {
hLog(`${counter} past actions streamed to ${socket.id}`);
hLog(`${counter} past actions streamed to ${socket.id} (${totalFiltered} filtered)`);
break;
}
if (init_response.body.hits.total.value < 1000) {
const next_response = await fastify.elastic.scroll({
body: {scroll_id: body['_scroll_id'], scroll: '30s'}
});
responseQueue.push(next_response);
} else {
hLog('Request too large!');
socket.emit('message', {type: 'action_trace', mode: 'history', messages: []});
}
const next_response = await fastify.elastic.scroll({
body: {scroll_id: body['_scroll_id'], scroll: '30s'}
});
responseQueue.push(next_response);
}
// destroy scroll context
await fastify.elastic.clearScroll({scroll_id: pendingScrollId});
return {status: true, lastTransmittedBlock};
}
export function addTermMatch(data, search_body, field) {
if (data[field] !== '*' && data[field] !== '') {
if (data[field] && data[field] !== '*' && data[field] !== '') {
const termQuery = {};
termQuery[field] = data[field];
search_body.query.bool.must.push({'term': termQuery});
@@ -217,6 +363,8 @@ export function extendResponseSchema(responseProps: any) {
cached: {type: "boolean"},
hot_only: {type: "boolean"},
lib: {type: "number"},
last_indexed_block: {type: "number"},
last_indexed_block_time: {type: "string"},
total: {
type: "object",
properties: {
@@ -367,7 +515,8 @@ function bigint2Milliseconds(input: bigint) {
const defaultRouteCacheMap = {
get_resource_usage: 3600,
get_creator: 3600 * 24
get_creator: 3600 * 24,
health: 10
}
export async function timedQuery(
@@ -384,16 +533,31 @@ export async function timedQuery(
request.method === 'POST' ? request.body : request.query
);
let lastIndexedBlockNumber = null;
let lastIndexedBlockTime = null;
const lastIndexedBlock = await fastify.redis.get(`${fastify.manager.chain}:last_idx_block`);
if (lastIndexedBlock) {
const arr = lastIndexedBlock.split("@");
if (arr.length === 2) {
lastIndexedBlockNumber = parseInt(arr[0], 10);
lastIndexedBlockTime = arr[1];
}
}
if (cachedResponse && !request.query["ignoreCache"]) {
// add cached query time
cachedResponse['query_time_ms'] = bigint2Milliseconds(process.hrtime.bigint() - t0);
if (lastIndexedBlock) {
cachedResponse['last_indexed_block'] = lastIndexedBlockNumber;
cachedResponse['last_indexed_block_time'] = lastIndexedBlockTime;
}
return cachedResponse;
}
// call query function
const response = await queryFunction(fastify, request);
// save response to cash
// save response to cache
if (hash) {
let EX = null;
if (defaultRouteCacheMap[route]) {
@@ -405,6 +569,10 @@ export async function timedQuery(
// add normal query time
if (response) {
response['query_time_ms'] = bigint2Milliseconds(process.hrtime.bigint() - t0);
if (lastIndexedBlock) {
response['last_indexed_block'] = lastIndexedBlockNumber;
response['last_indexed_block_time'] = lastIndexedBlockTime;
}
return response;
} else {
return {};
@@ -416,7 +584,7 @@ export function chainApiHandler(fastify: FastifyInstance) {
// check cache
const [cachedData, hash, path] = fastify.cacheManager.getCachedData(request);
if (cachedData) {
// console.log('cache hit:', path, hash);
console.log('cache hit:', path, hash);
reply.headers({'hyperion-cached': true}).send(cachedData);
} else {
// call actual request
+11 -12
View File
@@ -3,17 +3,16 @@ import {Api} from "eosjs/dist";
import fp from "fastify-plugin";
export default fp(async (fastify: FastifyInstance, options: FastifyPluginOptions): Promise<void> => {
const rpc = fastify.manager.nodeosJsonRPC;
const chain_data = await rpc.get_info();
const api = new Api({
rpc,
signatureProvider: null,
chainId: chain_data.chain_id,
textDecoder: new TextDecoder(),
textEncoder: new TextEncoder(),
});
fastify.decorate('eosjs', {api, rpc});
const rpc = fastify.manager.nodeosJsonRPC;
const api = new Api({
rpc,
signatureProvider: null,
chainId: options.chain_id,
textDecoder: new TextDecoder(),
textEncoder: new TextEncoder(),
});
fastify.decorate('eosjs', {api, rpc});
}, {
fastify: '>=2.0.0',
name: 'fastify-eosjs'
fastify: '>=2.0.0',
name: 'fastify-eosjs'
});
+20 -1
View File
@@ -62,7 +62,7 @@ export function registerRoutes(server: FastifyInstance) {
// chain api redirects
addRoute(server, 'v1-chain', '/v1/chain');
// other v1 requests
// other v1 requests
server.route({
url: '/v1/chain/*',
method: ["GET", "POST"],
@@ -95,6 +95,25 @@ export function registerRoutes(server: FastifyInstance) {
}
});
// global onRequest hook to collect hourly statistics for each path on redis
server.addHook('onResponse', (request: FastifyRequest, reply: FastifyReply, done) => {
const path = request.url.split('?')[0].replace(/\/$/, '');
const now = new Date();
const ts = now.getTime();
// normalized time in hours
const factor = 1000 * 60 * 60;
const ts_hour_unix = Math.floor(ts / factor) * factor;
const key = `stats:${server.manager.chain}:H:${ts_hour_unix}`;
// console.log(reply.statusCode, key, new Date(ts_hour_unix));
server.redis.multi()
.incr(`stats:${server.manager.chain}:total:${reply.statusCode}:${path}`)
.zincrby(key, 1, `[${reply.statusCode}]${path}`)
.expire(key, 60 * 60 * 24 * 7)
.exec()
.catch(console.log);
done();
});
server.addHook('onError', (request: FastifyRequest, reply: FastifyReply, error: FastifyError, done) => {
console.log(`[${request.headers['x-real-ip'] || request.ip}] ${request.method} ${request.url} failed >> ${error.message}`);
done();
+1 -5
View File
@@ -9,11 +9,7 @@ export default function (fastify: FastifyInstance, opts: any, next) {
{
"code": {$ref: 'AccountName#'},
"action": {$ref: 'AccountName#'},
"binargs": {
"type": "string",
"pattern": "^(0x)(([0-9a-f][0-9a-f])+)?$",
"title": "Hex"
}
"binargs": {"type": "string"}
},
["code", "action", "binargs"]
);
+6 -5
View File
@@ -7,13 +7,14 @@ export default function (fastify: FastifyInstance, opts: any, next) {
getRouteName(__filename),
'Convert JSON object to binary',
{
"binargs": {
"type": "string",
"pattern": "^(0x)(([0-9a-f][0-9a-f])+)?$",
"title": "Hex"
"code": {$ref: 'AccountName#'},
"action": {$ref: 'AccountName#'},
"args": {
type: 'object',
additionalProperties: true
}
},
["binargs"]
["code", "action", "args"]
);
next();
}
+1 -1
View File
@@ -37,7 +37,7 @@ export default function (fastify: FastifyInstance, opts: any, next) {
"type": "string"
}
},
["code", "table"]
["code", "table", "scope"]
);
next();
}
@@ -6,6 +6,7 @@ import * as AbiEOS from "@eosrio/node-abieos";
import {ApiResponse} from "@elastic/elasticsearch";
import {TextDecoder, TextEncoder} from "util";
import {JsonRpc} from "eosjs/dist";
import {terms} from "../../v2-history/get_actions/definitions";
const abi_remapping = {
"_Bool": "bool"
@@ -144,7 +145,6 @@ async function getContractAtBlock(esClient, rpc, chain, accountName: string, blo
}
const terms = ["notified", "act.authorization.actor"];
const extendedActions = new Set(["transfer", "newaccount", "updateauth"]);
async function getActions(fastify: FastifyInstance, request: FastifyRequest) {
@@ -182,8 +182,8 @@ async function getActions(fastify: FastifyInstance, request: FastifyRequest) {
}
pos = parseInt(reqBody.pos || 0, 10);
offset = parseInt(reqBody.offset || 20, 10);
let from, size;
from = size = 0;
let from = 0;
let size = 0;
if (pos === -1) {
if (offset < 0) {
from = 0;
@@ -272,6 +272,7 @@ async function getActions(fastify: FastifyInstance, request: FastifyRequest) {
}
}
};
// console.log(JSON.stringify(esOpts, null, 2));
const pResults = await Promise.all([fastify.eosjs.rpc.get_info(), fastify.elastic['search'](esOpts)]);
const results = pResults[1];
const response = {
@@ -1,265 +1,190 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {mergeActionMeta, timedQuery} from "../../../helpers/functions";
import {createHash} from "crypto";
import flatstr from 'flatstr';
import {GetInfoResult} from "eosjs/dist/eosjs-rpc-interfaces";
async function getTransaction(fastify: FastifyInstance, request: FastifyRequest) {
if (typeof request.body === 'string') {
request.body = JSON.parse(request.body)
}
const body: any = request.body;
const redis = fastify.redis;
const trxId = body.id.toLowerCase();
const conf = fastify.manager.config;
const cachedData = await redis.hgetall('trx_' + trxId);
const response: any = {
"id": body.id,
"trx": {
"receipt": {
"status": "executed",
"cpu_usage_us": 0,
"net_usage_words": 0,
"trx": [1, {
"signatures": "",
"compression": "none",
"packed_context_free_data": "",
"packed_trx": ""
}]
},
"trx": {}
},
"block_num": 0,
"block_time": "",
"last_irreversible_block": undefined,
"traces": []
};
if (request.method === 'POST' && !request.body) {
throw new Error("missing POST body");
}
let hits;
if (typeof request.body === 'string') {
request.body = JSON.parse(request.body)
}
const body: any = request.body;
const redis = fastify.redis;
const trxId = body.id.toLowerCase();
// build get_info request with caching
const $getInfo = new Promise<GetInfoResult>(resolve => {
const key = `${fastify.manager.chain}_get_info`;
fastify.redis.get(key).then(value => {
if (value) {
resolve(JSON.parse(value));
} else {
fastify.eosjs.rpc.get_info().then(value1 => {
fastify.redis.set(key, JSON.stringify(value1), 'EX', 6);
resolve(value1);
}).catch((reason) => {
console.log(reason);
response.error = 'failed to get last_irreversible_block_num'
resolve(null);
});
}
});
});
const conf = fastify.manager.config;
const cachedData = await redis.hgetall('trx_' + trxId);
// reconstruct hits from cached data
if (cachedData && Object.keys(cachedData).length > 0) {
const gsArr = [];
for (let cachedDataKey in cachedData) {
gsArr.push(cachedData[cachedDataKey]);
}
gsArr.sort((a, b) => {
return a.global_sequence - b.global_sequence;
});
hits = gsArr.map(value => {
return {
_source: JSON.parse(value)
};
});
const promiseResults = await Promise.all([
redis.ttl('trx_' + trxId),
$getInfo
]);
response.cache_expires_in = promiseResults[0];
response.last_irreversible_block = promiseResults[1].last_irreversible_block_num;
}
const response: any = {
"id": body.id,
"trx": {
"receipt": {
"status": "executed",
"cpu_usage_us": 0,
"net_usage_words": 0,
"trx": [1, {
"signatures": "",
"compression": "none",
"packed_context_free_data": "",
"packed_trx": ""
}]
},
"trx": {}
},
"block_num": 0,
"block_time": "",
"last_irreversible_block": undefined,
"traces": []
};
// search on ES if cache is not present
if (!hits) {
const _size = conf.api.limits.get_trx_actions || 100;
const blockHint = parseInt(body.block_num_hint, 10);
let indexPattern = '';
if (blockHint) {
const idxPart = Math.ceil(blockHint / conf.settings.index_partition_size).toString().padStart(6, '0');
indexPattern = fastify.manager.chain + `-action-${conf.settings.index_version}-${idxPart}`;
} else {
indexPattern = fastify.manager.chain + '-action-*';
}
let pResults;
try {
let hits;
// build search request
const $search = fastify.elastic.search({
index: indexPattern,
size: _size,
body: {
query: {bool: {must: [{term: {trx_id: trxId}}]}},
sort: {global_sequence: "asc"}
}
});
// build get_info request with caching
const $getInfo = new Promise<GetInfoResult>(resolve => {
const key = `${fastify.manager.chain}_get_info`;
fastify.redis.get(key).then(value => {
if (value) {
resolve(JSON.parse(value));
} else {
fastify.eosjs.rpc.get_info().then(value1 => {
fastify.redis.set(key, JSON.stringify(value1), 'EX', 6);
resolve(value1);
}).catch((reason) => {
console.log(reason);
response.error = 'failed to get last_irreversible_block_num'
resolve(null);
});
}
});
});
// execute in parallel
pResults = await Promise.all([$getInfo, $search]);
} catch (e) {
console.log(e.message);
if (e.meta.statusCode === 404) {
return response;
}
}
hits = pResults[1]['body']['hits']['hits'];
response.last_irreversible_block = pResults[0].last_irreversible_block_num;
}
// reconstruct hits from cached data
if (cachedData && Object.keys(cachedData).length > 0) {
const gsArr = [];
for (let cachedDataKey in cachedData) {
gsArr.push(cachedData[cachedDataKey]);
}
gsArr.sort((a, b) => {
return a.global_sequence - b.global_sequence;
});
hits = gsArr.map(value => {
return {
_source: JSON.parse(value)
};
});
const promiseResults = await Promise.all([
redis.ttl('trx_' + trxId),
$getInfo
]);
response.cache_expires_in = promiseResults[0];
response.last_irreversible_block = promiseResults[1].last_irreversible_block_num;
}
// search on ES if cache is not present
if (!hits) {
const _size = conf.api.limits.get_trx_actions || 100;
const blockHint = parseInt(body.block_num_hint, 10);
let indexPattern = '';
if (blockHint) {
const idxPart = Math.ceil(blockHint / conf.settings.index_partition_size).toString().padStart(6, '0');
indexPattern = fastify.manager.chain + `-action-${conf.settings.index_version}-${idxPart}`;
} else {
indexPattern = fastify.manager.chain + '-action-*';
}
let pResults;
try {
// build search request
const $search = fastify.elastic.search({
index: indexPattern,
size: _size,
body: {
query: {bool: {must: [{term: {trx_id: trxId}}]}},
sort: {global_sequence: "asc"}
}
});
// execute in parallel
pResults = await Promise.all([$getInfo, $search]);
} catch (e) {
console.log(e.message);
if (e.meta.statusCode === 404) {
return response;
}
}
hits = pResults[1]['body']['hits']['hits'];
response.last_irreversible_block = pResults[0].last_irreversible_block_num;
}
if (hits.length > 0) {
const actions = hits;
response.trx.trx = {
"expiration": "",
"ref_block_num": 0,
"ref_block_prefix": 0,
"max_net_usage_words": 0,
"max_cpu_usage_ms": 0,
"delay_sec": 0,
"context_free_actions": [],
"actions": [],
"transaction_extensions": [],
"signatures": [],
"context_free_data": []
};
let traces = {};
let seqNum = 0;
for (let action of actions) {
action = action._source;
mergeActionMeta(action);
// action.act['hex_data'] = Buffer.from(flatstr(JSON.stringify(action.act.data))).toString('hex');
if (action.parent === 0) {
response.trx.trx.actions.push(action.act);
}
response.block_num = action.block_num;
response.block_time = action['@timestamp'];
seqNum += 1;
let trace = {
receipt: {
receiver: action.act.account,
global_sequence: String(action.global_sequence),
auth_sequence: [
action.act.authorization[0].actor,
seqNum
],
act_digest: '',
recv_sequence: seqNum,
code_sequence: 1,
abi_sequence: 1
},
act: action.act,
account_ram_deltas: action.account_ram_deltas || [],
context_free: false,
block_num: action.block_num,
block_time: action['@timestamp'],
console: "",
elapsed: 0,
except: null,
inline_traces: [],
producer_block_id: "",
trx_id: body.id,
notified: action.notified
};
let hash = createHash('sha256');
hash.update(JSON.stringify(action.act));
trace.receipt.act_digest = hash.digest('hex');
traces[action.global_sequence] = trace;
}
actions.forEach(action => {
action = action._source;
for (let i = 0; i < traces[action.global_sequence].notified.length; i++) {
if (traces[action.global_sequence].notified[i] === action.act.account) {
traces[action.global_sequence].notified.splice(i, 1);
break;
}
}
if (action.parent !== 0 && action.parent) {
if (traces[action.parent]) {
for (let i = 0; i < traces[action.parent].notified.length; i++) {
if (traces[action.parent].notified[i] === action.act.account) {
traces[action.parent].notified.splice(i, 1);
break;
}
}
traces[action.parent].inline_traces.push(traces[action.global_sequence]);
}
}
});
actions.forEach(action => {
action = action._source;
response.traces.push(traces[action.global_sequence]);
if (traces[action.global_sequence] && traces[action.global_sequence].notified) {
traces[action.global_sequence].notified.forEach((note, index) => {
seqNum += 1;
let trace = {
receipt: {
receiver: note,
global_sequence: String(action.global_sequence + index + 1),
auth_sequence: [
action.act.authorization[0].actor,
seqNum
],
act_digest: traces[action.global_sequence].receipt.act_digest,
recv_sequence: seqNum,
code_sequence: 1,
abi_sequence: 1
},
account_ram_deltas: action.account_ram_deltas || [],
act: action.act,
block_num: action.block_num,
block_time: action['@timestamp'],
console: "",
context_free: false,
elapsed: 0,
except: null,
inline_traces: [],
producer_block_id: "",
trx_id: body.id,
};
traces[action.global_sequence].inline_traces.unshift(trace);
response.traces.push(trace);
});
delete traces[action.global_sequence].notified;
}
});
} else {
const errmsg = "Transaction " + body.id.toLowerCase() + " not found in history and no block hint was given";
return {
code: 500,
message: "Internal Service Error",
error: {
code: 3040011,
name: "tx_not_found",
what: "The transaction can not be found",
details: [
{
message: errmsg,
file: "",
line_number: 1,
method: "get_transaction"
}
]
}
}
}
return response;
if (hits.length > 0) {
const actions = hits;
response.trx.trx = {
"expiration": "",
"ref_block_num": 0,
"ref_block_prefix": 0,
"max_net_usage_words": 0,
"max_cpu_usage_ms": 0,
"delay_sec": 0,
"context_free_actions": [],
"actions": [],
"transaction_extensions": [],
"signatures": [],
"context_free_data": []
};
for (let action of actions) {
action = action._source;
mergeActionMeta(action);
response.block_num = action.block_num;
response.block_time = action['@timestamp'];
for (const receipt of action.receipts) {
if (action.act_digest) {
receipt.act_digest = action.act_digest.toLowerCase();
}
response.traces.push({
receipt: receipt,
act: action.act,
account_ram_deltas: action.account_ram_deltas || [],
context_free: false,
block_num: action.block_num,
block_time: action['@timestamp'],
console: "",
elapsed: 0,
except: null,
inline_traces: [],
producer_block_id: "",
trx_id: body.id
});
}
}
} else {
const errmsg = "Transaction " + body.id.toLowerCase() + " not found in history and no block hint was given";
return {
code: 500,
message: "Internal Service Error",
error: {
code: 3040011,
name: "tx_not_found",
what: "The transaction can not be found",
details: [
{
message: errmsg,
file: "",
line_number: 1,
method: "get_transaction"
}
]
}
}
}
return response;
}
export function getTransactionHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getTransaction, fastify, request, route));
}
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getTransaction, fastify, request, route));
}
}
@@ -12,7 +12,14 @@ export const extendedActions = new Set([
"buyrambytes",
"delegatebw",
"undelegatebw",
"voteproducer"
"voteproducer",
'statement',
'decision',
'batch',
'act',
'exec',
'votefor',
'voteagainst'
]);
export const primaryTerms = [
@@ -70,6 +70,15 @@ function addRangeQuery(queryStruct, prop, pkey, query) {
export function applyTimeFilter(query, queryStruct) {
if (query['after'] || query['before']) {
if (query['after']?.includes(' ')) {
query['after'] = query['after'].replace(' ', 'T');
}
if (query['before']?.includes(' ')) {
query['before'] = query['before'].replace(' ', 'T');
}
if (query['after']?.includes('T') || query['before']?.includes('T')) {
let _lte = "now";
let _gte = "0";
@@ -52,12 +52,15 @@ async function getActions(fastify: FastifyInstance, request: FastifyRequest) {
indexPattern = fastify.manager.chain + '-action';
}
const esResults = await fastify.elastic.search({
const esOpts = {
"index": indexPattern,
"from": skip || 0,
"size": (limit > maxActions ? maxActions : limit) || 10,
"body": query_body
});
};
// console.log(JSON.stringify(esOpts, null, 2));
const esResults = await fastify.elastic.search(esOpts);
const results = esResults['body']['hits'];
const response: any = {
@@ -107,13 +110,14 @@ async function getActions(fastify: FastifyInstance, request: FastifyRequest) {
}
if (query.simple) {
let notified = new Set(action.receipts.map(r => r.receiver));
response.simple_actions.push({
block: action['block_num'],
irreversible: response.lib !== 0 ? action['block_num'] < response.lib : undefined,
timestamp: action['@timestamp'],
transaction_id: action['trx_id'],
actors: action['act']['authorization'].map(a => `${a.actor}@${a.permission}`).join(","),
notified: action['notified'].join(','),
notified: [...notified].join(','),
contract: action['act']['account'],
action: action['act']['name'],
data: action['act']['data']
@@ -3,6 +3,7 @@ import {getActionsHandler} from "./get_actions";
import {addApiRoute, extendQueryStringSchema, extendResponseSchema, getRouteName} from "../../../helpers/functions";
export const getActionResponseSchema = {
"@timestamp": {type: "string"},
"timestamp": {type: "string"},
"block_num": {type: "number"},
"block_id": {type: "string"},
@@ -5,69 +5,101 @@ import {base58ToBinary, binaryToBase58} from "eosjs/dist/eosjs-numeric";
import {Search} from "@elastic/elasticsearch/api/requestParams";
function convertToLegacyKey(block_signing_key: string) {
if (block_signing_key.startsWith("PUB_K1_")) {
const buf = base58ToBinary(37, block_signing_key.substr(7));
const data = buf.slice(0, buf.length - 4);
const merged = Buffer.concat([
data,
createHash('ripemd160')
.update(data)
.digest()
.slice(0, 4)
]);
return "EOS" + binaryToBase58(merged);
} else {
return block_signing_key;
}
if (block_signing_key.startsWith("PUB_K1_")) {
const buf = base58ToBinary(37, block_signing_key.substr(7));
const data = buf.slice(0, buf.length - 4);
const merged = Buffer.concat([
data,
createHash('ripemd160')
.update(data)
.digest()
.slice(0, 4)
]);
return "EOS" + binaryToBase58(merged);
} else {
return block_signing_key;
}
}
async function getSchedule(fastify: FastifyInstance, request: FastifyRequest) {
const query: any = request.query;
const response: any = {
producers: []
};
const searchParams: Search<any> = {
track_total_hits: true,
index: fastify.manager.chain + "-block-*",
size: 1,
body: {
query: {
bool: {
must: []
}
},
sort: {block_num: "desc"}
}
};
if (query.version) {
searchParams.body.query.bool.must.push({"term": {"new_producers.version": {"value": query.version}}});
} else {
searchParams.body.query.bool.must.push({
"exists": {
"field": "new_producers.version"
}
});
}
const apiResponse = await fastify.elastic.search(searchParams);
const results = apiResponse.body.hits.hits;
if (results) {
response.timestamp = results[0]._source["@timestamp"];
response.block_num = results[0]._source.block_num;
response.version = results[0]._source.new_producers.version;
response.producers = results[0]._source.new_producers.producers.map(prod => {
return {
...prod,
legacy_key: convertToLegacyKey(prod.block_signing_key)
}
});
}
return response;
const query: any = request.query;
const response: any = {
producers: []
};
if (query.mode === 'activated') {
const searchParams: Search<any> = {
track_total_hits: true,
index: fastify.manager.chain + "-block-*",
size: 1,
body: {
query: {bool: {must: []}},
sort: {block_num: query.sort || "desc"}
}
};
if (query.version) {
searchParams.body.query.bool.must.push({"term": {"new_producers.version": {"value": query.version}}});
} else {
searchParams.body.query.bool.must.push({"exists": {"field": "new_producers.version"}});
}
const apiResponse = await fastify.elastic.search(searchParams);
const results = apiResponse.body.hits.hits;
if (results && results.length > 0) {
response.timestamp = results[0]._source["@timestamp"];
response.block_num = results[0]._source.block_num;
response.version = results[0]._source.new_producers.version;
response.producers = results[0]._source.new_producers.producers.map(prod => {
return {
...prod,
legacy_key: convertToLegacyKey(prod.block_signing_key)
}
});
}
} else {
// default "proposed" mode
const searchParams: Search<any> = {
track_total_hits: true,
index: fastify.manager.chain + "-schedule-*",
size: 1,
body: {
query: {bool: {must: []}},
sort: {version: query.sort || "desc"}
}
};
if (query.version) {
searchParams.body.query.bool.must.push({"term": {"version": {"value": query.version}}});
} else {
searchParams.body.query.bool.must.push({"exists": {"field": "version"}});
}
if (query.producer) {
searchParams.body.query.bool.must.push({"term": {"producers.name": {"value": query.producer}}});
}
if (query.key) {
searchParams.body.query.bool.must.push({"term": {"producers.keys": {"value": query.key}}});
}
const apiResponse = await fastify.elastic.search(searchParams);
const results = apiResponse.body.hits.hits;
if (results && results.length > 0) {
response.timestamp = results[0]._source["@timestamp"];
response.proposal_block_num = results[0]._source.block_num;
response.version = results[0]._source.version;
response.producers = results[0]._source.producers;
// sort producers by name
// response.producers.sort((a, b) => {
// if (a.name < b.name) {
// return -1;
// }
// if (a.name > b.name) {
// return 1;
// }
// return 0;
// });
}
}
return response;
}
export function getScheduleHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getSchedule, fastify, request, route));
}
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getSchedule, fastify, request, route));
}
}
+9 -2
View File
@@ -15,7 +15,7 @@ export default function (fastify: FastifyInstance, opts: any, next) {
type: 'string'
},
"key": {
description: 'search by key',
description: 'search by signing key',
type: 'string'
},
"after": {
@@ -26,6 +26,10 @@ export default function (fastify: FastifyInstance, opts: any, next) {
description: 'filter before specified date (ISO8601)',
type: 'string'
},
"mode": {
description: 'search mode (activated or proposed)',
type: 'string',
},
"version": {
description: 'schedule version',
type: 'integer',
@@ -36,6 +40,7 @@ export default function (fastify: FastifyInstance, opts: any, next) {
response: extendResponseSchema({
"timestamp": {type: "string"},
"block_num": {type: "number"},
"proposed_block_num": {type: "number"},
"version": {type: "number"},
"producers": {
type: "array",
@@ -43,8 +48,10 @@ export default function (fastify: FastifyInstance, opts: any, next) {
type: "object",
properties: {
"producer_name": {type: "string"},
"name": {type: "string"},
"block_signing_key": {type: "string"},
"legacy_key": {type: "string"}
"legacy_key": {type: "string"},
"keys": {type: "array", items: {type: "string"}}
}
}
},
@@ -0,0 +1,53 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {timedQuery} from "../../../helpers/functions";
import {parseInt} from "lodash";
async function getApiUsage(fastify: FastifyInstance, request: FastifyRequest) {
const response = {
total: {responses: {}},
buckets: []
} as any;
const data = await fastify.redis.keys(`stats:${fastify.manager.chain}:*`);
const buckets = [];
for (const key of data) {
const parts = key.split(':');
if (parts[2] === 'H') {
const bucket = {
timestamp: new Date(parseInt(parts[3])),
responses: {}
};
const sortedSet = await fastify.redis.zrange(key, 0, -1, 'WITHSCORES');
for (let i = 0; i < sortedSet.length; i += 2) {
const [status, path] = sortedSet[i].split(']');
if (status) {
const statusCode = status.slice(1);
if (!bucket.responses[statusCode]) {
bucket.responses[statusCode] = {};
}
if (statusCode) {
bucket.responses[statusCode][path] = parseInt(sortedSet[i + 1]);
}
}
}
response.buckets.push(bucket);
} else {
if (parts[2] === 'total' && parts[4]) {
const statusCode = parts[3];
if (!response.total.responses[statusCode]) {
response.total.responses[statusCode] = {};
}
const value = await fastify.redis.get(key);
response.total.responses[statusCode][parts[4]] = parseInt(value);
}
}
}
// reverse sorte by date
response.buckets.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
return response;
}
export function getApiUsageHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getApiUsage, fastify, request, route));
}
}
@@ -0,0 +1,41 @@
import {FastifyInstance} from "fastify";
import {addApiRoute, extendResponseSchema, getRouteName} from "../../../helpers/functions";
import {getApiUsageHandler} from "./get_api_usage";
export default function (fastify: FastifyInstance, opts: any, next) {
const schema = {
description: 'get hyperion api usage statistics',
summary: 'get hyperion api usage statistics',
tags: ['stats'],
response: extendResponseSchema({
total: {
type: 'object',
properties: {
responses: {
additionalProperties: true
}
}
},
buckets: {
type: 'array',
items: {
type: 'object',
properties: {
timestamp: {type: 'string'},
responses: {
additionalProperties: true
}
}
}
}
})
};
addApiRoute(
fastify,
'GET',
getRouteName(__filename),
getApiUsageHandler,
schema
);
next();
}
+8 -1
View File
@@ -63,7 +63,14 @@ interface ServiceResponse<T> {
async function checkElastic(fastify: FastifyInstance): Promise<ServiceResponse<ESService>> {
try {
let esStatus = await fastify.elastic.cat.health({format: 'json', v: true});
const esStatusCache = await fastify.redis.get(`${fastify.manager.chain}::es_status`);
let esStatus;
if (esStatusCache) {
esStatus = JSON.parse(esStatusCache);
} else {
esStatus = await fastify.elastic.cat.health({format: 'json', v: true});
await fastify.redis.set(`${fastify.manager.chain}::es_status`, JSON.stringify(esStatus), 'EX', 30 * 60);
}
let firstIndexedBlock: number;
const fib = await fastify.redis.get(`${fastify.manager.chain}::fib`);
if (fib) {
+55 -9
View File
@@ -1,4 +1,4 @@
import {hLog} from "../helpers/common_functions";
import {hLog, waitUntilReady} from "../helpers/common_functions";
import {ConfigurationModule} from "../modules/config";
import {ConnectionManager} from "../connections/manager.class";
import {HyperionConfig} from "../interfaces/hyperionConfig";
@@ -16,7 +16,6 @@ import {io, Socket} from "socket.io-client";
import {CacheManager} from "./helpers/cacheManager";
import {bootstrap} from 'global-agent';
bootstrap();
class HyperionApiServer {
@@ -29,6 +28,7 @@ class HyperionApiServer {
socketManager: SocketManager;
mLoader: HyperionModuleLoader;
private pluginParams: any;
constructor() {
@@ -37,6 +37,11 @@ class HyperionApiServer {
const cm = new ConfigurationModule();
this.conf = cm.config;
if (this.conf.settings.use_global_agent) {
bootstrap();
}
this.chain = this.conf.settings.chain;
process.title = `hyp-${this.chain}-api`;
this.manager = new ConnectionManager(cm);
@@ -111,6 +116,7 @@ class HyperionApiServer {
},
fastify_redis: this.manager.conn.redis,
fastify_eosjs: this.manager,
chain_id: '',
} as any;
if (!this.conf.api.disable_rate_limit) {
@@ -140,26 +146,25 @@ class HyperionApiServer {
pluginParams.fastify_swagger = docsConfig;
}
registerPlugins(this.fastify, pluginParams);
this.addGenericTypeParsing();
this.pluginParams = pluginParams;
}
activateStreaming() {
console.log('Importing stream module');
console.log('Importing stream module...');
import('./socketManager').then((mod) => {
const connOpts = this.manager.conn.chains[this.chain];
let _port = 57200;
if (connOpts.WS_ROUTER_PORT) {
_port = connOpts.WS_ROUTER_PORT;
}
let _host = "127.0.0.1";
if (connOpts.WS_ROUTER_HOST) {
_host = connOpts.WS_ROUTER_HOST;
}
if (_host === "0.0.0.0") {
hLog(`[ERROR] WS Router Host is set to 0.0.0.0, please use a fixed IP address instead. Can't start streaming.`);
return;
}
this.socketManager = new mod.SocketManager(
this.fastify,
`http://${_host}:${_port}`,
@@ -187,6 +192,47 @@ class HyperionApiServer {
async init() {
const rpc = this.manager.nodeosJsonRPC;
await waitUntilReady(async () => {
try {
const chain_data = await rpc.get_info();
if (chain_data && chain_data.chain_id) {
this.pluginParams.chain_id = chain_data.chain_id;
return true;
} else {
return false;
}
} catch (e) {
hLog(e.message);
return false;
}
}, 20, 5000, () => {
hLog('Failed to validate chain api!');
process.exit(1);
});
hLog('Chain API validated!');
// Wait for Elasticsearch availability
await waitUntilReady(async () => {
try {
const esInfo = await this.manager.elasticsearchClient.info();
hLog(`Elasticsearch: ${esInfo.body.version.number} | Lucene: ${esInfo.body.version.lucene_version}`);
return true;
} catch (e) {
console.log(e.message);
return false;
}
}, 10, 5000, () => {
hLog('Failed to check elasticsearch version!');
process.exit();
});
hLog('Elasticsearch validated!');
hLog('Registering plugins...');
registerPlugins(this.fastify, this.pluginParams);
this.addGenericTypeParsing();
await this.mLoader.init();
// add custom plugin routes
+110 -20
View File
@@ -1,4 +1,4 @@
import {hLog} from '../helpers/common_functions';
import {hLog, sleep} from '../helpers/common_functions';
import {Server, Socket} from 'socket.io';
import {createAdapter} from 'socket.io-redis';
import {io} from 'socket.io-client';
@@ -6,6 +6,7 @@ import {FastifyInstance} from "fastify";
import IORedis from "ioredis";
import {App, TemplatedApp} from 'uWebSockets.js';
import {streamPastActions, streamPastDeltas} from "./helpers/functions";
import {randomUUID} from "crypto";
export interface StreamDeltasRequest {
code: string;
@@ -40,17 +41,14 @@ export class SocketManager {
private readonly url;
private readonly server: FastifyInstance;
private readonly uwsApp: TemplatedApp;
private readonly chainId: string;
private currentBlockNum: number;
constructor(fastify: FastifyInstance, url, redisOptions) {
this.server = fastify;
this.url = url;
this.uwsApp = App({});
// this.io = new Server(fastify.server, {
// allowEIO3: true,
// transports: ['websocket', 'polling'],
// });
// WS Server for public access
this.io = new Server({
transports: ['websocket'],
@@ -59,9 +57,11 @@ export class SocketManager {
this.io.attachApp(this.uwsApp);
this.chainId = this.server.manager.conn.chains[this.server.manager.chain].chain_id
hLog(`[SocketManager] chain_id: ${this.chainId}`);
const pubClient = new IORedis(redisOptions);
const subClient = pubClient.duplicate();
this.io.adapter(createAdapter({pubClient, subClient}));
this.io.adapter(createAdapter({pubClient, subClient, key: this.chainId}));
this.io.on('connection', (socket: Socket) => {
@@ -84,10 +84,42 @@ export class SocketManager {
socket.on('delta_stream_request', async (data: StreamDeltasRequest, callback) => {
if (typeof callback === 'function' && data) {
try {
// generate random uuid
socket.data.reqUUID = randomUUID();
const lastHistoryBlock = await new Promise<number>((resolve) => {
// start sending realtime data
this.emitToRelay(data, 'delta_request', socket, (emissionResult) => {
callback(emissionResult);
resolve(emissionResult.currentBlockNum);
});
});
// push history data
if (data.start_from) {
await streamPastDeltas(this.server, socket, data);
data.read_until = lastHistoryBlock;
console.log('Performing primary scroll request...');
let ltb = 0;
const hStreamResult = await streamPastDeltas(this.server, socket, data);
if (hStreamResult.status === false) {
return;
} else {
ltb = hStreamResult.lastTransmittedBlock;
let attempts = 0;
await sleep(500);
while (ltb > 0 && lastHistoryBlock > ltb && attempts < 3) {
attempts++;
console.log(`Performing fill request from ${ltb}...`);
data.start_from = hStreamResult.lastTransmittedBlock + 1;
data.read_until = lastHistoryBlock;
const r = await streamPastDeltas(this.server, socket, data);
if (r.status === false) {
console.log(r);
return;
} else {
ltb = r.lastTransmittedBlock;
}
}
}
}
this.emitToRelay(data, 'delta_request', socket, callback);
} catch (e) {
console.log(e);
}
@@ -97,10 +129,42 @@ export class SocketManager {
socket.on('action_stream_request', async (data: StreamActionsRequest, callback) => {
if (typeof callback === 'function' && data) {
try {
// generate random uuid
socket.data.reqUUID = randomUUID();
const lastHistoryBlock = await new Promise<number>((resolve) => {
// start sending realtime data
this.emitToRelay(data, 'action_request', socket, (emissionResult) => {
callback(emissionResult);
resolve(emissionResult.currentBlockNum);
});
});
// push history data
if (data.start_from) {
await streamPastActions(this.server, socket, data);
data.read_until = lastHistoryBlock;
console.log('Performing primary scroll request...');
let ltb = 0;
const hStreamResult = await streamPastActions(this.server, socket, data);
if (hStreamResult.status === false) {
return;
} else {
ltb = hStreamResult.lastTransmittedBlock;
let attempts = 0;
await sleep(500);
while (ltb > 0 && lastHistoryBlock > ltb && attempts < 3) {
attempts++;
console.log(`Performing fill request from ${hStreamResult.lastTransmittedBlock}...`);
data.start_from = hStreamResult.lastTransmittedBlock + 1;
data.read_until = lastHistoryBlock;
const r = await streamPastActions(this.server, socket, data);
if (r.status === false) {
console.log(r);
return;
} else {
ltb = r.lastTransmittedBlock;
}
}
}
}
this.emitToRelay(data, 'action_request', socket, callback);
} catch (e) {
console.log(e);
}
@@ -160,18 +224,40 @@ export class SocketManager {
this.emitToClient(traceData, 'action_trace');
});
// Relay LIB info to clients;
this.relay.on('lib_update', (data) => {
if (this.server.manager.conn.chains[this.server.manager.chain].chain_id === data.chain_id) {
this.io.emit('lib_update', data);
this.relay.on('block', (blockData) => {
try {
// const decodedBlock = JSON.parse(blockData.content.toString());
// console.log(blockData.serverTime, blockData.blockNum, decodedBlock);
this.currentBlockNum = blockData.blockNum;
} catch (e) {
hLog(`Failed to decode incoming live block ${blockData.blockNum}: ${e.message}`);
}
});
// Relay LIB info to clients;
this.relay.on('fork_event', (data) => {
hLog(data);
if (this.server.manager.conn.chains[this.server.manager.chain].chain_id === data.chain_id) {
this.io.emit('fork_event', data);
this.addRelayForwarding('lib_update');
this.addRelayForwarding('fork_event');
// // Relay LIB info to clients;
// this.relay.on('lib_update', (data) => {
// if (this.server.manager.conn.chains[this.server.manager.chain].chain_id === data.chain_id) {
// this.io.emit('lib_update', data);
// }
// });
//
// // Relay fork info to clients;
// this.relay.on('fork_event', (data) => {
// if (this.server.manager.conn.chains[this.server.manager.chain].chain_id === data.chain_id) {
// this.io.emit('fork_event', data);
// }
// });
}
// Relay events to clients
addRelayForwarding(event: string) {
this.relay.on(event, (data: any) => {
if (data.chain_id && this.chainId === data.chain_id) {
this.io.emit(event, data);
}
});
}
@@ -180,6 +266,7 @@ export class SocketManager {
if (this.io.sockets.sockets.has(traceData.client)) {
this.io.sockets.sockets.get(traceData.client).emit('message', {
type: type,
reqUUID: traceData.req,
mode: 'live',
message: traceData.message,
});
@@ -189,10 +276,13 @@ export class SocketManager {
emitToRelay(data, type, socket, callback) {
if (this.relay.connected) {
this.relay.emit('event', {
reqUUID: socket.data.reqUUID,
type: type,
client_socket: socket.id,
request: data,
}, (response) => {
response['reqUUID'] = socket.data.reqUUID;
response['currentBlockNum'] = this.currentBlockNum;
callback(response);
});
} else {
+3 -1
View File
@@ -7,6 +7,8 @@
"server_addr": "127.0.0.1",
"server_port": 7000,
"stream_port": 1234,
"stream_scroll_limit": -1,
"stream_scroll_batch": 500,
"server_name": "127.0.0.1:7000",
"provider_name": "Example Provider",
"provider_url": "https://example.com",
@@ -77,7 +79,7 @@
"auto_mode_switch": false,
"hot_warm_policy": false,
"custom_policy": "",
"bypass_index_map": false,
"use_global_agent": false,
"index_partition_size": 10000000,
"es_replicas": 0
},
+8 -9
View File
@@ -17,17 +17,16 @@ export async function createConnection(config): Promise<Connection> {
}
export function getAmpqUrl(config): string {
let frameMaxValue = '0x10000';
if (config.frameMax) {
frameMaxValue = config.frameMax;
}
const u = encodeURIComponent(config.user);
const p = encodeURIComponent(config.pass);
const v = encodeURIComponent(config.vhost)
return `amqp://${u}:${p}@${config.host}/${v}?frameMax=${frameMaxValue}`;
let frameMaxValue = '0x10000';
if (config.frameMax) {
frameMaxValue = config.frameMax;
}
const u = encodeURIComponent(config.user);
const p = encodeURIComponent(config.pass);
const v = encodeURIComponent(config.vhost);
return `amqp://${u}:${p}@${config.host}/${v}?frameMax=${frameMaxValue}`;
}
async function createChannels(connection) {
try {
const channel = await connection.createChannel();
+2 -3
View File
@@ -9,7 +9,6 @@ import {StateHistorySocket} from "./state-history";
import fetch from 'cross-fetch';
import {exec} from "child_process";
import {hLog} from "../helpers/common_functions";
import {readFileSync} from "fs";
export class ConnectionManager {
@@ -20,8 +19,8 @@ export class ConnectionManager {
last_commit_hash: string;
current_version: string;
private readonly esIngestClients: Client[];
private esIngestClient: Client;
esIngestClients: Client[];
esIngestClient: Client;
constructor(private cm: ConfigurationModule) {
this.config = cm.config;
File diff suppressed because it is too large Load Diff
+8 -1
View File
@@ -5,7 +5,7 @@ const path = require('path');
const apps = [];
const chainsRoot = path.join(path.resolve(), 'chains');
readdirSync(chainsRoot)
.filter(f => f.endsWith('.config.json'))
.filter(f => f.endsWith('.config.json') && !f.startsWith('example'))
.forEach(value => {
const configFile = readFileSync(path.join(chainsRoot, value))
const config = JSON.parse(configFile.toString());
@@ -20,4 +20,11 @@ readdirSync(chainsRoot)
}
});
// apps.push({
// name: 'hyperion-governor',
// namespace: 'hyperion',
// script: 'governor/server/index.js',
// watch: false,
// });
module.exports = {apps};
+2 -1
View File
@@ -27,8 +27,9 @@
"chain_id": "aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906",
"http": "http://127.0.0.1:8888",
"ship": "ws://127.0.0.1:8080",
"WS_ROUTER_HOST": "127.0.0.1",
"WS_ROUTER_PORT": 7001,
"WS_ROUTER_HOST": "127.0.0.1"
"control_port": 7002
}
}
}
+235 -192
View File
@@ -6,245 +6,288 @@ import {join} from "path";
let config;
const conf_path = join(__dirname, `../${process.env.CONFIG_JSON}`);
if (existsSync(conf_path)) {
try {
config = require(conf_path);
} catch (e) {
console.log(e.message);
process.exit(1);
}
try {
config = require(conf_path);
} catch (e) {
console.log(e.message);
process.exit(1);
}
}
if (!config) {
console.log(`Configuration not found: ${conf_path}`);
process.exit(1);
console.log(`Configuration not found: ${conf_path}`);
process.exit(1);
}
function getLastResult(results: ApiResponse) {
if (results.body.hits?.hits?.length > 0) {
return parseInt(results.body.hits.hits[0].sort[0], 10);
} else {
return 1;
}
if (results.body.hits?.hits?.length > 0) {
return parseInt(results.body.hits.hits[0].sort[0], 10);
} else {
return 1;
}
}
export async function getLastIndexedBlockByDelta(es_client: Client, chain: string) {
const results: ApiResponse = await es_client.search({
index: chain + '-delta-*',
size: 1,
body: {
query: {bool: {filter: {match_all: {}}}},
sort: [{block_num: {order: "desc"}}]
}
});
return getLastResult(results);
const results: ApiResponse = await es_client.search({
index: chain + '-delta-*',
size: 1,
body: {
query: {bool: {filter: {match_all: {}}}},
sort: [{block_num: {order: "desc"}}]
}
});
return getLastResult(results);
}
export async function getLastIndexedBlock(es_client: Client, chain: string) {
const results: ApiResponse = await es_client.search({
index: chain + '-block-*',
size: 1,
body: {
query: {bool: {filter: {match_all: {}}}},
sort: [{block_num: {order: "desc"}}]
}
});
return getLastResult(results);
const results: ApiResponse = await es_client.search({
index: chain + '-block-*',
size: 1,
body: {
query: {bool: {filter: {match_all: {}}}},
sort: [{block_num: {order: "desc"}}]
}
});
return getLastResult(results);
}
export async function getLastIndexedBlockWithTotalBlocks(es_client: Client, chain: string): Promise<[number, number]> {
const results: ApiResponse = await es_client.search({
index: chain + '-block-*',
size: 1,
body: {
query: {bool: {filter: {match_all: {}}}},
sort: [{block_num: {order: "desc"}}],
track_total_hits: true
}
});
let lastBlock = getLastResult(results);
let totalBlocks = results.body.hits.total.value || 1;
return [lastBlock, totalBlocks];
const results: ApiResponse = await es_client.search({
index: chain + '-block-*',
size: 1,
body: {
query: {bool: {filter: {match_all: {}}}},
sort: [{block_num: {order: "desc"}}],
track_total_hits: true
}
});
let lastBlock = getLastResult(results);
let totalBlocks = results.body.hits.total.value || 1;
return [lastBlock, totalBlocks];
}
export async function getFirstIndexedBlock(es_client: Client, chain: string): Promise<number> {
const results: ApiResponse = await es_client.search({
index: chain + '-block-*',
size: 1,
body: {
query: {bool: {filter: {match_all: {}}}},
sort: [{block_num: {order: "asc"}}]
}
});
return getLastResult(results);
const results: ApiResponse = await es_client.search({
index: chain + '-block-*',
size: 1,
body: {
query: {bool: {filter: {match_all: {}}}},
sort: [{block_num: {order: "asc"}}]
}
});
return getLastResult(results);
}
export async function getLastIndexedABI(es_client: Client, chain: string) {
const results: ApiResponse = await es_client.search({
index: chain + '-abi-*',
size: 1,
body: {
query: {
match_all: {}
},
sort: [{block: {order: "desc"}}]
}
});
return getLastResult(results);
const results: ApiResponse = await es_client.search({
index: chain + '-abi-*',
size: 1,
body: {
query: {
match_all: {}
},
sort: [{block: {order: "desc"}}]
}
});
return getLastResult(results);
}
export async function getLastIndexedBlockByDeltaFromRange(es_client: Client, chain: string, first: number, last: number) {
const results: ApiResponse = await es_client.search({
index: chain + '-delta-*',
size: 1,
body: {
query: {
range: {
block_num: {
"gte": first,
"lt": last,
"boost": 2
}
}
},
sort: [{block_num: {order: "desc"}}]
}
});
return getLastResult(results);
const results: ApiResponse = await es_client.search({
index: chain + '-delta-*',
size: 1,
body: {
query: {
range: {
block_num: {
"gte": first,
"lt": last,
"boost": 2
}
}
},
sort: [{block_num: {order: "desc"}}]
}
});
return getLastResult(results);
}
export async function getLastIndexedBlockFromRange(es_client: Client, chain: string, first: number, last: number) {
const results = await es_client.search({
index: chain + '-block-*',
size: 1,
body: {
query: {
range: {
block_num: {
"gte": first,
"lt": last,
"boost": 2
}
}
},
sort: [{block_num: {order: "desc"}}]
}
});
return getLastResult(results);
const results = await es_client.search({
index: chain + '-block-*',
size: 1,
body: {
query: {
range: {
block_num: {
"gte": first,
"lt": last,
"boost": 2
}
}
},
sort: [{block_num: {order: "desc"}}]
}
});
return getLastResult(results);
}
export function messageAllWorkers(cl, payload) {
for (const c in cl.workers) {
if (cl.workers.hasOwnProperty(c)) {
const _w = cl.workers[c];
if (_w) {
try {
if (_w.isConnected()) {
_w.send(payload);
} else {
hLog('Worker is not connected!');
}
} catch (e) {
hLog('Failed to message worker!');
hLog(e);
}
} else {
hLog('Worker not found!');
}
}
}
for (const c in cl.workers) {
if (cl.workers.hasOwnProperty(c)) {
const _w = cl.workers[c];
if (_w) {
try {
if (_w.isConnected()) {
_w.send(payload);
} else {
hLog('Worker is not connected!');
}
} catch (e) {
hLog('Failed to message worker!');
hLog(e);
}
} else {
hLog('Worker not found!');
}
}
}
}
export function serialize(type, value, txtEnc, txtDec, types) {
const buffer = new Serialize.SerialBuffer({
textEncoder: txtEnc,
textDecoder: txtDec
});
Serialize.getType(types, type).serialize(buffer, value);
return buffer.asUint8Array();
const buffer = new Serialize.SerialBuffer({
textEncoder: txtEnc,
textDecoder: txtDec
});
Serialize.getType(types, type).serialize(buffer, value);
return buffer.asUint8Array();
}
export function deserialize(type, array, txtEnc, txtDec, types) {
const buffer = new Serialize.SerialBuffer({
textEncoder: txtEnc,
textDecoder: txtDec,
array
});
return Serialize.getType(types, type).deserialize(buffer, new Serialize.SerializerState({bytesAsUint8Array: true}));
const buffer = new Serialize.SerialBuffer({
textEncoder: txtEnc,
textDecoder: txtDec,
array
});
return Serialize.getType(types, type).deserialize(buffer, new Serialize.SerializerState({bytesAsUint8Array: true}));
}
function getNested(path_array, jsonObj) {
const nextPath = path_array.shift();
const nextValue = jsonObj[nextPath];
if (!nextValue) {
return null;
} else {
if (typeof nextValue !== 'object') {
return nextValue;
} else {
if (Array.isArray(nextValue)) {
return nextValue;
} else {
return getNested(path_array, nextValue);
}
}
}
const nextPath = path_array.shift();
const nextValue = jsonObj[nextPath];
if (!nextValue) {
return null;
} else {
if (typeof nextValue !== 'object') {
return nextValue;
} else {
if (Array.isArray(nextValue)) {
return nextValue;
} else {
return getNested(path_array, nextValue);
}
}
}
}
export function checkDeltaFilter(filter, _source) {
if (filter.field && filter.value) {
let fieldValue = getNested(filter.field.split("."), _source);
if (!fieldValue) {
const fArray = filter.field.split(".");
if (fArray[0].startsWith('@')) {
const tableName = fArray[0].replace('@', '');
if (_source.table === tableName) {
fArray[0] = 'data';
fieldValue = getNested(fArray, _source);
}
}
}
if (fieldValue) {
if (Array.isArray(fieldValue)) {
return fieldValue.indexOf(filter.value) !== -1;
} else {
return fieldValue === filter.value;
}
} else {
return !filter.value;
}
} else {
return false;
}
}
export function checkFilter(filter, _source) {
if (filter.field && filter.value) {
let fieldValue = getNested(filter.field.split("."), _source);
if (!fieldValue) {
const fArray = filter.field.split(".");
if (fArray[0].startsWith('@')) {
const actName = fArray[0].replace('@', '');
if (_source.act.name === actName) {
fArray[0] = 'data';
fArray.unshift('act');
fieldValue = getNested(fArray, _source);
}
}
}
if (fieldValue) {
if (Array.isArray(fieldValue)) {
return fieldValue.indexOf(filter.value) !== -1;
} else {
return fieldValue === filter.value;
}
} else {
return !filter.value;
}
} else {
return false;
}
if (filter.field && filter.value) {
let fieldValue = getNested(filter.field.split("."), _source);
if (!fieldValue) {
const fArray = filter.field.split(".");
if (fArray[0].startsWith('@')) {
const actName = fArray[0].replace('@', '');
if (_source.act.name === actName) {
fArray[0] = 'data';
fArray.unshift('act');
fieldValue = getNested(fArray, _source);
}
}
}
if (fieldValue) {
if (Array.isArray(fieldValue)) {
return fieldValue.indexOf(filter.value) !== -1;
} else {
return fieldValue === filter.value;
}
} else {
return !filter.value;
}
} else {
return false;
}
}
export function hLog(input: any, ...extra: any[]) {
let role;
if (process.env.worker_role) {
const id = parseInt(process.env.worker_id);
role = `[${process.pid} - ${(id < 10 ? '0' : '') + id.toString()}_${process.env.worker_role}]`;
} else {
if (process.env.script && process.env.script === './api/server.js') {
role = `[${process.pid} - api]`;
} else {
role = `[${process.pid} - 00_master]`;
}
}
if (process.env.TRACE_LOGS === 'true') {
const e = new Error();
const frame = e.stack.split("\n")[2];
const where = frame.split(" ")[6].split(/[:()]/);
const arr = where[1].split("/");
const fileName = arr[arr.length - 1];
const lineNumber = where[2];
role += ` ${fileName}:${lineNumber}`;
}
console.log(role, input, ...extra);
let role;
if (process.env.worker_role) {
const id = parseInt(process.env.worker_id);
role = `[${process.pid} - ${(id < 10 ? '0' : '') + id.toString()}_${process.env.worker_role}]`;
} else {
if (process.env.script && process.env.script === './api/server.js') {
role = `[${process.pid} - api]`;
} else {
role = `[${process.pid} - 00_master]`;
}
}
if (process.env.TRACE_LOGS === 'true') {
const e = new Error();
const frame = e.stack.split("\n")[2];
const where = frame.split(" ")[6].split(/[:()]/);
const arr = where[1].split("/");
const fileName = arr[arr.length - 1];
const lineNumber = where[2];
role += ` ${fileName}:${lineNumber}`;
}
console.log(role, input, ...extra);
}
export function debugLog(text: any, ...extra: any[]) {
if (config.settings.debug) {
hLog(text, ...extra);
}
if (config.settings.debug) {
hLog(text, ...extra);
}
}
export async function sleep(ms): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
export async function waitUntilReady(executor: () => Promise<boolean>, attempts: number, interval: number, onError: () => void): Promise<void> {
let i = 0;
while (i < attempts) {
if (await executor()) {
return;
}
await sleep(interval);
i++;
}
onError();
}
+34 -30
View File
@@ -145,6 +145,14 @@ function buildResUsageBulk(payloads, messageMap: MMap) {
});
}
function buildScheduleBulk(payloads, messageMap: MMap) {
return flatMap(payloads, (payload, body) => {
const id = `${body.version}`;
messageMap.set(id, _.omit(payload, ['content']));
return makeScriptedOp(id, body);
});
}
function buildGenTrxBulk(payloads, messageMap: MMap) {
return flatMap(payloads, (payload, body) => {
const hash = createHash('sha256')
@@ -183,6 +191,10 @@ const generatorsMap = {
index_name: 'userres',
func: buildResUsageBulk
},
schedule: {
index_name: 'schedule',
func: buildScheduleBulk
},
generated_transaction: {
index_name: 'gentrx',
func: buildGenTrxBulk
@@ -193,21 +205,13 @@ const generatorsMap = {
},
};
interface IndexDist {
index: string;
first_block: number;
last_block: number;
}
export class ElasticRoutes {
public routes: any;
cm: ConnectionManager;
chain: string;
ingestNodeCounters = {};
distributionMap: IndexDist[];
constructor(connectionManager: ConnectionManager, distributionMap: IndexDist[]) {
this.distributionMap = distributionMap;
constructor(connectionManager: ConnectionManager) {
this.routes = {generic: this.handleGenericRoute.bind(this)};
this.cm = connectionManager;
this.chain = this.cm.chain;
@@ -382,27 +386,27 @@ export class ElasticRoutes {
return Math.ceil(blockNum / this.cm.config.settings.index_partition_size).toString().padStart(6, '0');
}
getIndexNameByBlock(block_num) {
if (!this.distributionMap) {
return null;
}
for (let i = this.distributionMap.length - 1; i >= 0; i--) {
const test = this.distributionMap[i].first_block <= block_num && this.distributionMap[i].last_block >= block_num;
if (test) {
return this.distributionMap[i].index;
}
}
return null;
}
addToIndexMap(map, idx, payload) {
if (idx) {
if (!map[idx]) {
map[idx] = [];
}
map[idx].push(payload);
}
}
// getIndexNameByBlock(block_num) {
// if (!this.distributionMap) {
// return null;
// }
// for (let i = this.distributionMap.length - 1; i >= 0; i--) {
// const test = this.distributionMap[i].first_block <= block_num && this.distributionMap[i].last_block >= block_num;
// if (test) {
// return this.distributionMap[i].index;
// }
// }
// return null;
// }
//
// addToIndexMap(map, idx, payload) {
// if (idx) {
// if (!map[idx]) {
// map[idx] = [];
// }
// map[idx].push(payload);
// }
// }
private routeFactory(indexName: string, bulkGenerator, routerFunction) {
return async (payloads, channel, cb) => {
Executable
+2
View File
@@ -0,0 +1,2 @@
#!/bin/bash
node ./scripts/hyp-repair.js "$@"
+3
View File
@@ -20,6 +20,7 @@ export interface ScalingConfigs {
}
export interface MainSettings {
use_global_agent?: boolean;
process_prefix?: string;
ignore_snapshot?: boolean;
ship_request_rev: string;
@@ -88,6 +89,8 @@ interface CachedRouteConfig {
}
interface ApiConfigs {
stream_scroll_batch?: number;
stream_scroll_limit?: number;
enabled?: boolean;
pm2_scaling?: number;
node_max_old_space_size?: number;
+1
View File
@@ -23,6 +23,7 @@ interface HyperionChainData {
ship: string;
WS_ROUTER_PORT: number;
WS_ROUTER_HOST: string;
control_port: number;
}
interface RedisConfig {
+1
View File
@@ -13,6 +13,7 @@ const hyperionWorkers = {
reader: 'state-reader',
deserializer: 'deserializer',
continuous_reader: 'state-reader',
repair_reader: 'state-reader',
ingestor: 'indexer',
router: 'ws-router',
delta_updater: 'delta-updater'
+39
View File
@@ -0,0 +1,39 @@
const hyperionModule = {
chain: "*",
contract: '*',
action: 'act',
parser_version: ['3.2', '2.1','1.8','1.7'],
defineQueryPrefix: 'act',
mappings: {
action: {
"@act": {
"properties": {
"coopname": {"type": "keyword"},
"username": {"type": "keyword"},
"action": {"type": "keyword"},
"decision_id": {"type": "long"},
"batch_id": {"type": "long"},
"act": {"enabled": false}
}
}
}
},
handler: (action) => {
const data = action['act']['data'];
action['@act'] = {
coopname: data.coopname,
username: data.username,
action: data.action,
decision_id: data.decision_id,
batch_id: data.batch_id,
act: data.act
};
}
};
module.exports = {hyperionModule};
+33
View File
@@ -0,0 +1,33 @@
const hyperionModule = {
chain: "*",
contract: '*',
action: 'batch',
parser_version: ['3.2', '2.1','1.8','1.7'],
defineQueryPrefix: 'batch',
mappings: {
action: {
"@batch": {
"properties": {
"coopname": {"type": "keyword"},
"action": {"type": "keyword"},
"batch_id": {"type": "long"}
}
}
}
},
handler: (action) => {
const data = action['act']['data'];
action['@batch'] = {
coopname: data.coopname,
action: data.action,
batch_id: data.batch_id,
};
}
};
module.exports = {hyperionModule};
+39
View File
@@ -0,0 +1,39 @@
const hyperionModule = {
chain: "*",
contract: '*',
action: 'decision',
parser_version: ['3.2', '2.1','1.8','1.7'],
defineQueryPrefix: 'decision',
mappings: {
action: {
"@decision": {
"properties": {
"coopname": {"type": "keyword"},
"username": {"type": "keyword"},
"action": {"type": "keyword"},
"decision_id": {"type": "long"},
"batch_id": {"type": "long"},
"decision": {"enabled": false}
}
}
}
},
handler: (action) => {
const data = action['act']['data'];
action['@decision'] = {
coopname: data.coopname,
username: data.username,
action: data.action,
decision_id: data.decision_id,
batch_id: data.batch_id,
decision: data.decision
};
}
};
module.exports = {hyperionModule};
+33
View File
@@ -0,0 +1,33 @@
const hyperionModule = {
chain: "*",
contract: '*',
action: 'exec',
parser_version: ['3.2', '2.1','1.8','1.7'],
defineQueryPrefix: 'exec',
mappings: {
action: {
"@exec": {
"properties": {
"executer": {"type": "keyword"},
"coopname": {"type": "keyword"},
"decision_id": {"type": "long"}
}
}
}
},
handler: (action) => {
const data = action['act']['data'];
action['@exec'] = {
executer: data.executer,
coopname: data.coopname,
decision_id: data.decision_id,
};
}
};
module.exports = {hyperionModule};
+39
View File
@@ -0,0 +1,39 @@
const hyperionModule = {
chain: "*",
contract: '*',
action: 'statement',
parser_version: ['3.2', '2.1','1.8','1.7'],
defineQueryPrefix: 'statement',
mappings: {
action: {
"@statement": {
"properties": {
"coopname": {"type": "keyword"},
"username": {"type": "keyword"},
"action": {"type": "keyword"},
"decision_id": {"type": "long"},
"batch_id": {"type": "long"},
"statement": {"enabled": false}
}
}
}
},
handler: (action) => {
const data = action['act']['data'];
action['@statement'] = {
coopname: data.coopname,
username: data.username,
action: data.action,
decision_id: data.decision_id,
batch_id: data.batch_id,
statement: data.statement
};
}
};
module.exports = {hyperionModule};
+33
View File
@@ -0,0 +1,33 @@
const hyperionModule = {
chain: "*",
contract: '*',
action: 'voteagainst',
parser_version: ['3.2', '2.1','1.8','1.7'],
defineQueryPrefix: 'voteagainst',
mappings: {
action: {
"@voteagainst": {
"properties": {
"coopname": {"type": "keyword"},
"member": {"type": "keyword"},
"decision_id": {"type": "long"}
}
}
}
},
handler: (action) => {
const data = action['act']['data'];
action['@voteagainst'] = {
coopname: data.coopname,
member: data.member,
decision_id: data.decision_id
};
}
};
module.exports = {hyperionModule};
+33
View File
@@ -0,0 +1,33 @@
const hyperionModule = {
chain: "*",
contract: '*',
action: 'votefor',
parser_version: ['3.2', '2.1','1.8','1.7'],
defineQueryPrefix: 'votefor',
mappings: {
action: {
"@votefor": {
"properties": {
"coopname": {"type": "keyword"},
"member": {"type": "keyword"},
"decision_id": {"type": "long"}
}
}
}
},
handler: (action) => {
const data = action['act']['data'];
action['@votefor'] = {
coopname: data.coopname,
member: data.member,
decision_id: data.decision_id
};
}
};
module.exports = {hyperionModule};
+2 -2
View File
@@ -53,12 +53,12 @@ class HyperionModuleLoader {
const key = `${_module.contract}::${_module.action}`;
if (this.chainMappings.has(key)) {
if (this.chainMappings.get(key) === '*' && _module.chain === chainID) {
// console.log('Overwriting module ' + key + ' for ' + _module.chain);
console.log('Overwriting module ' + key + ' for ' + _module.chain);
this.includeModule(_module);
this.chainMappings.set(key, _module.chain);
}
} else {
// console.log('Including module ' + key + ' for ' + _module.chain);
console.log('Including module ' + key + ' for ' + _module.chain);
this.includeModule(_module);
this.chainMappings.set(key, _module.chain);
}
+462 -584
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -215,6 +215,7 @@ export default class HyperionParser extends BaseParser {
event: 'consumed_block',
block_num: result['block_num'],
block_id: result['block_id'],
block_ts: result['block_ts'],
trx_ids: result['trx_ids'],
lib: res.last_irreversible.block_num,
live: process.env.live_mode
+1
View File
@@ -253,6 +253,7 @@ export default class HyperionParser extends BaseParser {
event: 'consumed_block',
block_num: result['block_num'],
block_id: result['block_id'],
block_ts: result['block_ts'],
trx_ids: result['trx_ids'],
lib: res.last_irreversible.block_num,
live: process.env.live_mode
+1
View File
@@ -137,6 +137,7 @@ export default class HyperionParser extends BaseParser {
event: 'consumed_block',
block_num: result['block_num'],
block_id: result['block_id'],
block_ts: result['block_ts'],
trx_ids: result['trx_ids'],
lib: res.last_irreversible.block_num,
live: process.env.live_mode
-1
View File
@@ -192,7 +192,6 @@ export abstract class BaseParser {
if (ds_act) {
if (ds_act.account && ds_act.name && ds_act.authorization) {
console.log(ds_act);
action.act.data = ds_act.data;
}
+569 -2702
View File
File diff suppressed because it is too large Load Diff
+18 -17
View File
@@ -1,6 +1,6 @@
{
"name": "hyperion-history",
"version": "3.3.9",
"version": "3.3.9-8",
"description": "Scalable Full History API Solution for EOSIO based blockchains",
"main": "launcher.js",
"scripts": {
@@ -14,7 +14,7 @@
"fix-permissions": "node scripts/fix-permissions.js"
},
"engines": {
"node": "^18"
"node": ">=18"
},
"author": {
"name": "EOS Rio",
@@ -27,7 +27,7 @@
"license": "MIT",
"dependencies": {
"@elastic/elasticsearch": "^7.17.0",
"@eosrio/node-abieos": "^2.1.1",
"@eosrio/node-abieos": "2.1.1",
"@fastify/autoload": "4.0.1",
"@fastify/cors": "7.0.0",
"@fastify/formbody": "^6.0.1",
@@ -38,8 +38,9 @@
"amqplib": "^0.10.3",
"async": "^3.2.4",
"base-x": "^4.0.0",
"cli-progress": "^3.12.0",
"commander": "^8.3.0",
"cross-fetch": "^3.1.5",
"cross-fetch": "^3.1.8",
"eosjs": "^22.1.0",
"fast-json-stringify": "2.7.13",
"fastify": "3.29.4",
@@ -52,28 +53,28 @@
"lodash": "^4.17.21",
"moment": "^2.29.4",
"nodemailer": "^6.9.0",
"pino-pretty": "^10.2.0",
"portfinder": "^1.0.32",
"pino-pretty": "^9.1.1",
"socket.io": "4.5.4",
"socket.io-client": "4.5.4",
"socket.io": "4.7.2",
"socket.io-client": "4.7.2",
"socket.io-redis": "^6.1.1",
"telegraf": "^4.11.2",
"telegraf": "^4.12.3-canary.1",
"typescript": "^4.5.2",
"uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.19.0",
"ws": "^8.12.0"
"uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.31.0",
"ws": "^8.14.2"
},
"devDependencies": {
"@types/amqplib": "^0.8.2",
"@types/async": "^3.2.16",
"@types/amqplib": "^0.10.2",
"@types/async": "^3.2.18",
"@types/global-agent": "^2.1.1",
"@types/ioredis": "^4.28.10",
"@types/lodash": "^4.14.191",
"@types/node": "^18.11.18",
"@types/nodemailer": "^6.4.7",
"@types/ws": "^8.5.4"
"@types/lodash": "^4.14.197",
"@types/node": "^20.5.7",
"@types/nodemailer": "^6.4.9",
"@types/ws": "^8.5.5"
},
"optionalDependencies": {
"bufferutil": "^4.0.7",
"utf-8-validate": "^5.0.10"
"utf-8-validate": "^6.0.3"
}
}
@@ -5,6 +5,7 @@ from subprocess import Popen, PIPE, STDOUT
from sh import tail
import time
import sys
import shutil
# Import that you stop the indexer before you start running this script
@@ -41,28 +42,71 @@ live_reader = '.live_reader.*'
rewrite = '.rewrite.*'
shutting_down = r'.*Shutting down master.*'
#shutting_down = r'.*Shutting down master.*'
offline = r'.*(offline or unexistent)'
lastblock_indexed = r'.*Last Indexed Block:.* (\d*)' #group the last block indexed for reference.
# Config
hyperionfolder="/home/charles/hyperion-history-api/"
hyperionfolder="/home/charles/hyperion/"
indexer_log_file = "/home/charles/.pm2/logs/wax-indexer-out.log"
ecosystem_file_location = f'{hyperionfolder}ecosystem.config.js'
chain = "wax" #set the Chain name eos or wax
filepath=hyperionfolder+"chains/wax.config.json"
indexer_stop = ["pm2", "trigger", "wax-indexer", "stop"]
indexer_start = [hyperionfolder+"./run.sh", "wax-indexer"]
indexer_start = [f"{hyperionfolder}./run.sh", f'{ecosystem_file_location}', "wax-indexer"]
#indexer_start = ['/usr/bin/pm2', 'start', {ecosystem_file_location}, '--only', 'wax-indexer', '--update-env', '--silent']
tail_indexer_logs = ["tail","-f", indexer_log_file]
hyperion_version = '3.3' #'3.1' or '3.3'
hyperion_version = '3.7' #'3.1' or '3.3'
# No blocks being processed differs between 3.3 and 3.1
if hyperion_version == '3.3':
no_blocks_processed = r'.*No blocks are being processed.*'
else:
if hyperion_version == '3.7':
no_blocks_processed = r'.*-------- BLOCK RANGE COMPLETED -------------.*'
shutting_down = r'.*No more active workers, stopping now...*'
elif hyperion_version == '3.1':
no_blocks_processed = r'.*No blocks processed.*'
shutting_down = r'.*Shutting down master.*'
else:
no_blocks_processed = r'.*-------- BLOCK RANGE COMPLETED -------------.*'
shutting_down = r'.*No more active workers, stopping now...*'
def update_run_script():
# Copy the original run.sh to run.bak
shutil.copy2(f"{hyperionfolder}/run.sh", f"{hyperionfolder}/run.bak")
# Update the contents of run.sh with the new command
with open("run.sh", "w") as f:
f.write(f'''#!/usr/bin/env bash
if [ $# -lt 2 ]; then
echo "Please inform the app name and ecosystem file location. ex: './run.sh indexer /path/to/ecosystem.config.js'"
exit 1
fi
echo -e "\n-->> Starting $1..."
(
set -x
pm2 start "$2" --only "$1" --update-env --silent
)
echo -e "\n-->> Saving pm2 state..."
(
set -x
pm2 save
)
echo -e "\n-->> Reading $2 logs..."
(
set -x
pm2 logs --raw --lines 10 "$2"
)''')
def restore_run_script():
# Restore the original run.sh from run.bak
shutil.copy2(f"{hyperionfolder}/run.bak", f"{hyperionfolder}/run.sh")
# Set ES index pattern
es_index = f'{chain}-block-*'
@@ -208,8 +252,8 @@ def spinning_cursor(run):
sys.stdout.flush()
def startRewrite(gt,lt):
run = Popen(indexer_start,stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
time.sleep(10)
Popen(indexer_start,stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
time.sleep(2)
logtail = Popen(tail_indexer_logs, stdin=PIPE, stdout=PIPE, stderr=STDOUT)
completedcount = 0
for linenum, line in enumerate(logtail.stdout):
+5 -1
View File
@@ -1,7 +1,11 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
+1 -1
View File
File diff suppressed because one or more lines are too long
-259
View File
@@ -1,259 +0,0 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const commander_1 = require("commander");
const path_1 = __importDefault(require("path"));
const promises_1 = require("fs/promises");
const fs_1 = require("fs");
const eosjs_1 = require("eosjs");
const cross_fetch_1 = __importDefault(require("cross-fetch"));
const ws_1 = __importDefault(require("ws"));
const program = new commander_1.Command();
const chainsDir = path_1.default.join(path_1.default.resolve(), 'chains');
async function getConnections() {
const connectionsJsonFile = await (0, promises_1.readFile)(path_1.default.join(path_1.default.resolve(), 'connections.json'));
return JSON.parse(connectionsJsonFile.toString());
}
async function getExampleConfig() {
const exampleChainData = await (0, promises_1.readFile)(path_1.default.join(chainsDir, 'example.config.json'));
return JSON.parse(exampleChainData.toString());
}
async function listChains(flags) {
const dirdata = await (0, promises_1.readdir)(chainsDir);
const connections = await getConnections();
const exampleChain = await getExampleConfig();
const configuredTable = [];
const pendingTable = [];
for (const file of dirdata.filter(f => f.endsWith('.json'))) {
if (file === 'example.config.json') {
continue;
}
try {
const fileData = await (0, promises_1.readFile)(path_1.default.join(chainsDir, file));
const jsonData = JSON.parse(fileData.toString());
const chainName = jsonData.settings.chain;
const chainConn = connections.chains[chainName];
const fullChainName = jsonData.api.chain_name;
// validate configs with example
let missingSections = 0;
let missingFields = 0;
for (const key in exampleChain) {
if (!jsonData[key]) {
if (flags.fixMissingFields) {
jsonData[key] = exampleChain[key];
}
else {
console.log(`⚠ Section ${key} missing on ${file}`);
missingSections++;
}
}
else {
for (const subKey in exampleChain[key]) {
if (!jsonData[key].hasOwnProperty(subKey)) {
if (flags.fixMissingFields) {
jsonData[key][subKey] = exampleChain[key][subKey];
}
else {
console.log(`⚠ Field ${subKey} missing from ${key} on ${file} (default = ${exampleChain[key][subKey]})`);
missingFields++;
}
}
}
}
}
if (flags.fixMissingFields) {
await (0, promises_1.writeFile)(path_1.default.join(chainsDir, file), JSON.stringify(jsonData, null, 2));
}
const common = {
full_name: fullChainName,
name: chainName,
parser: jsonData.settings.parser,
abi_scan: jsonData.indexer.abi_scan_mode,
live_reader: jsonData.indexer.live_reader,
start_on: jsonData.indexer.start_on,
stop_on: jsonData.indexer.stop_on
};
if (chainConn) {
configuredTable.push({
...common,
nodeos_http: chainConn?.http,
nodeos_ship: chainConn?.ship,
chain_id: chainConn?.chain_id.substr(0, 16) + '...'
});
}
else {
pendingTable.push(common);
}
}
catch (e) {
console.log(`Failed to parse ${file} - ${e.message}`);
}
}
console.log(' >>>> Fully Configured Chains');
console.table(configuredTable);
if (!flags.valid) {
console.log(' >>>> Pending Configuration Chains');
console.table(pendingTable);
}
}
async function newChain(shortName, options) {
console.log(`Creating new config for ${shortName}...`);
const targetPath = path_1.default.join(chainsDir, `${shortName}.config.json`);
if ((0, fs_1.existsSync)(targetPath)) {
console.error(`Chain config for ${shortName} already defined!`);
process.exit(0);
}
// read example
const exampleChain = await getExampleConfig();
// read connections.json
const connections = await getConnections();
if (connections.chains[shortName]) {
console.log('Connections already defined!');
console.log(connections.chains[shortName]);
process.exit(0);
}
else {
connections.chains[shortName] = {
name: '',
ship: '',
http: '',
chain_id: '',
WS_ROUTER_HOST: '127.0.0.1',
WS_ROUTER_PORT: 7001
};
}
const jsonData = exampleChain;
jsonData.settings.chain = shortName;
if (options.http) {
if (options.http.startsWith('http://') || options.http.startsWith('https://')) {
console.log(`Testing connection on ${options.http}`);
}
else {
console.error(`Invalid HTTP Endpoint [${options.http}] - Url must start with either http:// or https://`);
process.exit(1);
}
// test nodeos availability
const jsonRpc = new eosjs_1.JsonRpc(options.http, { fetch: cross_fetch_1.default });
const info = await jsonRpc.get_info();
jsonData.api.chain_api = options.http;
connections.chains[shortName].chain_id = info.chain_id;
connections.chains[shortName].http = options.http;
if (info.server_version_string.includes('2.1')) {
jsonData.settings.parser = '2.1';
}
else {
jsonData.settings.parser = '1.8';
}
console.log(`Parser version set to ${jsonData.settings.parser}`);
console.log(`Chain ID: ${info.chain_id}`);
}
if (options.ship) {
if (options.ship.startsWith('ws://') || options.ship.startsWith('wss://')) {
console.log(`Testing connection on ${options.ship}`);
}
else {
console.error(`Invalid WS Endpoint [${options.ship}] - Url must start with either ws:// or wss://`);
process.exit(1);
}
// test ship availability
const status = await new Promise(resolve => {
const ws = new ws_1.default(options.ship);
ws.on("message", (data) => {
try {
const abi = JSON.parse(data.toString());
if (abi.version) {
console.log(`Received SHIP Abi Version: ${abi.version}`);
resolve(true);
}
else {
resolve(false);
}
}
catch (e) {
console.log(e.message);
resolve(false);
}
ws.close();
});
ws.on("error", err => {
console.log(err);
ws.close();
resolve(false);
});
});
if (status) {
connections.chains[shortName].ship = options.ship;
}
else {
console.log(`Invalid SHIP Endpoint [${options.ship}]`);
}
}
const fullNameArr = [];
shortName.split('-').forEach((word) => {
fullNameArr.push(word[0].toUpperCase() + word.substr(1));
});
const fullChainName = fullNameArr.join(' ');
jsonData.api.chain_name = fullChainName;
connections.chains[shortName].name = fullChainName;
console.log(connections.chains[shortName]);
console.log('Saving connections.json...');
await (0, promises_1.writeFile)(path_1.default.join(path_1.default.resolve(), 'connections.json'), JSON.stringify(connections, null, 2));
console.log(`Saving chains/${shortName}.config.json...`);
await (0, promises_1.writeFile)(targetPath, JSON.stringify(jsonData, null, 2));
}
async function rmChain(shortName) {
console.log(`Removing config for ${shortName}...`);
const targetPath = path_1.default.join(chainsDir, `${shortName}.config.json`);
if (!(0, fs_1.existsSync)(targetPath)) {
console.error(`Chain config for ${shortName} not found!`);
process.exit(0);
}
// create backups
const backupDir = path_1.default.join(path_1.default.resolve(), 'configuration_backups');
if (!(0, fs_1.existsSync)(backupDir)) {
await (0, promises_1.mkdir)(backupDir);
}
const connections = await getConnections();
try {
const chainConn = connections.chains[shortName];
const now = Date.now();
if (chainConn) {
await (0, promises_1.writeFile)(path_1.default.join(backupDir, `${shortName}_${now}_connections.json`), JSON.stringify(chainConn, null, 2));
}
await (0, promises_1.cp)(targetPath, path_1.default.join(backupDir, `${shortName}_${now}_config.json`));
}
catch (e) {
console.log(e.message);
console.log('Failed to create backups! Aborting!');
process.exit(1);
}
console.log(`Deleting ${targetPath}`);
await (0, promises_1.rm)(targetPath);
delete connections.chains[shortName];
console.log('Saving connections.json...');
await (0, promises_1.writeFile)(path_1.default.join(path_1.default.resolve(), 'connections.json'), JSON.stringify(connections, null, 2));
console.log(`${shortName} removal completed!`);
}
// main program
(() => {
const list = program.command('list').alias('ls');
list.command('chains')
.option('--valid', 'only show valid chains')
.option('--fix-missing-fields', 'set defaults on missing fields')
.description('list configured chains')
.action(listChains);
const newCmd = program.command('new').alias('n');
newCmd.command('chain <shortName>')
.description('initialize new chain config based on example')
.option('--http <http_endpoint>', 'define chain api http endpoint')
.option('--ship <ship_endpoint>', 'define state history ws endpoint')
.action(newChain);
const remove = program.command('remove').alias('rm');
remove.command('chain <shortName>')
.description('backup and delete chain configuration')
.action(rmChain);
program.parse(process.argv);
})();
//# sourceMappingURL=hyp-config.js.map
File diff suppressed because one or more lines are too long
+341 -20
View File
@@ -3,17 +3,28 @@ import path from "path";
import {cp, mkdir, readdir, readFile, rm, writeFile} from "fs/promises";
import {HyperionConfig} from "../interfaces/hyperionConfig";
import {HyperionConnections} from "../interfaces/hyperionConnections";
import {existsSync} from "fs";
import {copyFileSync, existsSync, rmSync} from "fs";
import {JsonRpc} from "eosjs";
import fetch from "cross-fetch";
import WebSocket from 'ws';
import * as readline from "readline";
import * as amqp from "amqplib";
import IORedis from "ioredis";
import {Client} from "@elastic/elasticsearch";
const program = new Command();
const chainsDir = path.join(path.resolve(), 'chains');
const connectionsPath = path.join(path.resolve(), 'connections.json');
const exampleConnectionsPath = path.join(path.resolve(), 'example-connections.json');
const backupDir = path.join(path.resolve(), 'configuration_backups');
async function getConnections(): Promise<HyperionConnections> {
const connectionsJsonFile = await readFile(path.join(path.resolve(), 'connections.json'));
return JSON.parse(connectionsJsonFile.toString());
if (existsSync(connectionsPath)) {
const connectionsJsonFile = await readFile(connectionsPath);
return JSON.parse(connectionsJsonFile.toString());
} else {
return null;
}
}
async function getExampleConfig(): Promise<HyperionConfig> {
@@ -85,7 +96,7 @@ async function listChains(flags) {
...common,
nodeos_http: chainConn?.http,
nodeos_ship: chainConn?.ship,
chain_id: chainConn?.chain_id.substr(0,16) + '...'
chain_id: chainConn?.chain_id.substr(0, 16) + '...'
});
} else {
pendingTable.push(common);
@@ -119,6 +130,11 @@ async function newChain(shortName, options) {
// read connections.json
const connections = await getConnections();
if (!connections) {
console.log(`The connections.json file is not present, please run "./hyp-config connections init" to configure it.`);
process.exit(1);
}
if (connections.chains[shortName]) {
console.log('Connections already defined!');
console.log(connections.chains[shortName]);
@@ -130,7 +146,8 @@ async function newChain(shortName, options) {
http: '',
chain_id: '',
WS_ROUTER_HOST: '127.0.0.1',
WS_ROUTER_PORT: 7001
WS_ROUTER_PORT: 7001,
control_port: 7002
};
}
@@ -147,18 +164,25 @@ async function newChain(shortName, options) {
}
// test nodeos availability
const jsonRpc = new JsonRpc(options.http, {fetch});
const info = await jsonRpc.get_info();
jsonData.api.chain_api = options.http;
connections.chains[shortName].chain_id = info.chain_id;
connections.chains[shortName].http = options.http;
if (info.server_version_string.includes('2.1')) {
jsonData.settings.parser = '2.1';
} else {
jsonData.settings.parser = '1.8';
try {
const jsonRpc = new JsonRpc(options.http, {fetch});
const info = await jsonRpc.get_info();
jsonData.api.chain_api = options.http;
connections.chains[shortName].chain_id = info.chain_id;
connections.chains[shortName].http = options.http;
if (info.server_version_string.includes('v3')) {
jsonData.settings.parser = '3.2';
} else if (info.server_version_string.includes('v2.1')) {
jsonData.settings.parser = '2.1';
} else {
jsonData.settings.parser = '1.8';
}
console.log(`Parser version set to ${jsonData.settings.parser}`);
console.log(`Chain ID: ${info.chain_id}`);
} catch (e) {
console.error(`Invalid HTTP Endpoint [${options.http}] - ${e.message}`);
process.exit(1);
}
console.log(`Parser version set to ${jsonData.settings.parser}`);
console.log(`Chain ID: ${info.chain_id}`)
}
if (options.ship) {
@@ -204,7 +228,7 @@ async function newChain(shortName, options) {
const fullNameArr = [];
shortName.split('-').forEach((word: string) => {
fullNameArr.push(word[0].toUpperCase() + word.substr(1));
});
});
const fullChainName = fullNameArr.join(' ');
jsonData.api.chain_name = fullChainName;
@@ -213,7 +237,7 @@ async function newChain(shortName, options) {
console.log(connections.chains[shortName]);
console.log('Saving connections.json...');
await writeFile(path.join(path.resolve(), 'connections.json'), JSON.stringify(connections, null, 2));
await writeFile(connectionsPath, JSON.stringify(connections, null, 2));
console.log(`Saving chains/${shortName}.config.json...`);
await writeFile(targetPath, JSON.stringify(jsonData, null, 2));
}
@@ -228,7 +252,6 @@ async function rmChain(shortName) {
}
// create backups
const backupDir = path.join(path.resolve(), 'configuration_backups');
if (!existsSync(backupDir)) {
await mkdir(backupDir);
}
@@ -254,12 +277,310 @@ async function rmChain(shortName) {
await rm(targetPath);
delete connections.chains[shortName];
console.log('Saving connections.json...');
await writeFile(path.join(path.resolve(), 'connections.json'), JSON.stringify(connections, null, 2));
await writeFile(connectionsPath, JSON.stringify(connections, null, 2));
console.log(`${shortName} removal completed!`);
}
async function getExampleConnections(): Promise<HyperionConnections | null> {
try {
const connectionsJsonFile = await readFile(exampleConnectionsPath);
return JSON.parse(connectionsJsonFile.toString());
} catch (e: any) {
return null;
}
}
async function checkAMQP(conn: HyperionConnections): Promise<boolean> {
console.log(`\n[info] [RABBIT:AMQP] - Testing rabbitmq amqp connection...`);
let frameMaxValue = '0x10000';
if (conn.amqp.frameMax) {
frameMaxValue = conn.amqp.frameMax;
}
const u = encodeURIComponent(conn.amqp.user);
const p = encodeURIComponent(conn.amqp.pass);
const v = encodeURIComponent(conn.amqp.vhost)
const amqp_url = `amqp://${u}:${p}@${conn.amqp.host}/${v}?frameMax=${frameMaxValue}`;
try {
const connection = await amqp.connect(amqp_url);
if (connection) {
console.log('[info] [RABBIT:AMQP] - Connection established!');
await connection.close();
console.log(`\n[info] [RABBIT:HTTP] - Testing rabbitmq http api connection...`);
const apiUrl = `${conn.amqp.protocol}://${conn.amqp.api}/api/nodes`;
let headers = new Headers();
headers.set('Authorization', 'Basic ' + btoa(conn.amqp.user + ":" + conn.amqp.pass));
const data = await fetch(apiUrl, {
method: 'GET',
headers: headers
});
const resp = await data.json();
if (resp.length > 0) {
for (const node of resp) {
console.log(`[info] [RABBIT:HTTP] - Node name: ${node.name} (running: ${node.running})`);
if (!node.running) {
return false;
}
}
return true;
} else {
return false;
}
} else {
return false;
}
} catch (reason: any) {
console.log('[error] [RABBIT] - ' + reason.message);
return false;
}
}
async function checkES(conn: HyperionConnections): Promise<boolean> {
console.log(`\n[info] [ES] - Testing elasticsearch connection...`);
let es_url;
const _es = conn.elasticsearch;
if (!_es.protocol) {
_es.protocol = 'http';
}
if (_es.user !== '') {
es_url = `${_es.protocol}://${_es.user}:${_es.pass}@${_es.host}`;
} else {
es_url = `${_es.protocol}://${_es.host}`
}
// console.log(`Prepared client: ${es_url}`);
const client = new Client({
node: es_url,
ssl: {rejectUnauthorized: false}
});
try {
const result = await client.cat.health();
console.log(`[info] [ES] - ${result.body.trim()}`);
console.log('[info] [ES] - Connection established!');
return true;
} catch (reason: any) {
console.log('[error] [ES] - ' + reason.message);
return false;
}
}
async function checkRedis(conn: HyperionConnections) {
console.log(`\n[info] [REDIS] - Testing redis connection...`);
try {
const ioRedisClient = new IORedis(conn.redis);
const pingResult = await ioRedisClient.ping();
if (pingResult === 'PONG') {
console.log('[info] [REDIS] - Connection established!');
ioRedisClient.disconnect();
return true;
} else {
console.log('[error] [REDIS] - PONG expected, got: ' + pingResult);
return false;
}
} catch (reason: any) {
console.log('[error] [REDIS] - ' + reason.message);
return false;
}
}
async function initConfig() {
const connections = await getConnections();
if (connections) {
console.log('connections.json already created!\n use "./hyp-config connections reset" to revert to default');
return;
}
const exampleConn = await getExampleConnections();
const rl = readline.createInterface({input: process.stdin, output: process.stdout});
const prompt = (query: string) => new Promise((resolve) => rl.question(query, resolve));
const conn = exampleConn;
if (!conn) {
console.log('No example-connections.json to use as reference!');
process.exit(1);
}
conn.chains = {};
// check rabbitmq
let amqp_state = false;
while (!amqp_state) {
amqp_state = await checkAMQP(conn);
if (!amqp_state) {
const amqp_user = await prompt('\n > Enter the RabbitMQ user (or press ENTER to use "hyperion_user"): ');
if (amqp_user) {
conn.amqp.user = amqp_user as string;
} else {
conn.amqp.user = 'hyperion_user'
}
const amqp_pass = await prompt(' > Enter the RabbitMQ password (or press ENTER to use "hyperion_password"): ');
if (amqp_pass) {
conn.amqp.pass = amqp_pass as string;
} else {
conn.amqp.pass = 'hyperion_password'
}
const amqp_vhost = await prompt(' > Enter the RabbitMQ vhost (or press ENTER to use "hyperion"): ');
if (amqp_vhost) {
conn.amqp.vhost = amqp_vhost as string;
} else {
conn.amqp.vhost = 'hyperion'
}
console.log('\n------ current RabbitMQ config -----');
console.log(conn.amqp);
console.log('--------------------------------------');
}
}
console.log(`\n RabbitMQ ✅`);
// check elasticsearch
let elastic_state = false;
while (!elastic_state) {
elastic_state = await checkES(conn);
if (!elastic_state) {
const es_user = await prompt('\n > Enter the elasticsearch user (or press ENTER to use "elastic"): ');
if (es_user) {
conn.elasticsearch.user = es_user as string;
} else {
conn.elasticsearch.user = 'elastic'
}
const es_pass = await prompt(' > Enter the elasticsearch password: ');
if (es_pass) {
conn.elasticsearch.pass = es_pass as string;
}
const es_proto = await prompt(' > Do you want to use http or https?\n1 = http\n2 = https (default)\n');
if (es_proto === "1") {
conn.elasticsearch.protocol = 'http';
} else {
conn.elasticsearch.protocol = 'https';
}
console.log('\n------ current elasticsearch config -----');
console.log(conn.elasticsearch);
console.log('-------------------------------------------');
}
}
console.log(`\n Elasticsearch ✅`);
// check redis connection
let redis_state = false;
while (!redis_state) {
redis_state = await checkRedis(conn);
if (!redis_state) {
const redis_host = await prompt('\n > Enter the Redis Server address (or press ENTER to use "127.0.0.1"): ');
if (redis_host) {
conn.redis.host = redis_host as string;
} else {
conn.redis.host = '127.0.0.1';
}
const redis_port = await prompt(' > Enter the Redis Server port (or press ENTER to use "6379"): ');
if (redis_port) {
conn.redis.port = parseInt(redis_port as string);
} else {
conn.redis.port = 6379;
}
console.log('\n------ current redis config -----');
console.log(conn.redis);
console.log('-----------------------------------');
}
}
console.log(`\n Redis ✅`);
console.log('Init completed! Saving configuration...');
await writeFile(connectionsPath, JSON.stringify(conn, null, 2));
console.log('✅ ✅');
rl.close();
}
async function testConnections() {
if (existsSync(connectionsPath)) {
try {
const conn = await getConnections();
if (conn) {
const results = {
redis: await checkRedis(conn),
elasticsearch: await checkES(conn),
rabbitmq: await checkAMQP(conn)
}
console.log('\n------ Testing Completed -----');
console.table(results);
} else {
console.log(`The connections.json was not found!.`);
}
} catch (e: any) {
console.log(e.message);
}
} else {
console.log(`The connections.json file is not present, please run "./hyp-config connections init" to configure it.`);
}
}
async function resetConnections() {
try {
// create backups
if (!existsSync(backupDir)) {
await mkdir(backupDir);
}
if (existsSync(connectionsPath)) {
const rl = readline.createInterface({input: process.stdin, output: process.stdout});
const prompt = (query: string) => new Promise((resolve) => rl.question(query, resolve));
const confirmation = await prompt('Are you sure you want to reset the connection configuration? Type "YES" to confirm.\n');
if (confirmation === 'YES') {
copyFileSync(connectionsPath, path.join(backupDir, 'connections.json.bak'));
rmSync(connectionsPath);
console.log('connections.json removed, please use "./hyp-config connections init" to reconfigure');
} else {
console.log('Operation canceled. No files were removed.');
}
rl.close();
} else {
console.log(`The connections.json file is not present, please run "./hyp-config connections init" to configure it.`);
}
} catch (e: any) {
console.log(e);
}
}
// main program
(() => {
const connections = program.command('connections');
// ./hyp-config connections init
connections
.command('init')
.description('create and test the connections.json file')
.action(initConfig);
// ./hyp-config connections test
connections
.command('test')
.description('test connections to the hyperion infrastructure')
.action(testConnections);
// ./hyp-config connections reset
connections
.command('reset')
.description('remove the connections.json file')
.action(resetConnections);
const list = program.command('list').alias('ls');
list.command('chains')
.option('--valid', 'only show valid chains')
+954
View File
@@ -0,0 +1,954 @@
import { Command } from 'commander';
import { readFileSync } from 'node:fs';
import { Client, estypes } from '@elastic/elasticsearch';
// @ts-ignore
import cliProgress from 'cli-progress';
import { JsonRpc } from 'eosjs';
import fetch from 'cross-fetch';
import { existsSync, mkdirSync, writeFileSync } from 'fs';
import { HyperionBlock } from './repair-cli/interfaces.js';
import {
getBlocks,
getFirstIndexedBlock,
getLastIndexedBlock,
initESClient,
readChainConfig,
readConnectionConfig,
} from './repair-cli/functions.js';
import { SearchResponse } from '@elastic/elasticsearch/api/types';
import { WebSocket } from 'ws';
import { block } from '../definitions/index-templates';
const progressBar = new cliProgress.SingleBar(
{},
cliProgress.Presets.shades_classic
);
const program = new Command();
const errorRanges: any[] = [];
let pendingBlock: HyperionBlock | null = null;
const missingBlocks: {
start: number;
end: number;
}[] = [];
async function run(
client: Client,
rpc: JsonRpc,
indexName: string,
blockInit: number,
firstBlock: number,
qtdTotal: any,
loop: any = 1
) {
let blockInitial = blockInit;
let blockFinal = blockInitial - qtdTotal;
const tRef = process.hrtime.bigint();
progressBar.start(loop, 0);
for (let i: any = 1; i <= loop; i++) {
if (blockFinal < firstBlock) {
blockFinal = firstBlock;
}
try {
let { body } = await getBlocks(
client,
indexName,
blockInitial,
blockFinal,
qtdTotal
);
let {
hits: { hits },
} = body;
const blocks = hits.map((obj: any) => obj._source);
await findForksOnRange(blocks, rpc);
blockInitial = blockFinal;
blockFinal = blockInitial - qtdTotal;
progressBar.update(i);
} catch (e: any) {
console.log('Error: ', e);
process.exit(1);
}
}
progressBar.stop();
console.log('=========== Forked Ranges ===========');
console.table(errorRanges);
console.log('=========== Missing Ranges ==========');
console.table(missingBlocks);
const tDiff = Number(process.hrtime.bigint() - tRef) / 1000000;
console.log(`Total time: ${Math.round(tDiff / 1000)}s`);
}
async function findForksOnRange(blocks: HyperionBlock[], rpc: JsonRpc) {
const removals: Set<string> = new Set();
let start = null;
let end = null;
if (
blocks.length > 0 &&
pendingBlock &&
blocks[0].block_num !== pendingBlock.block_num
) {
blocks.unshift(pendingBlock);
}
let i = 0;
for (const currentBlock of blocks) {
const currentBlockNumber = currentBlock.block_num;
// console.log('current -> ', currentBlockNumber);
const previousBlock = blocks[i + 1];
if (!previousBlock) {
pendingBlock = currentBlock;
// console.log('pending -> ', pendingBlock.block_num);
return;
}
i++;
// console.log('previous -> ', previousBlock.block_num);
if (previousBlock.block_num !== currentBlockNumber - 1) {
console.log(
`\nBlock number mismatch, expected: ${
currentBlockNumber - 1
} got ${previousBlock.block_num}`
);
missingBlocks.push({
start: previousBlock.block_num + 1,
end: currentBlockNumber - 1,
});
continue;
}
if (previousBlock && previousBlock.block_id !== currentBlock.prev_id) {
if (start === null) {
start = currentBlockNumber - 1;
const blockData = await rpc.get_block_info(start);
if (blockData && blockData.id !== previousBlock.block_id) {
removals.add(previousBlock.block_id);
}
}
} else {
if (start) {
const blockData = await rpc.get_block_info(currentBlockNumber);
if (blockData) {
if (blockData.id !== currentBlock.block_id) {
removals.add(currentBlock.block_id);
} else {
end = currentBlockNumber + 1;
const range = { start, end, ids: [...removals] };
errorRanges.push(range);
removals.clear();
// console.log(`\n ⚠️⚠️ Forked at ${range.start} to ${range.end}`);
start = null;
end = null;
}
}
}
}
}
}
async function scanChain(chain: string, args: any) {
const chainConfig = readChainConfig(chain);
console.log(chainConfig.settings.index_version);
const config = readConnectionConfig();
const client = initESClient(config);
const jsonRpc = new JsonRpc(config.chains[chain].http, { fetch });
const ping = await client.ping();
if (!ping) {
console.log('Could not connect to ElasticSearch');
process.exit();
}
const blockIndex = `${chain}-block-${chainConfig.settings.index_version}`;
let firstBlock;
let lastBlock;
if (args.first) {
// reduce by 2 to account for the fork end validation
firstBlock = args.first - 2;
} else {
firstBlock = await getFirstIndexedBlock(client, blockIndex);
}
if (args.last) {
lastBlock = args.last;
} else {
lastBlock = await getLastIndexedBlock(client, blockIndex);
}
const totalBlocks = lastBlock - firstBlock;
let batchSize = 1000;
if (args.batch) {
batchSize = args.batch;
}
const numberOfBatches = Math.ceil(totalBlocks / batchSize);
console.log('Range:', firstBlock, lastBlock);
console.log('Total Blocks:', totalBlocks);
console.log('Batch Size:', batchSize);
console.log('Number of Batches:', numberOfBatches);
await run(
client,
jsonRpc,
blockIndex,
lastBlock,
firstBlock,
batchSize,
numberOfBatches
);
console.log(`Finished checking forked blocks!`);
if (errorRanges.length > 0 || missingBlocks.length > 0) {
if (!existsSync('.repair')) {
mkdirSync('.repair');
}
}
if (errorRanges.length > 0) {
let path = `.repair/${chain}-${
firstBlock + 2
}-${lastBlock}-forked-blocks.json`;
if (args.outFile) {
path = args.outFile + '-forked-blocks.json';
}
writeFileSync(path, JSON.stringify(errorRanges));
}
if (missingBlocks.length > 0) {
let path = `.repair/${chain}-${
firstBlock + 2
}-${lastBlock}-missing-blocks.json`;
if (args.outFile) {
path = args.outFile + '-missing-blocks.json';
}
writeFileSync(path, JSON.stringify(missingBlocks));
}
}
async function repairMissing(chain: string, file: string, args: any) {
const chainConfig = readChainConfig(chain);
const config = readConnectionConfig();
const client = initESClient(config);
const ping = await client.ping();
if (!ping) {
console.log('Could not connect to ElasticSearch');
process.exit();
}
await fillMissingBlocksFromFile(args.host, chain, file, args.dry);
}
async function repairChain(chain: string, file: string, args: any) {
const chainConfig = readChainConfig(chain);
const config = readConnectionConfig();
const client = initESClient(config);
const ping = await client.ping();
if (!ping) {
console.log('Could not connect to ElasticSearch');
process.exit();
}
if (args.checkTasks) {
const tasks = await client.tasks.list();
if (tasks.body && tasks.body.nodes) {
for (let node in tasks.body.nodes) {
const nodeTasks = tasks.body.nodes[node].tasks;
console.log(nodeTasks);
}
}
process.exit(0);
}
const forkedBlocks = JSON.parse(readFileSync(file).toString());
const blockIndex = `${chain}-block-${chainConfig.settings.index_version}`;
let deleteBlocks = 0;
let deleteActions = 0;
let deleteDeltas = 0;
let deleteAbis = 0;
let deleteAccounts = 0;
let deleteVoters = 0;
let deleteProposals = 0;
let deleteLinks = 0;
let deletePermissions = 0;
for (const range of forkedBlocks) {
// ACTIONS
const searchActions = {
index: `${chain}-action-${chainConfig.settings.index_version}-*`,
size: 0,
track_total_hits: true,
body: {
query: {
bool: {
must: [
{
range: {
block_num: {
lte: range.start,
gte: range.end,
},
},
},
],
},
},
},
};
if (args.dry) {
const resultActions = await client.search<any>(searchActions);
if (
(resultActions.body.hits.total as estypes.SearchTotalHits)
?.value > 0
) {
deleteActions += (
resultActions.body.hits.total as estypes.SearchTotalHits
)?.value;
}
} else {
const indexExists = await client.indices.exists({
index: searchActions.index,
});
if (indexExists.body) {
delete searchActions.size;
const deletedActionsResult = await client.deleteByQuery(
searchActions
);
if (
deletedActionsResult &&
deletedActionsResult.body.deleted &&
deletedActionsResult.body.deleted > 0
) {
deleteActions += deletedActionsResult.body.deleted;
}
} else {
console.log(
`Index ${searchActions.index} doesn't exist. Unable to delete.`
);
}
}
// DELTAS
const searchDeltas = {
index: `${chain}-delta-${chainConfig.settings.index_version}-*`,
size: 0,
track_total_hits: true,
body: {
query: {
bool: {
must: [
{
range: {
block_num: {
lte: range.start,
gte: range.end,
},
},
},
],
},
},
},
};
if (args.dry) {
const resultDeltas = await client.search<any>(searchDeltas);
if (
(resultDeltas.body.hits.total as estypes.SearchTotalHits)
?.value > 0
) {
deleteDeltas += (
resultDeltas.body.hits.total as estypes.SearchTotalHits
)?.value;
}
} else {
const indexExists = await client.indices.exists({
index: searchDeltas.index,
});
if (indexExists.body) {
delete searchDeltas.size;
const deletedDeltasResult = await client.deleteByQuery(
searchDeltas
);
if (
deletedDeltasResult &&
deletedDeltasResult.body.deleted &&
deletedDeltasResult.body.deleted > 0
) {
deleteDeltas += deletedDeltasResult.body.deleted;
}
} else {
console.log(
`Index ${searchDeltas.index} doesn't exist. Unable to delete.`
);
}
}
// ABIS
const searchAbis = {
index: `${chain}-abi-${chainConfig.settings.index_version}`,
size: 0,
track_total_hits: true,
body: {
query: {
bool: {
must: [
{
range: {
block: { lte: range.start, gte: range.end },
},
},
],
},
},
},
};
if (args.dry) {
const resultAbis = await client.search<any>(searchAbis);
if (
(resultAbis.body.hits.total as estypes.SearchTotalHits)?.value >
0
) {
deleteAbis += (
resultAbis.body.hits.total as estypes.SearchTotalHits
)?.value;
console.log('ABIs', { lte: range.start, gte: range.end });
}
} else {
const indexExists = await client.indices.exists({
index: searchAbis.index,
});
if (indexExists.body) {
delete searchAbis.size;
const deletedAbisResult = await client.deleteByQuery(
searchAbis
);
if (
deletedAbisResult &&
deletedAbisResult.body.deleted &&
deletedAbisResult.body.deleted > 0
) {
deleteAbis += deletedAbisResult.body.deleted;
}
} else {
console.log(
`Index ${searchAbis.index} doesn't exist. Unable to delete.`
);
}
}
// ACCOUNTS
const searchAccounts = {
index: `${chain}-table-accounts-${chainConfig.settings.index_version}`,
size: 0,
track_total_hits: true,
body: {
query: {
bool: {
must: [
{
range: {
block_num: {
lte: range.start,
gte: range.end,
},
},
},
],
},
},
},
};
if (args.dry) {
const resultAccounts = await client.search<any>(searchAccounts);
if (
(resultAccounts.body.hits.total as estypes.SearchTotalHits)
?.value > 0
) {
console.log(
'[WARNING]',
(resultAccounts.body.hits.total as estypes.SearchTotalHits)
?.value,
'accounts needs to be updated'
);
deleteAccounts += (
resultAccounts.body.hits.total as estypes.SearchTotalHits
)?.value;
}
} else {
const indexExists = await client.indices.exists({
index: searchAccounts.index,
});
if (indexExists.body) {
delete searchAccounts.size;
const deletedAccountsResult = await client.deleteByQuery(
searchAccounts
);
if (
deletedAccountsResult &&
deletedAccountsResult.body.deleted &&
deletedAccountsResult.body.deleted > 0
) {
deleteAccounts += deletedAccountsResult.body.deleted;
}
}else {
console.log(
`Index ${searchAccounts.index} doesn't exist. Unable to delete.`
);
}
}
// VOTERS
const searchVoters = {
index: `${chain}-table-voters-${chainConfig.settings.index_version}`,
size: 0,
track_total_hits: true,
body: {
query: {
bool: {
must: [
{
range: {
block_num: {
lte: range.start,
gte: range.end,
},
},
},
],
},
},
},
};
if (args.dry) {
const resultVoters = await client.search<any>(searchVoters);
if (
(resultVoters.body.hits.total as estypes.SearchTotalHits)
?.value > 0
) {
console.log(
'[WARNING]',
(resultVoters.body.hits.total as estypes.SearchTotalHits)
?.value,
'voters needs to be updated'
);
deleteVoters += (
resultVoters.body.hits.total as estypes.SearchTotalHits
)?.value;
}
} else {
const indexExists = await client.indices.exists({
index: searchVoters.index,
});
if (indexExists.body) {
delete searchVoters.size;
const deletedVotersResult = await client.deleteByQuery(
searchVoters
);
if (
deletedVotersResult &&
deletedVotersResult.body.deleted &&
deletedVotersResult.body.deleted > 0
) {
deleteVoters += deletedVotersResult.body.deleted;
}
}else {
console.log(
`Index ${searchVoters.index} doesn't exist. Unable to delete.`
);
}
}
// PROPOSALS
const searchProposals = {
index: `${chain}-table-proposals-${chainConfig.settings.index_version}`,
size: 0,
track_total_hits: true,
body: {
query: {
bool: {
must: [
{
range: {
block_num: {
lte: range.start,
gte: range.end,
},
},
},
],
},
},
},
};
if (args.dry) {
const resultProposals = await client.search<any>(searchProposals);
if (
(resultProposals.body.hits.total as estypes.SearchTotalHits)
?.value > 0
) {
console.log(
'[WARNING]',
(resultProposals.body.hits.total as estypes.SearchTotalHits)
?.value,
'proposals needs to be updated'
);
deleteProposals += (
resultProposals.body.hits.total as estypes.SearchTotalHits
)?.value;
}
} else {
const indexExists = await client.indices.exists({index: searchProposals.index});
if (indexExists.body) {
delete searchProposals.size;
const deletedProposalsResult = await client.deleteByQuery(
searchProposals
);
if (
deletedProposalsResult &&
deletedProposalsResult.body.deleted &&
deletedProposalsResult.body.deleted > 0
) {
deleteProposals += deletedProposalsResult.body.deleted;
}
}else { console.log(`Index ${searchProposals.index} doesn't exist. Unable to delete.`);}
}
// LINKS
const searchLinks = {
index: `${chain}-link-${chainConfig.settings.index_version}`,
size: 0,
track_total_hits: true,
body: {
query: {
bool: {
must: [
{
range: {
block_num: {
lte: range.start,
gte: range.end,
},
},
},
],
},
},
},
};
if (args.dry) {
const resultLinks = await client.search<any>(searchLinks);
if (
(resultLinks.body.hits.total as estypes.SearchTotalHits)
?.value > 0
) {
console.log(
'[WARNING]',
(resultLinks.body.hits.total as estypes.SearchTotalHits)
?.value,
'links needs to be updated'
);
deleteLinks += (
resultLinks.body.hits.total as estypes.SearchTotalHits
)?.value;
}
} else {
const indexExists = await client.indices.exists({index: searchLinks.index});
if (indexExists.body) {
delete searchLinks.size;
const deletedLinksResult = await client.deleteByQuery(searchLinks);
if (
deletedLinksResult &&
deletedLinksResult.body.deleted &&
deletedLinksResult.body.deleted > 0
) {
deleteLinks += deletedLinksResult.body.deleted;
}
}else { console.log(`Index ${searchLinks.index} doesn't exist. Unable to delete.`);}
}
// PERMISSIONS
const searchPermissions = {
index: `${chain}-perm-${chainConfig.settings.index_version}`,
size: 0,
track_total_hits: true,
body: {
query: {
bool: {
must: [
{
range: {
block_num: {
lte: range.start,
gte: range.end,
},
},
},
],
},
},
},
};
if (args.dry) {
const resultPermissions = await client.search<any>(
searchPermissions
);
if (
(resultPermissions.body.hits.total as estypes.SearchTotalHits)
?.value > 0
) {
console.log(
'[WARNING]',
(
resultPermissions.body.hits
.total as estypes.SearchTotalHits
)?.value,
'permissions needs to be updated'
);
console.log({ lte: range.start, gte: range.end });
deletePermissions += (
resultPermissions.body.hits.total as estypes.SearchTotalHits
)?.value;
}
} else {
const indexExists = await client.indices.exists({index: searchPermissions.index});
if (indexExists.body) {
delete searchPermissions.size;
const deletedPermissionsResult = await client.deleteByQuery(
searchPermissions
);
if (
deletedPermissionsResult &&
deletedPermissionsResult.body.deleted &&
deletedPermissionsResult.body.deleted > 0
) {
deletePermissions += deletedPermissionsResult.body.deleted;
}
}else { console.log(`Index ${searchPermissions.index} doens't exist. Não foi possível realizar a exclusão.`);}
}
for (const id of range.ids) {
const searchBlocks = {
index: blockIndex,
body: {
query: {
bool: { must: [{ term: { block_id: { value: id } } }] },
},
},
};
if (args.dry) {
console.log(`Deleting block ${id}...`);
const resultBlocks = await client.search<
SearchResponse<HyperionBlock>
>(searchBlocks);
if (
resultBlocks.body.hits.hits.length > 0 &&
resultBlocks.body.hits.hits[0]._source
) {
deleteBlocks++;
}
} else {
const result = await client.deleteByQuery(searchBlocks);
if (result && result.body.deleted && result.body.deleted > 0) {
deleteBlocks += result.body.deleted;
}
}
}
}
if (args.dry) {
console.log(
`DRY-RUN: Would have deleted ${deleteBlocks} blocks, ${deleteActions} actions, ${deleteDeltas} deltas and ${deleteAbis} ABIs`
);
console.dir({
blocks: deleteBlocks,
actions: deleteActions,
deltas: deleteDeltas,
abis: deleteAbis,
accounts: deleteAccounts,
voters: deleteVoters,
proposals: deleteProposals,
links: deleteLinks,
permissions: deletePermissions,
});
} else {
console.log(
`Deleted ${deleteBlocks} blocks, ${deleteActions} actions, ${deleteDeltas} deltas and ${deleteAbis} ABIs`
);
}
await fillMissingBlocksFromFile(args.host, chain, file, args.dry);
}
async function fillMissingBlocksFromFile(host, chain, file, dryRun) {
const config = readConnectionConfig();
const controlPort = config.chains[chain].control_port;
let hyperionIndexer = `ws://localhost:${controlPort}`;
if (host) {
hyperionIndexer = `ws://${host}:${controlPort}`;
}
const controller = new WebSocket(hyperionIndexer + '/local');
// Function to send Chunk
async function sendChunk(chunk): Promise<void> {
return new Promise((resolve, reject) => {
const payload = {
event: 'fill_missing_blocks',
data: chunk,
};
controller.send(JSON.stringify(payload));
// Wait repair_completed confirmation
controller.once('message', (data) => {
const parsed = JSON.parse(data.toString());
if (parsed.event === 'repair_completed') {
// console.log(`Hyperion repair completed for chunk!`);
resolve();
}
});
});
}
controller.on('open', async () => {
console.log('Connected to Hyperion Controller');
const parsedFile = JSON.parse(readFileSync(file).toString());
const chunkSize = 20; // Chunk size
const totalLines = parsedFile.length;
if (!dryRun) {
let completedLines = 0;
for (let i = 0; i < parsedFile.length; i += chunkSize) {
const chunk = parsedFile.slice(i, i + chunkSize);
await sendChunk(chunk);
// Atualizar o progresso com base no número total de linhas
completedLines += chunk.length;
const progress = (completedLines / totalLines) * 100;
const progressBar = Array(Math.round(progress / 2))
.fill('#')
.join('');
process.stdout.clearLine(0); // Limpar a linha anterior
process.stdout.cursorTo(0); // Mover o cursor para o início da linha
process.stdout.write(
`Progress: [${progressBar}] ${progress.toFixed(2)}%`
);
}
console.log(); // Pule para a próxima linha após a conclusão
controller.close();
} else {
console.log('Dry run, skipping repair');
controller.close();
}
});
controller.on('close', () => {
console.log('Disconnected from Hyperion Controller');
});
controller.on('error', (err) => {
console.log(err);
});
}
function viewFile(file: string) {
const data = readFileSync(file, 'utf8');
const parsed = JSON.parse(data);
console.table(parsed);
}
// Commander Logic
program
.name('Hyperion Repair CLI')
.description('CLI to find and repair forked and missing blocks on Hyperion')
.version('0.2.2');
program
.command('scan <chain>')
.description('scan for forked blocks')
.option('-d, --dry', 'dry-run, do not delete or repair blocks')
.option('-o, --out-file <file>', 'forked-blocks.json output file')
.option('-f, --first <number>', 'initial block to start validation')
.option('-l, --last <number>', 'last block to validate')
.option('-b, --batch <number>', 'batch size to process')
.action(scanChain);
program
.command('repair <chain> <file>')
.description('repair forked blocks')
.option('-h, --host <host>', 'Hyperion local control api')
.option('-d, --dry', 'dry-run, do not delete or repair blocks')
.option('-t, --check-tasks', 'check for running tasks')
.action(repairChain);
program
.command('fill-missing <chain> <file>')
.description('write missing blocks')
.option('-h, --host <host>', 'Hyperion local control api')
.option('-d, --dry', 'dry-run, do not delete or repair blocks')
.action(repairMissing);
program
.command('view <file>')
.description('view forked blocks')
.action(viewFile);
program
.command('connect')
.option('-h, --host <host>', 'Hyperion local control api')
.action(async (args) => {
let hyperionIndexer = 'ws://localhost:4321';
let valid = false;
if (args.host) {
hyperionIndexer = args.host;
}
try {
const controller = new WebSocket(hyperionIndexer + '/local');
controller.on('open', () => {
valid = true;
controller.close();
});
controller.on('close', () => {
if (valid) {
console.log(
`✅ Hyperion Indexer Online - ${hyperionIndexer}`
);
}
});
controller.on('error', (err) => {
console.log('Error:', err.message);
console.log(
`Failed to connect on Hyperion Indexer at ${hyperionIndexer}, please use "--host ws://ADDRESS:PORT" to specify a remote indexer connection`
);
});
} catch (e) {
console.log(e);
}
});
program.parse();
+82
View File
@@ -0,0 +1,82 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getBlocks = exports.getLastIndexedBlock = exports.getFirstIndexedBlock = exports.readChainConfig = exports.initESClient = exports.readConnectionConfig = void 0;
const node_fs_1 = require("node:fs");
const elasticsearch_1 = require("@elastic/elasticsearch");
function readConnectionConfig() {
const file = (0, node_fs_1.readFileSync)('connections.json', 'utf8');
return JSON.parse(file);
}
exports.readConnectionConfig = readConnectionConfig;
function initESClient(config) {
const node = `${config.elasticsearch.protocol}://${config.elasticsearch.host}`;
return new elasticsearch_1.Client({
nodes: [node],
auth: {
username: config.elasticsearch.user,
password: config.elasticsearch.pass
},
ssl: {
rejectUnauthorized: false
}
});
}
exports.initESClient = initESClient;
function readChainConfig(chain) {
const file = (0, node_fs_1.readFileSync)(`chains/${chain}.config.json`, 'utf8');
return JSON.parse(file);
}
exports.readChainConfig = readChainConfig;
async function getFirstIndexedBlock(client, blockIndex) {
const { body } = await client.search({
index: blockIndex,
size: 1,
body: {
sort: [{ block_num: { order: 'asc' } }]
}
});
if (body.hits.hits[0]._source) {
return body.hits.hits[0]._source.block_num;
}
else {
console.log('No blocks indexed yet');
process.exit();
}
}
exports.getFirstIndexedBlock = getFirstIndexedBlock;
async function getLastIndexedBlock(client, blockIndex) {
const { body } = await client.search({
index: blockIndex,
size: 1,
body: {
sort: [{ block_num: { order: 'desc' } }]
}
});
if (body.hits.hits[0]._source) {
return body.hits.hits[0]._source.block_num;
}
else {
console.log('No blocks indexed yet');
process.exit();
}
}
exports.getLastIndexedBlock = getLastIndexedBlock;
async function getBlocks(client, indexName, blocoInicial, blocoFinal, size) {
return await client.search({
index: indexName,
size: size,
body: {
sort: [{ block_num: { order: "desc" } }],
query: {
range: {
block_num: {
gte: blocoFinal,
lte: blocoInicial,
},
},
},
}
});
}
exports.getBlocks = getBlocks;
//# sourceMappingURL=functions.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"functions.js","sourceRoot":"","sources":["functions.ts"],"names":[],"mappings":";;;AAAA,qCAAqC;AACrC,0DAAuD;AAGvD,SAAgB,oBAAoB;IAChC,MAAM,IAAI,GAAG,IAAA,sBAAY,EAAC,kBAAkB,EAAE,MAAM,CAAC,CAAC;IACtD,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AAC5B,CAAC;AAHD,oDAGC;AAED,SAAgB,YAAY,CAAC,MAAW;IACpC,MAAM,IAAI,GAAG,GAAG,MAAM,CAAC,aAAa,CAAC,QAAQ,MAAM,MAAM,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC;IAC/E,OAAO,IAAI,sBAAM,CAAC;QACd,KAAK,EAAE,CAAC,IAAI,CAAC;QACb,IAAI,EAAE;YACF,QAAQ,EAAE,MAAM,CAAC,aAAa,CAAC,IAAI;YACnC,QAAQ,EAAE,MAAM,CAAC,aAAa,CAAC,IAAI;SACtC;QACD,GAAG,EAAE;YACD,kBAAkB,EAAE,KAAK;SAC5B;KACJ,CAAC,CAAC;AACP,CAAC;AAZD,oCAYC;AAED,SAAgB,eAAe,CAAC,KAAa;IACzC,MAAM,IAAI,GAAG,IAAA,sBAAY,EAAC,UAAU,KAAK,cAAc,EAAE,MAAM,CAAC,CAAC;IACjE,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AAC5B,CAAC;AAHD,0CAGC;AAEM,KAAK,UAAU,oBAAoB,CAAC,MAAc,EAAE,UAAkB;IACzE,MAAM,EAAC,IAAI,EAAC,GAAG,MAAM,MAAM,CAAC,MAAM,CAAwC;QACtE,KAAK,EAAE,UAAU;QACjB,IAAI,EAAE,CAAC;QACP,IAAI,EAAE;YACF,IAAI,EAAE,CAAC,EAAC,SAAS,EAAE,EAAC,KAAK,EAAE,KAAK,EAAC,EAAC,CAAC;SACtC;KACJ,CAAC,CAAC;IACH,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE;QAC3B,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC;KAC9C;SAAM;QACH,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC;QACrC,OAAO,CAAC,IAAI,EAAE,CAAC;KAClB;AACL,CAAC;AAdD,oDAcC;AAEM,KAAK,UAAU,mBAAmB,CAAC,MAAc,EAAE,UAAkB;IACxE,MAAM,EAAC,IAAI,EAAC,GAAG,MAAM,MAAM,CAAC,MAAM,CAAwC;QACtE,KAAK,EAAE,UAAU;QACjB,IAAI,EAAE,CAAC;QACP,IAAI,EAAE;YACF,IAAI,EAAE,CAAC,EAAC,SAAS,EAAE,EAAC,KAAK,EAAE,MAAM,EAAC,EAAC,CAAC;SACvC;KACJ,CAAC,CAAC;IACH,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE;QAC3B,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC;KAC9C;SAAM;QACH,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC;QACrC,OAAO,CAAC,IAAI,EAAE,CAAC;KAClB;AACL,CAAC;AAdD,kDAcC;AAEM,KAAK,UAAU,SAAS,CAAC,MAAW,EAAE,SAAiB,EAAE,YAAiB,EAAE,UAAe,EAAE,IAAS;IACzG,OAAO,MAAM,MAAM,CAAC,MAAM,CAAC;QACvB,KAAK,EAAE,SAAS;QAChB,IAAI,EAAE,IAAI;QACV,IAAI,EAAE;YACF,IAAI,EAAE,CAAC,EAAC,SAAS,EAAE,EAAC,KAAK,EAAE,MAAM,EAAC,EAAC,CAAC;YACpC,KAAK,EAAE;gBACH,KAAK,EAAE;oBACH,SAAS,EAAE;wBACP,GAAG,EAAE,UAAU;wBACf,GAAG,EAAE,YAAY;qBACpB;iBACJ;aACJ;SACJ;KACJ,CAAC,CAAC;AACP,CAAC;AAhBD,8BAgBC"}
+78
View File
@@ -0,0 +1,78 @@
import {readFileSync} from "node:fs";
import {Client, estypes} from "@elastic/elasticsearch";
import {HyperionBlock} from "./interfaces.js";
export function readConnectionConfig() {
const file = readFileSync('connections.json', 'utf8');
return JSON.parse(file);
}
export function initESClient(config: any) {
const node = `${config.elasticsearch.protocol}://${config.elasticsearch.host}`;
return new Client({
nodes: [node],
auth: {
username: config.elasticsearch.user,
password: config.elasticsearch.pass
},
ssl: {
rejectUnauthorized: false
}
});
}
export function readChainConfig(chain: string) {
const file = readFileSync(`chains/${chain}.config.json`, 'utf8');
return JSON.parse(file);
}
export async function getFirstIndexedBlock(client: Client, blockIndex: string) {
const {body} = await client.search<estypes.SearchResponse<HyperionBlock>>({
index: blockIndex,
size: 1,
body: {
sort: [{block_num: {order: 'asc'}}]
}
});
if (body.hits.hits[0]._source) {
return body.hits.hits[0]._source.block_num;
} else {
console.log('No blocks indexed yet');
process.exit();
}
}
export async function getLastIndexedBlock(client: Client, blockIndex: string) {
const {body} = await client.search<estypes.SearchResponse<HyperionBlock>>({
index: blockIndex,
size: 1,
body: {
sort: [{block_num: {order: 'desc'}}]
}
});
if (body.hits.hits[0]._source) {
return body.hits.hits[0]._source.block_num;
} else {
console.log('No blocks indexed yet');
process.exit();
}
}
export async function getBlocks(client: any, indexName: string, blocoInicial: any, blocoFinal: any, size: any) {
return await client.search({
index: indexName,
size: size,
body: {
sort: [{block_num: {order: "desc"}}],
query: {
range: {
block_num: {
gte: blocoFinal,
lte: blocoInicial,
},
},
},
}
});
}
+3
View File
@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=interfaces.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"interfaces.js","sourceRoot":"","sources":["interfaces.ts"],"names":[],"mappings":""}
+11
View File
@@ -0,0 +1,11 @@
export interface HyperionBlock {
"@timestamp": string,
"block_num": number,
"block_id": string,
"producer": string,
"new_producers"?: any,
"schedule_version": number,
"cpu_usage": number,
"net_usage": number,
"prev_id": string
}
+39
View File
@@ -0,0 +1,39 @@
const hyperionModule = {
chain: "*",
contract: '*',
action: 'act',
parser_version: ['3.2', '2.1','1.8','1.7'],
defineQueryPrefix: 'act',
mappings: {
action: {
"@act": {
"properties": {
"coopname": {"type": "keyword"},
"username": {"type": "keyword"},
"action": {"type": "keyword"},
"decision_id": {"type": "long"},
"batch_id": {"type": "long"},
"act": {"enabled": false}
}
}
}
},
handler: (action) => {
const data = action['act']['data'];
action['@act'] = {
coopname: data.coopname,
username: data.username,
action: data.action,
decision_id: data.decision_id,
batch_id: data.batch_id,
act: data.act
};
}
};
module.exports = {hyperionModule};
+33
View File
@@ -0,0 +1,33 @@
const hyperionModule = {
chain: "*",
contract: '*',
action: 'batch',
parser_version: ['3.2', '2.1','1.8','1.7'],
defineQueryPrefix: 'batch',
mappings: {
action: {
"@batch": {
"properties": {
"coopname": {"type": "keyword"},
"action": {"type": "keyword"},
"batch_id": {"type": "long"}
}
}
}
},
handler: (action) => {
const data = action['act']['data'];
action['@batch'] = {
coopname: data.coopname,
action: data.action,
batch_id: data.batch_id,
};
}
};
module.exports = {hyperionModule};
+39
View File
@@ -0,0 +1,39 @@
const hyperionModule = {
chain: "*",
contract: '*',
action: 'decision',
parser_version: ['3.2', '2.1','1.8','1.7'],
defineQueryPrefix: 'decision',
mappings: {
action: {
"@decision": {
"properties": {
"coopname": {"type": "keyword"},
"username": {"type": "keyword"},
"action": {"type": "keyword"},
"decision_id": {"type": "long"},
"batch_id": {"type": "long"},
"decision": {"enabled": false}
}
}
}
},
handler: (action) => {
const data = action['act']['data'];
action['@decision'] = {
coopname: data.coopname,
username: data.username,
action: data.action,
decision_id: data.decision_id,
batch_id: data.batch_id,
decision: data.decision
};
}
};
module.exports = {hyperionModule};
+33
View File
@@ -0,0 +1,33 @@
const hyperionModule = {
chain: "*",
contract: '*',
action: 'exec',
parser_version: ['3.2', '2.1','1.8','1.7'],
defineQueryPrefix: 'exec',
mappings: {
action: {
"@exec": {
"properties": {
"executer": {"type": "keyword"},
"coopname": {"type": "keyword"},
"decision_id": {"type": "long"}
}
}
}
},
handler: (action) => {
const data = action['act']['data'];
action['@exec'] = {
executer: data.executer,
coopname: data.coopname,
decision_id: data.decision_id,
};
}
};
module.exports = {hyperionModule};
+39
View File
@@ -0,0 +1,39 @@
const hyperionModule = {
chain: "*",
contract: '*',
action: 'statement',
parser_version: ['3.2', '2.1','1.8','1.7'],
defineQueryPrefix: 'statement',
mappings: {
action: {
"@statement": {
"properties": {
"coopname": {"type": "keyword"},
"username": {"type": "keyword"},
"action": {"type": "keyword"},
"decision_id": {"type": "long"},
"batch_id": {"type": "long"},
"statement": {"enabled": false}
}
}
}
},
handler: (action) => {
const data = action['act']['data'];
action['@statement'] = {
coopname: data.coopname,
username: data.username,
action: data.action,
decision_id: data.decision_id,
batch_id: data.batch_id,
statement: data.statement
};
}
};
module.exports = {hyperionModule};
+33
View File
@@ -0,0 +1,33 @@
const hyperionModule = {
chain: "*",
contract: '*',
action: 'voteagainst',
parser_version: ['3.2', '2.1','1.8','1.7'],
defineQueryPrefix: 'voteagainst',
mappings: {
action: {
"@voteagainst": {
"properties": {
"coopname": {"type": "keyword"},
"member": {"type": "keyword"},
"decision_id": {"type": "long"}
}
}
}
},
handler: (action) => {
const data = action['act']['data'];
action['@voteagainst'] = {
coopname: data.coopname,
member: data.member,
decision_id: decision_id
};
}
};
module.exports = {hyperionModule};
+33
View File
@@ -0,0 +1,33 @@
const hyperionModule = {
chain: "*",
contract: '*',
action: 'votefor',
parser_version: ['3.2', '2.1','1.8','1.7'],
defineQueryPrefix: 'votefor',
mappings: {
action: {
"@votefor": {
"properties": {
"coopname": {"type": "keyword"},
"member": {"type": "keyword"},
"decision_id": {"type": "long"}
}
}
}
},
handler: (action) => {
const data = action['act']['data'];
action['@votefor'] = {
coopname: data.coopname,
member: data.member,
decision_id: decision_id
};
}
};
module.exports = {hyperionModule};
+1
View File
@@ -6,6 +6,7 @@
"allowSyntheticDefaultImports": true,
"noImplicitAny": false,
"moduleResolution": "node",
"noEmitOnError": true,
"sourceMap": true,
"allowJs": false,
"typeRoots": [
+141 -66
View File
@@ -8,39 +8,40 @@ import {Type} from "../addons/eosjs-native/eosjs-serialize";
import {debugLog, hLog} from "../helpers/common_functions";
import {createHash} from "crypto";
import flatstr from 'flatstr';
import {Options} from "amqplib";
const FJS = require('fast-json-stringify');
const lightBlockSerializer = FJS({
title: 'Light Block',
type: 'object',
properties: {
'@timestamp': {type: 'string'},
block_num: {type: 'integer'},
block_id: {type: 'string'},
prev_id: {type: 'string'},
producer: {type: 'string'},
new_producers: {
type: 'object',
nullable: true,
properties: {
version: {type: 'integer'},
producers: {
type: 'array',
items: {
properties: {
block_signing_key: {type: 'string'},
producer_name: {type: 'string'}
}
}
}
}
},
schedule_version: {type: 'integer'},
cpu_usage: {type: 'integer'},
net_usage: {type: 'integer'}
}
});
// const lightBlockSerializer = FJS({
// title: 'Light Block',
// type: 'object',
// properties: {
// '@timestamp': {type: 'string'},
// block_num: {type: 'integer'},
// block_id: {type: 'string'},
// prev_id: {type: 'string'},
// producer: {type: 'string'},
// new_producers: {
// type: 'object',
// nullable: true,
// properties: {
// version: {type: 'integer'},
// producers: {
// type: 'array',
// items: {
// properties: {
// block_signing_key: {type: 'string'},
// producer_name: {type: 'string'}
// }
// }
// }
// }
// },
// schedule_version: {type: 'integer'},
// cpu_usage: {type: 'integer'},
// net_usage: {type: 'integer'}
// }
// });
const index_queues = require('../definitions/index-queues').index_queues;
const {AbiDefinitions} = require("../definitions/abi_def");
@@ -124,6 +125,9 @@ export default class MainDSWorker extends HyperionWorker {
allowedDynamicContracts: Set<string> = new Set<string>();
backpressureQueue: any[] = [];
waitToSend = false;
constructor() {
super();
@@ -290,6 +294,16 @@ export default class MainDSWorker extends HyperionWorker {
this.ch.consume(process.env['worker_queue'], (data) => {
this.consumerQueue.push(data).catch(console.log);
});
this.ch.on('drain', args => {
this.waitToSend = false;
while (this.backpressureQueue.length > 0) {
const msg = this.backpressureQueue.shift();
const status = this.controlledSendToQueue(msg.queue, msg.payload, msg.options);
if (!status) {
break;
}
}
});
}
}
@@ -410,6 +424,16 @@ export default class MainDSWorker extends HyperionWorker {
live: process.env.live_mode
});
}
// stream light block
if (this.allowStreaming) {
this.ch.publish('', this.chain + ':stream', Buffer.from(JSON.stringify(light_block)), {
headers: {
event: 'block',
blockNum: light_block.block_num
}
});
}
}
// Process Delta Traces (must be done first to catch ABI updates)
@@ -468,6 +492,7 @@ export default class MainDSWorker extends HyperionWorker {
hLog(`${block_num} was filtered with ${inline_count} actions!`);
}
try {
trace[1].signatures = signatures;
this.routeToPool(trace[1], {
block_num,
block_id,
@@ -475,8 +500,7 @@ export default class MainDSWorker extends HyperionWorker {
ts,
inline_count,
filtered,
live: process.env['live_mode'],
signatures
live: process.env['live_mode']
});
} catch (e) {
hLog(e);
@@ -495,6 +519,7 @@ export default class MainDSWorker extends HyperionWorker {
return {
block_num: res['this_block']['block_num'],
block_id: res['this_block']['block_id'],
block_ts,
trx_ids: onBlockTransactions,
size: _traces.length
};
@@ -611,15 +636,33 @@ export default class MainDSWorker extends HyperionWorker {
}
const pool_queue = `${this.chain}:ds_pool:${selected_q}`;
if (this.ch_ready) {
// console.log('selected_q', pool_queue);
this.ch.sendToQueue(pool_queue, bufferFromJson(trace, true), {headers});
return true;
const payload = bufferFromJson(trace, true);
if (!this.waitToSend) {
if (this.ch_ready) {
this.controlledSendToQueue(pool_queue, payload, {headers});
return true;
} else {
return false;
}
} else {
this.backpressureQueue.push({
queue: pool_queue,
payload: payload,
options: {headers}
});
return false;
}
}
controlledSendToQueue(pool_queue: string, payload: Buffer, options: Options.Publish): boolean {
const enqueueResult = this.ch.sendToQueue(pool_queue, payload, options);
if (!enqueueResult) {
this.waitToSend = true;
}
return enqueueResult;
}
createSerialBuffer(inputArray) {
return new Serialize.SerialBuffer({textEncoder: this.txEnc, textDecoder: this.txDec, array: inputArray});
}
@@ -1149,6 +1192,7 @@ export default class MainDSWorker extends HyperionWorker {
let jsonRow = await this.processContractRowNative(payload, block_num);
if (jsonRow?.value && !jsonRow['_blacklisted']) {
debugLog(jsonRow);
debugLog('Delta DS failed ->>', jsonRow);
jsonRow = await this.processContractRowNative(payload, block_num - 1);
debugLog('Retry with previous ABI ->>', jsonRow);
@@ -1389,9 +1433,33 @@ export default class MainDSWorker extends HyperionWorker {
},
// Global Chain configuration update
// "global_property": async (global_property, block_num, block_ts, row, block_id) => {
// hLog(block_num, global_property);
// },
"global_property": async (global_property, block_num, block_ts) => {
if (global_property.proposed_schedule.version !== 0) {
hLog("Proposed Schedule version: " + global_property.proposed_schedule.version + " at block: " + global_property.proposed_schedule_block_num);
try {
const payload = {
block_num: global_property.proposed_schedule_block_num,
'@timestamp': block_ts,
version: global_property.proposed_schedule.version,
producers: global_property.proposed_schedule.producers
};
payload.producers.forEach((producer: any) => {
producer.name = producer.producer_name;
delete producer.producer_name;
if (producer.authority[0] === 'block_signing_authority_v0') {
producer.authority = producer.authority[1];
producer.keys = producer.authority.keys.map((key: any) => {
return key.key;
});
delete producer.authority;
}
});
await this.pushToIndexQueue(payload, 'schedule');
} catch (e) {
hLog("Failed to parse proposed schedule: " + e.message);
}
}
},
// Activated Protocol features
// "protocol_state": async (protocol_state, block_num, block_ts, row, block_id) => {
@@ -1458,6 +1526,8 @@ export default class MainDSWorker extends HyperionWorker {
await this.deltaStructHandlers[key](data[1], block_num, block_ts, row, block_id);
} catch (e) {
hLog(`Delta struct [${key}] processing error: ${e.message}`);
hLog(e);
hLog(data[1]);
}
}
}
@@ -1602,41 +1672,46 @@ export default class MainDSWorker extends HyperionWorker {
this.tableHandlers[EOSIO_ALIAS + ':producers'] = (delta) => {
const data = delta['data'];
delta['@producers'] = {
total_votes: parseFloat(data['total_votes']),
is_active: data['is_active'],
unpaid_blocks: data['unpaid_blocks']
};
delete delta['data'];
if (data) {
delta['@producers'] = {
total_votes: parseFloat(data['total_votes']),
is_active: data['is_active'],
unpaid_blocks: data['unpaid_blocks']
};
delete delta['data'];
}
};
this.tableHandlers[EOSIO_ALIAS + ':userres'] = (delta) => {
const data = delta['data'];
const net = parseFloat(data['net_weight'].split(" ")[0]);
const cpu = parseFloat(data['cpu_weight'].split(" ")[0]);
delta['@userres'] = {
owner: data['owner'],
net_weight: net,
cpu_weight: cpu,
total_weight: parseFloat((net + cpu).toFixed(4)),
ram_bytes: parseInt(data['ram_bytes'])
};
delete delta['data'];
if (data['net_weight'] && data['cpu_weight']) {
const net = parseFloat(data['net_weight'].split(" ")[0]);
const cpu = parseFloat(data['cpu_weight'].split(" ")[0]);
delta['@userres'] = {
owner: data['owner'],
net_weight: net,
cpu_weight: cpu,
total_weight: parseFloat((net + cpu).toFixed(4)),
ram_bytes: parseInt(data['ram_bytes'])
};
delete delta['data'];
}
};
this.tableHandlers[EOSIO_ALIAS + ':delband'] = (delta) => {
const data = delta['data'];
const net = parseFloat(data['net_weight'].split(" ")[0]);
const cpu = parseFloat(data['cpu_weight'].split(" ")[0]);
delta['@delband'] = {
from: data['from'],
to: data['to'],
net_weight: net,
cpu_weight: cpu,
total_weight: parseFloat((net + cpu).toFixed(4))
};
delete delta['data'];
// hLog(delta);
if (data['net_weight'] && data['cpu_weight']) {
const net = parseFloat(data['net_weight'].split(" ")[0]);
const cpu = parseFloat(data['cpu_weight'].split(" ")[0]);
delta['@delband'] = {
from: data['from'],
to: data['to'],
net_weight: net,
cpu_weight: cpu,
total_weight: parseFloat((net + cpu).toFixed(4))
};
delete delta['data'];
}
};
this.tableHandlers[EOSIO_ALIAS + '.msig:proposal'] = async (delta) => {
+2 -2
View File
@@ -454,8 +454,8 @@ export default class DSPoolWorker extends HyperionWorker {
}
async processTraces(transaction_trace, extra) {
const {cpu_usage_us, net_usage_words} = transaction_trace;
const {block_num, block_id, producer, ts, inline_count, filtered, live, signatures} = extra;
const {cpu_usage_us, net_usage_words, signatures} = transaction_trace;
const {block_num, block_id, producer, ts, inline_count, filtered, live} = extra;
if (transaction_trace.status === 0) {
let action_count = 0;
+1 -10
View File
@@ -11,20 +11,11 @@ export default class IndexerWorker extends HyperionWorker {
private temp_indexed_count = 0;
esRoutes: ElasticRoutes;
distributionMap;
constructor() {
super();
if (process.env.distribution) {
try {
this.distributionMap = JSON.parse(process.env.distribution);
} catch {
hLog('Failed to parse distribution map');
}
}
this.esRoutes = new ElasticRoutes(this.manager, this.distributionMap);
this.esRoutes = new ElasticRoutes(this.manager);
this.indexQueue = cargo((payload: Message[], callback) => {
if (this.ch_ready && payload) {
if (this.esRoutes.routes[process.env.type]) {
+163 -55
View File
@@ -22,6 +22,7 @@ export default class StateReader extends HyperionWorker {
private completionMonitoring: NodeJS.Timeout;
private types: Map<string, Type>;
private local_block_num = parseInt(process.env.first_block, 10) - 1;
private local_block_id = '';
private queueSizeCheckInterval = 5000;
private local_distributed_count = 0;
private lastPendingCount = 0;
@@ -41,6 +42,9 @@ export default class StateReader extends HyperionWorker {
private forkedBlocks = new Map<string, number>();
private idle = true;
private repairMode = false;
private internalPendingRanges = [];
constructor() {
super();
@@ -190,17 +194,56 @@ export default class StateReader extends HyperionWorker {
newRange(data: any) {
debugLog(`new_range [${data.first_block},${data.last_block}]`);
if (this.repairMode) {
hLog(`Received Repair request [${data.first_block},${data.last_block}]`);
}
this.local_distributed_count = 0;
clearInterval(this.completionMonitoring);
this.completionMonitoring = null;
this.completionSignaled = false;
this.local_last_block = data.last_block;
// make sure no data is requested from before the first indexed block on ship
if (parseInt(data.first_block) < this.shipInitStatus['trace_begin_block']) {
data.first_block = this.shipInitStatus['trace_begin_block'];
hLog("Impossible to repair requested range, first block is before the first indexed block on ship, trimming to " + data.first_block);
}
this.range_size = parseInt(data.last_block) - parseInt(data.first_block);
if (this.allowRequests) {
this.requestBlockRange(data.first_block, data.last_block);
this.pendingRequest = null;
// if range size is not positive we need to immediately signal completion to master
if (this.range_size <= 0) {
this.emitCompletedSignal();
}
// make sure the range doesn't exceed the batch size
if (this.range_size > this.conf.scaling.batch_size) {
// split the range into multiple ranges
const ranges = [];
let start = parseInt(data.first_block);
let end = parseInt(data.last_block);
while (start < end) {
const s = start;
const e = start + this.conf.scaling.batch_size;
ranges.push({
first_block: s,
last_block: e > end ? end : e
});
start = e + 1;
}
console.log(`Splitting range [${data.first_block},${data.last_block}] into ${ranges.length} ranges`);
this.internalPendingRanges = ranges;
const next = this.internalPendingRanges.shift();
this.newRange(next);
} else {
this.pendingRequest = [data.first_block, data.last_block];
if (this.allowRequests) {
this.requestBlockRange(data.first_block, data.last_block);
this.pendingRequest = null;
} else {
this.pendingRequest = [data.first_block, data.last_block];
}
}
}
@@ -270,10 +313,18 @@ export default class StateReader extends HyperionWorker {
if (pending === 0) {
debugLog(`Reader completed - ${this.range_size} - ${this.local_distributed_count}`);
clearInterval(this.completionMonitoring);
process.send({
event: 'completed',
id: process.env['worker_id']
});
// check if there are any pending ranges, then signal completion to master
if (this.internalPendingRanges.length === 0) {
process.send({
event: 'completed',
id: process.env['worker_id']
});
} else {
// process next range in queue
const next = this.internalPendingRanges.shift();
this.newRange(next);
}
}
}, 200);
}
@@ -285,7 +336,7 @@ export default class StateReader extends HyperionWorker {
try {
await this.onMessage(block);
} catch (e) {
console.log(e);
hLog(e);
}
}
this.ackBlockRange(block_array.length);
@@ -308,7 +359,7 @@ export default class StateReader extends HyperionWorker {
}
if (!process.env.worker_role) {
console.log("[FATAL ERROR] undefined role! Exiting now!");
hLog("[FATAL ERROR] undefined role! Exiting now!");
this.ship.close(false);
process.exit(1);
return;
@@ -324,7 +375,7 @@ export default class StateReader extends HyperionWorker {
this.shipInitStatus = result[1];
hLog(`\n| SHIP Status Report\n| Init block: ${this.shipInitStatus['chain_state_begin_block']}\n| Head block: ${this.shipInitStatus['chain_state_end_block']}`);
const chain_state_begin_block = this.shipInitStatus['chain_state_begin_block'];
if (!this.conf.indexer.disable_reading) {
if (!this.conf.indexer.disable_reading || process.env['worker_role'] === 'repair_reader') {
switch (process.env['worker_role']) {
@@ -356,6 +407,15 @@ export default class StateReader extends HyperionWorker {
break;
}
case 'repair_reader': {
this.repairMode = true;
hLog('Waiting for operations...');
process.send({
event: 'repair_reader_ready'
});
break;
}
}
} else {
this.ship.close(true);
@@ -370,10 +430,17 @@ export default class StateReader extends HyperionWorker {
if (res['this_block']) {
this.idle = false;
const blk_num = res['this_block']['block_num'];
const blk_id = res['this_block']['block_id'];
const lib = res['last_irreversible'];
const task_payload = {num: blk_num, content: data};
if (res.block && res.traces && res.deltas) {
if (this.repairMode) {
hLog("Repaired block: " + blk_num);
}
debugLog(`block_num: ${blk_num}, block_size: ${res.block.length}, traces_size: ${res.traces.length}, deltas_size: ${res.deltas.length}`);
} else {
if (!res.traces) {
@@ -390,18 +457,35 @@ export default class StateReader extends HyperionWorker {
if (this.isLiveReader) {
// LIVE READER MODE
if (blk_num !== this.local_block_num + 1) {
hLog(`Expected: ${this.local_block_num + 1}, received: ${blk_num}`);
let prev_id;
if (res['prev_block'] && res['prev_block']['block_id']) {
prev_id = res['prev_block']['block_id'];
}
// // 2% chance to modify the previous block id to simulate a fork
// if (Math.random() < 0.02) {
// // prev_id = prev_id + 'a';
// this.local_block_num = this.local_block_num - 1;
// }
if ((this.local_block_id && prev_id && this.local_block_id !== prev_id) || (blk_num !== this.local_block_num + 1)) {
console.log((this.local_block_id && prev_id && this.local_block_id !== prev_id), (blk_num !== this.local_block_num + 1));
console.log(blk_num, this.local_block_num + 1);
hLog(`Unlinked block at ${prev_id} with previous block ${this.local_block_id}`);
hLog(`Forked block: ${blk_num}`);
try {
// delete all previously stored data for the forked blocks
await this.handleFork(res);
} catch (e) {
hLog(`Failed to handle fork during live reading! - Error: ${e.message}`);
}
} else {
this.local_block_num = blk_num;
}
this.local_block_num = blk_num;
this.local_block_id = blk_id;
if (lib.block_num > this.local_lib) {
this.local_lib = lib.block_num;
// emit lib update event
@@ -409,22 +493,32 @@ export default class StateReader extends HyperionWorker {
}
await this.stageOneDistQueue.push(task_payload);
return 1;
} else {
// Detect skipped first block
if (!this.receivedFirstBlock) {
if (blk_num !== this.local_block_num + 1) {
hLog(`WARNING: First block received was #${blk_num}, but #${this.local_block_num + 1} was expected!`);
hLog(`Make sure the block.log file contains the requested range, check with "eosio-blocklog --smoke-test"`);
// Special case for block 2 that is actually the first block on ship
if (blk_num !== 2) {
hLog(`WARNING: First block received was #${blk_num}, but #${this.local_block_num + 1} was expected!`);
hLog(`Make sure the block.log file contains the requested range, check with "eosio-blocklog --smoke-test"`);
}
this.local_block_num = blk_num - 1;
this.local_distributed_count++;
process.send({
event: 'skipped_block',
block_num: this.local_block_num + 1
})
}
this.receivedFirstBlock = true;
}
// BACKLOG MODE
if (this.future_block !== 0 && this.future_block === blk_num) {
console.log('Missing block ' + blk_num + ' received!');
hLog('Missing block ' + blk_num + ' received!');
} else {
this.future_block = 0;
}
@@ -449,6 +543,9 @@ export default class StateReader extends HyperionWorker {
}
}
} else {
if (this.repairMode) {
hLog("No data on requested block!");
}
this.idle = true;
// hLog(`Reader is idle! - Head at: ${result[1].head.block_num}`);
// this.ship.close(true);
@@ -588,11 +685,14 @@ export default class StateReader extends HyperionWorker {
id: targetBlock
});
targetBlock++;
console.log(blockData);
if (blockData.body) {
const targetBlockId = blockData.body._source.block_id;
this.forkedBlocks.set(targetBlockId, Date.now());
this.deleteForkedBlock(targetBlockId).catch(console.log);
try {
await this.deleteForkedBlock(targetBlockId);
} catch (e: any) {
hLog(`Error deleting forked block ${targetBlockId}: ${e.message}`);
}
}
}
}
@@ -616,41 +716,41 @@ export default class StateReader extends HyperionWorker {
this.send(['get_blocks_ack_request_v0', {num_messages: size}]);
}
private startQueueWatcher() {
setInterval(() => {
if (this.isLiveReader) {
this.manager.checkQueueSize(this.chain + ":live_blocks").then(value => {
if (value > this.conf.scaling.block_queue_limit) {
this.allowRequests = false;
} else {
this.allowRequests = true;
if (this.pendingRequest) {
this.processPending();
}
}
});
} else {
let checkArr = [];
for (let i = 0; i < this.conf.scaling.ds_queues; i++) {
const q = this.chain + ":blocks:" + (i + 1);
checkArr.push(this.manager.checkQueueSize(q));
}
Promise.all(checkArr).then(data => {
if (data.some(el => el > this.conf.scaling.block_queue_limit)) {
if (this.allowRequests) {
hLog('ship reader paused!', data);
}
this.allowRequests = false;
} else {
this.allowRequests = true;
if (this.pendingRequest) {
this.processPending();
}
}
});
}
}, this.queueSizeCheckInterval);
}
// private startQueueWatcher() {
// setInterval(() => {
// if (this.isLiveReader) {
// this.manager.checkQueueSize(this.chain + ":live_blocks").then(value => {
// if (value > this.conf.scaling.block_queue_limit) {
// this.allowRequests = false;
// } else {
// this.allowRequests = true;
// if (this.pendingRequest) {
// this.processPending();
// }
// }
// });
// } else {
// let checkArr = [];
// for (let i = 0; i < this.conf.scaling.ds_queues; i++) {
// const q = this.chain + ":blocks:" + (i + 1);
// checkArr.push(this.manager.checkQueueSize(q));
// }
// Promise.all(checkArr).then(data => {
// if (data.some(el => el > this.conf.scaling.block_queue_limit)) {
// if (this.allowRequests) {
// hLog('ship reader paused!', data);
// }
// this.allowRequests = false;
// } else {
// this.allowRequests = true;
// if (this.pendingRequest) {
// this.processPending();
// }
// }
// });
// }
// }, this.queueSizeCheckInterval);
// }
private processPending() {
this.requestBlockRange(this.pendingRequest[0], this.pendingRequest[1]);
@@ -669,6 +769,7 @@ export default class StateReader extends HyperionWorker {
);
}
private handleLostConnection() {
this.recovery = true;
this.ship.close(false);
@@ -680,4 +781,11 @@ export default class StateReader extends HyperionWorker {
this.startWS();
}, 5000);
}
private emitCompletedSignal() {
process.send({
event: 'completed',
id: process.env['worker_id']
});
}
}
+45 -24
View File
@@ -1,7 +1,7 @@
import {HyperionWorker} from "./hyperionWorker";
import {Server, Socket} from "socket.io";
import {checkFilter, hLog} from "../helpers/common_functions";
import {checkDeltaFilter, checkFilter, hLog} from "../helpers/common_functions";
import {createServer} from "http";
const greylist = ['eosio.token'];
@@ -34,21 +34,21 @@ export default class WSRouter extends HyperionWorker {
this.ch.consume(this.q, this.onConsume.bind(this));
}
appendIdAndEmit(event, data) {
this.io.emit(event, {
chain_id: this.manager.conn.chains[this.chain]?.chain_id,
...data
});
};
onIpcMessage(msg: any): void {
switch (msg.event) {
case 'lib_update': {
this.io.emit('lib_update', {
chain_id: this.manager.conn.chains[this.chain]?.chain_id,
...msg.data
});
this.appendIdAndEmit('lib_update', msg.data);
break;
}
case 'fork_event': {
this.io.emit('fork_event', {
chain_id: this.manager.conn.chains[this.chain]?.chain_id,
...msg.data
});
this.appendIdAndEmit('fork_event', msg.data);
break;
}
}
@@ -68,7 +68,17 @@ export default class WSRouter extends HyperionWorker {
// push to plugin handlers
this.mLoader.processStreamEvent(msg);
// process incoming live messages
switch (msg.properties.headers.event) {
case 'block': {
// forward block events to all APIs
this.io.of('/').emit('block', {
serverTime: Date.now(),
blockNum: msg.properties.headers.blockNum,
content: msg.content
});
break;
}
case 'trace': {
const actHeader = msg.properties.headers;
const code = actHeader.account;
@@ -146,7 +156,7 @@ export default class WSRouter extends HyperionWorker {
}
default: {
console.log('Unindentified message!');
console.log('Unidentified message!');
console.log(msg);
}
}
@@ -206,7 +216,10 @@ export default class WSRouter extends HyperionWorker {
addActionRequest(data, id) {
const req = data.request;
if (typeof req.account !== 'string') {
return {status: 'FAIL', reason: 'invalid request'};
return {
status: 'FAIL',
reason: 'invalid request'
};
}
if (greylist.indexOf(req.contract) !== -1) {
if (req.account === '' || req.account === req.contract) {
@@ -219,6 +232,7 @@ export default class WSRouter extends HyperionWorker {
const link = {
type: 'action',
relay: id,
reqUUID: data.reqUUID,
client: data.client_socket,
filters: req.filters,
account: req.account,
@@ -230,7 +244,10 @@ export default class WSRouter extends HyperionWorker {
if (req.account !== '') {
this.appendToL1Map(this.notifiedMap, req.account, link);
} else {
return {status: 'FAIL', reason: 'invalid request'};
return {
status: 'FAIL',
reason: 'invalid request'
};
}
}
this.addToClientIndex(data, id, [req.contract, req.action, req.account]);
@@ -257,6 +274,7 @@ export default class WSRouter extends HyperionWorker {
const link = {
type: 'delta',
relay: id,
reqUUID: data.reqUUID,
client: data.client_socket,
filters: data.request.filters,
payer: data.request.payer,
@@ -268,7 +286,10 @@ export default class WSRouter extends HyperionWorker {
if (req.payer !== '' && req.payer !== '*') {
this.appendToL1Map(this.payerMap, req.payer, link);
} else {
return {status: 'FAIL', reason: 'invalid request'};
return {
status: 'FAIL',
reason: 'invalid request'
};
}
}
this.addToClientIndex(data, id, [req.code, req.table, req.payer]);
@@ -340,7 +361,7 @@ export default class WSRouter extends HyperionWorker {
if (result.status === 'OK') {
callback(result);
} else {
callback(result.reason);
callback(result);
}
break;
}
@@ -349,7 +370,7 @@ export default class WSRouter extends HyperionWorker {
if (result.status === 'OK') {
callback(result);
} else {
callback(result.reason);
callback(result);
}
break;
}
@@ -412,7 +433,7 @@ export default class WSRouter extends HyperionWorker {
});
}
if (allow) {
relay.emit('trace', {client: link.client, message: msg});
relay.emit('trace', {client: link.client, req: link.reqUUID, message: msg});
this.totalRoutedMessages++;
}
}
@@ -427,15 +448,15 @@ export default class WSRouter extends HyperionWorker {
} else {
allow = true;
}
// if (link.filters?.length > 0) {
// // check filters
// const _parsedMsg = JSON.parse(msg);
// allow = link.filters.every(filter => {
// return checkDeltaFilter(filter, _parsedMsg);
// });
// }
if (link.filters?.length > 0) {
// check filters
const _parsedMsg = JSON.parse(msg);
allow = link.filters.every(filter => {
return checkDeltaFilter(filter, _parsedMsg);
});
}
if (allow) {
relay.emit('delta', {client: link.client, message: msg});
relay.emit('delta', {client: link.client, req: link.reqUUID, message: msg});
this.totalRoutedMessages++;
}
}