Compare commits

...

124 Commits

Author SHA1 Message Date
Igor Lins e Silva d7c372654c Merge pull request #114 from bensig/v3.3.5
Helpful Scripts
2022-09-11 16:25:48 -03:00
Ben Sigman 360f09b3df Remove changes to run.sh 2022-08-12 21:52:19 -07:00
Ben Sigman 5b4b7e7a22 Update scripts + add error catch to python paths 2022-08-12 21:45:28 -07:00
Ben Sigman c6d0da9ed8 Update fix-missing-blocks.py to work for any user 2022-08-12 20:56:07 -07:00
Ben Sigman af242f4297 Cleanup scripts 2022-08-12 01:35:45 -07:00
Ben Sigman ef257e6f4c Add useful scripts 2022-08-12 01:30:59 -07:00
Ben Sigman 93b22c46aa Merge remote-tracking 'upstream/v3.3.5' v3.3.5 2022-08-12 01:25:17 -07:00
Ben Sigman f6c669519c Update syncbyrange to use 1,000,000 block span 2022-04-12 23:51:58 -04:00
Ben Sigman 9b7beceef1 Add sync by range and fixmissing scripts 2022-04-12 13:29:35 -04:00
Igor Lins e Silva 9e2e48e6ed fix wrong api assignment 2022-04-07 04:57:45 -03:00
Ben Sigman 089a7e99ae add syncbyrange.sh 2022-04-05 03:48:23 -04:00
Igor Lins e Silva 2cec16e22f Update state-reader.ts 2022-04-05 03:40:16 -03:00
Igor Lins e Silva f8f349b982 retry removal every 5 seconds for 30s 2022-04-05 03:28:36 -03:00
Igor Lins e Silva 1f8f81411c more robust fork handling 2022-04-05 02:38:39 -03:00
Igor Lins e Silva d1ce1b22af Merge pull request #93 from ankh2054/v3.3.5
path updates to python config to make it easier to start & Allow ES pattern to be set in config for multi chain use
2022-04-05 01:30:44 -03:00
Ben Sigman 4f87c1a2e1 Update scripts and addons eosjs 2022-04-03 23:29:00 -04:00
Charles Holtzkampf 9331fa8ef3 Merge branch 'eosrio:v3.3.5' into v3.3.5 2022-03-30 10:08:25 +01:00
Charles Holtzkampf 638ae1ea80 set ES pattern based on config for multichain use 2022-03-30 10:07:38 +01:00
Charles Holtzkampf 23ca3ef943 path updates 2022-03-02 13:57:31 +00:00
Igor Lins e Silva 3f9217e741 add global-agent support 2022-02-15 23:20:28 -03:00
Igor Lins e Silva 62ee9b2ac7 Merge pull request #82 from eosrio/v3.3.5-fix-install-env
Fix install env script
2022-02-09 11:31:52 -03:00
Thiago Souza 6d0c91c6a4 move rabbitmq installation script to correct place 2022-02-09 11:50:09 +00:00
Thiago Souza 73876475ea use jvm.options.d to configure elasticsearch 2022-02-09 11:05:49 +00:00
Igor Lins e Silva 7d14977702 Merge pull request #80 from bensig/v3.3.5
install_env.sh: Remove comment from es user
2022-02-05 01:06:15 -03:00
bensig 9cdf8db87a Update to node 16 2022-02-04 20:02:14 -08:00
bensig 68c1497e74 install_env.sh: Remove comment from es user
hyp-config: fix ts error
install_env.sh fix dl.bintray.com issue

Signed-off-by: bensig <bensig@gmail.com>
2022-02-04 19:12:21 -08:00
Igor Lins e Silva a7df9fb826 Merge pull request #76 from ankh2054/v3.3.5
Updated code to reflect log file text of no blocks processed.
2022-01-15 19:15:21 -03:00
Igor Lins e Silva c570b6e4c4 add settings.es_replicas
define number of replicas for the templates
2022-01-12 10:40:10 -03:00
Charles Holtzkampf 884d944937 changes to how logfile text displays of no block processed 2021-12-29 11:03:49 +05:00
Charles Holtzkampf 5711353471 changes to how logfile text displays of no block processed 2021-12-29 10:57:50 +05:00
Igor Lins e Silva bd8fe99594 Merge pull request #70 from ankh2054/v3.3.5
Update README.MD - specify version of Elasticsearch.
2021-12-09 17:05:47 -03:00
Charles Holtzkampf 9cfea6bb72 Update README.MD 2021-12-07 06:31:35 +00:00
Charles Holtzkampf 6825b743b6 Update README.MD 2021-12-06 20:38:43 +00:00
Igor Lins e Silva 12fc936075 allow injecting IPC handlers from plugins to master 2021-11-29 03:06:52 -03:00
Igor Lins e Silva 2e5bd27e1b include plugin initOnce entrypoint 2021-11-26 02:32:33 -03:00
Igor Lins e Silva a3420a27c0 add support for plugin stream handlers 2021-11-26 01:31:40 -03:00
Igor Lins e Silva 7212ca8397 update & cleanup 2021-11-25 23:48:19 -03:00
Igor Lins e Silva c1366bad75 move tooling scripts to separate folders 2021-11-25 23:48:02 -03:00
Igor Lins e Silva ba273f96d1 Merge pull request #68 from ankh2054/v3.3.5
python script to identify and fix missing blocks
2021-11-25 23:44:01 -03:00
Charles Holtzkampf 03ce188b96 python fix block thingy 2021-11-15 09:28:09 +00:00
Igor Lins e Silva 8633c19792 include signatures on get_actions schema 2021-11-04 12:56:38 -03:00
Igor Lins e Silva 7eb46ece9d cleanup + no-explorer launch fix 2021-10-11 16:13:44 -03:00
Igor Lins e Silva 862817bbe3 Update deserializer.ts 2021-10-11 14:38:50 -03:00
Igor Lins e Silva 917a17b392 Update deserializer.ts 2021-10-11 14:36:23 -03:00
Igor Lins e Silva 3f26e0d518 Update deserializer.ts 2021-10-11 14:29:43 -03:00
Igor Lins e Silva 139a3b85e8 allow passing POST requests to cache manager 2021-09-19 05:12:16 +04:00
Igor Lins e Silva b82382fc5e Merge pull request #64 from poplexity/v3.3.5
Add support for git repo URLs
2021-09-17 20:00:41 -03:00
Jesse Schulman 67b1da2c82 Add support for git repo URLs 2021-09-17 15:58:17 -07:00
João Barbosa fc0e57ca57 Small change on hyp-config list chains output 2021-09-17 15:19:37 -03:00
Igor Lins e Silva 13ad1445e5 remove ecosystem file from gitignore 2021-09-16 23:47:14 -03:00
Igor Lins e Silva 4c127a4d39 3.3.5 2021-09-16 12:06:19 -03:00
Igor Lins e Silva 03a56359da Update package.json 2021-09-13 15:53:37 -03:00
Igor Lins e Silva 6afb85fd57 3.3.5 cleanup 2021-09-13 15:53:00 -03:00
Igor Lins e Silva 2c95cfbcf5 Update .gitignore 2021-09-13 04:16:05 -03:00
Igor Lins e Silva 8552dbf31e Update example.config.json 2021-09-13 04:14:46 -03:00
Igor Lins e Silva f545aad172 Update .gitignore 2021-09-13 04:14:36 -03:00
Igor Lins e Silva 5e68b54610 Update .gitignore 2021-09-13 04:14:16 -03:00
Igor Lins e Silva 1e79521ccc add 1 to the total indexed blocks to counter the missing block 1 on ship 2021-09-09 22:32:23 -03:00
Igor Lins e Silva 18ef675b88 cleanup 2021-09-09 03:27:14 -03:00
Igor Lins e Silva 0947f43b38 fix missing deltas on 2.1 2021-09-09 03:23:23 -03:00
Igor Lins e Silva 0d5d438378 fix heatmap mode 2021-09-09 02:54:13 -03:00
Igor Lins e Silva e678678fef assert queue to avoid race condition 2021-09-08 23:37:36 -03:00
Igor Lins e Silva 0eaba697eb Update get_abi_snapshot.ts 2021-09-08 23:18:56 -03:00
Igor Lins e Silva 80bd4adc8c 3.3.4-rc7
- include config disable_rate_limit
- minor package upgrades
- date parser fix
2021-09-06 10:23:58 -03:00
Igor Lins e Silva 81ae93b1bd Merge pull request #62 from eosrio/es-8-alpha
Es 8 alpha
2021-09-06 10:12:43 -03:00
Igor Lins e Silva 49d3b3434c Update elastic-routes.ts 2021-09-06 10:11:04 -03:00
Igor Lins e Silva 350d2107fe 3.3.4-rc7
validate date inputs before forwarding to ES
avoid duplicated checking of failed token balances
2021-09-06 10:04:24 -03:00
Igor Lins e Silva eda6655600 implement tx caching on v1 get_transaction 2021-08-26 01:39:39 -03:00
Igor Lins e Silva 8ed55f003d allow pre-deserialized block struct 2021-08-22 22:53:00 -03:00
Igor Lins e Silva db3c18fdf9 Update ecosystem_settings.js 2021-08-20 00:17:30 -03:00
Igor Lins e Silva 2d58c18048 Update ecosystem_settings.js 2021-08-20 00:16:32 -03:00
Igor Lins e Silva e21e3dd314 update packages 2021-08-17 00:38:51 -03:00
Igor Lins e Silva d854b339f9 cleanup 2021-08-17 00:32:55 -03:00
Igor Lins e Silva 3026ab9d25 remove type on bulk request 2021-08-17 00:31:22 -03:00
Igor Lins e Silva 9a7ee17508 Update elastic-routes.ts 2021-08-17 00:24:13 -03:00
Igor Lins e Silva 41dce730db Update elastic-routes.ts 2021-08-17 00:15:45 -03:00
Igor Lins e Silva 4bbc92d888 Update elastic-routes.ts 2021-08-17 00:14:00 -03:00
Igor Lins e Silva b08448812e re-introduce delta present as byte 2021-08-10 07:46:06 -03:00
Igor Lins e Silva 82f31f82ba merge tx_cache feature 2021-08-05 21:54:04 -03:00
Igor Lins e Silva 2a82e5842a v3.3.4-rc6
- update ws to v8
- add indexer.disable_delta_rm option
2021-08-05 20:50:15 -03:00
Igor Lins e Silva f27f5ca2a9 Update server.ts 2021-08-05 13:28:44 -03:00
Igor Lins e Silva cc87fab9cd update packages 3.3.4-rc6 2021-08-05 13:27:37 -03:00
Igor Lins e Silva 5242a757ac cleanup 2021-08-05 13:27:13 -03:00
Igor Lins e Silva d2a16e6059 fix get_transaction empty output + implement cached_lib + allow block_hint 2021-08-05 13:17:30 -03:00
Igor Lins e Silva 99c43af7bf docker updates 2021-07-31 04:45:52 -03:00
Igor Lins e Silva a8452d7906 Update fix-permissions.js 2021-07-27 06:03:39 -03:00
Igor Lins e Silva b8104a1f0c require node v16 2021-07-27 05:59:24 -03:00
Igor Lins e Silva d3b4e75513 Update .gitignore 2021-07-27 05:58:08 -03:00
Igor Lins e Silva 204bb63bd7 3.3.4-rc5 2021-07-21 07:40:02 -03:00
Igor Lins e Silva 034dccfbab Update index.d.ts 2021-07-20 06:17:15 -03:00
Igor Lins e Silva aafd4a3d63 Update routes.ts 2021-07-20 06:17:06 -03:00
Igor Lins e Silva cbe37827fc fix error handling on amqp 2021-07-12 14:23:40 -03:00
Igor Lins e Silva aa3487b238 Update example-connections.json 2021-07-12 00:15:49 -03:00
Igor Lins e Silva c090822d3b re-add frameMax patch 2021-07-12 00:14:30 -03:00
Igor Lins e Silva 0564efd4ac Update .gitignore 2021-07-09 07:22:50 -03:00
Igor Lins e Silva cd7620c09b log cleanup 2021-06-14 11:20:38 -03:00
Igor Lins e Silva c7078f6973 Update state-reader.ts 2021-06-14 11:00:03 -03:00
Igor Lins e Silva 9508b34edb Update state-reader.ts 2021-06-14 10:34:29 -03:00
Igor Lins e Silva 0a577ebbf3 Update state-reader.ts 2021-06-14 10:20:14 -03:00
Igor Lins e Silva b9ba061967 improved reading controller 2021-06-14 10:04:54 -03:00
Igor Lins e Silva 9ba24d4acd Update state-reader.ts 2021-06-14 09:21:19 -03:00
Igor Lins e Silva bf4d84cc1d improved reader controller + logging 2021-06-14 08:59:37 -03:00
Igor Lins e Silva 10e369685a Update eosio-buyram.js 2021-06-14 08:17:39 -03:00
Igor Lins e Silva b402f183bd 3.3.4-rc3
- fix missing consumers on some indexing queues
2021-06-14 08:11:02 -03:00
Igor Lins e Silva cdd39ff2f1 3.3.4-rc2
- fix for ship nodes that transitioned from 2.0.x to 2.1 without replay
2021-06-14 06:39:55 -03:00
Igor Lins e Silva 5a05b068fe fix param typo 2021-06-10 09:02:34 -03:00
Igor Lins e Silva 7cea98e3fb log cleanup 2021-06-10 08:41:13 -03:00
Igor Lins e Silva 1c85c2b3cd add block_id for action template 2021-06-10 08:14:30 -03:00
Igor Lins e Silva cbefd0272d Delete .state.json 2021-06-10 07:55:28 -03:00
Igor Lins e Silva fc57c7fb41 v3.3.4-rc1 2021-06-10 07:53:14 -03:00
Igor Lins e Silva 31969f0c8b update ws types 2021-05-21 20:50:05 -03:00
Igor Lins e Silva d8bad499b4 update packages 2021-05-21 14:37:07 -03:00
Igor Lins e Silva 7e2e09af23 Update get_transaction.ts 2021-04-28 18:20:46 -03:00
Igor Lins e Silva f4dea4305d fix missing alert channel 2021-04-20 10:14:26 -03:00
Igor Lins e Silva 65c55c7ebb support variant struct on dynamic mappings 2021-04-19 07:54:57 -03:00
Igor Lins e Silva fc1566a6c1 Update master.ts 2021-04-19 04:40:41 -03:00
Igor Lins e Silva 85581e288e handle forks for check_transaction 2021-04-17 15:25:42 -03:00
Igor Lins e Silva 35e93ef98e Update example.config.json 2021-04-17 13:27:25 -03:00
Igor Lins e Silva 218943cfe4 configurable api rate limiter 2021-04-17 13:25:32 -03:00
Igor Lins e Silva 996b36dd15 alerts module WIP 2021-04-12 15:02:33 -03:00
Igor Lins e Silva a9f8e545b4 [api] add check_transaction 2021-04-12 15:01:59 -03:00
Igor Lins e Silva 0cfac309a7 disable auto_stop by default 2021-04-12 15:01:05 -03:00
Igor Lins e Silva 9d7bf75c25 Update package.json 2021-04-06 03:44:14 -03:00
Igor Lins e Silva ea80bf8c92 prepare 3.3 pre-release branch 2021-04-06 03:42:25 -03:00
276 changed files with 12262 additions and 36016 deletions
+18 -32
View File
@@ -1,3 +1,5 @@
# elastic
elastic_pass.txt
# Logs
logs
*.log
@@ -11,33 +13,8 @@ pids
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
.npm
@@ -51,18 +28,11 @@ typings/
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# next.js build output
.next
.idea
*.pem
ecosystem.config.js
package-lock.json
connections.json
connections.json2
@@ -106,4 +76,20 @@ modules/**/*.js.map
addons/**/*.js
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
.gitmodules
docker/redis/data
docker/elasticsearch/data
docker/rabbitmq/data
docker/eosio/data
configuration_backups
+8 -8
View File
@@ -1,6 +1,6 @@
# Hyperion History API
<img height="64" src="https://eosrio.io/hyperion.png">
<img alt="hyperion logo" height="64" src="https://eosrio.io/hyperion.png">
<br/>
Scalable Full History API Solution for EOSIO based blockchains
@@ -21,30 +21,30 @@ no blocks or transaction data is stored, all information can be reconstructed fr
### 2. Architecture
The following components are required in order to have a fully functional Hyperion API deployment,
for small use cases its fine to run all components on a single machine. But for larger chains and
for small use cases its absolutely fine to run all components on a single machine. For larger chains and
production environments we recommend setting them up into different servers under a high-speed local network.
#### 2.1 - Elasticsearch Cluster
#### 2.1 Elasticsearch Cluster
The ES cluster is responsible for storing all indexed data.
Direct access to the Hyperion API and Indexer must be provided. We recommend nodes in the
cluster to have at least 32GB of RAM and 8 cpu cores. SSD/NVME drives are recommended for
cluster to have at least 32 GB of RAM and 8 cpu cores. SSD/NVME drives are recommended for
maximum indexing throughput. For production environments a multi-node cluster is highly recommended.
#### 2.2 - Hyperion Indexer
#### 2.2 Hyperion Indexer
The Indexer is a Node.js based app that process data from the state history plugin and allows it to be indexed.
The PM2 process manager is used to launch and operate the indexer. The configuration flexibility is very extensive,
so system recommendations will depend on the use case and data load. It will require access to at least one ES node,
RabbitMQ and the state history node.
#### 2.3 - Hyperion API
#### 2.3 Hyperion API
Parallelizable API server that provides the V2 and V1 (legacy history plugin) endpoints.
It is launched by PM2 and can also operate in cluster mode. It requires direct access to
at least one ES node for the queries and all other services for full healthcheck
#### 2.4 - RabbitMQ
#### 2.4 RabbitMQ
Use as messaging queue and data transport between the indexer stages
#### 2.5 - EOSIO State History
#### 2.5 EOSIO State History
Nodeos plugin used to collect action traces and state deltas. Provides data via websocket to the indexer
### 3. How to use
+3
View File
@@ -308,6 +308,7 @@ var KeyType;
(function (KeyType) {
KeyType[KeyType["k1"] = 0] = "k1";
KeyType[KeyType["r1"] = 1] = "r1";
KeyType[KeyType["wa"] = 2] = "wa";
})(KeyType = exports.KeyType || (exports.KeyType = {}));
/** Public key data size, excluding type field */
exports.publicKeyDataSize = 33;
@@ -459,6 +460,8 @@ function signatureToString(signature) {
return keyToString(signature, 'K1', 'SIG_K1_');
} else if (signature.type === KeyType.r1) {
return keyToString(signature, 'R1', 'SIG_R1_');
} else if (signature.type === KeyType.wa) {
return keyToString(signature, 'WA', 'SIG_WA_');
} else {
throw new Error('unrecognized signature format');
}
+9 -2
View File
@@ -4,6 +4,7 @@ export function generateOpenApiConfig(config: HyperionConfig) {
const packageData = require('../../package');
const health_link = `https://${config.api.server_name}/v2/health`;
const explorer_link = `https://${config.api.server_name}/v2/explore`;
let description = `
<img height="64" src="https://eosrio.io/hyperion.png">
### Scalable Full History API Solution for EOSIO based blockchains
@@ -13,9 +14,15 @@ export function generateOpenApiConfig(config: HyperionConfig) {
#### Provided by [${config.api.provider_name}](${config.api.provider_url})
#### Health API: <a target="_blank" href="${health_link}">${health_link}</a>
`;
if(config.api.enable_explorer) {
description += `#### Integrated Explorer: <a target="_blank" href="${explorer_link}">${explorer_link}</a>`
if (config.plugins) {
if (config.plugins.explorer) {
if (config.plugins.explorer.enabled) {
description += `#### Integrated Explorer: <a target="_blank" href="${explorer_link}">${explorer_link}</a>`
}
}
}
return {
routePrefix: '/v2/docs',
exposeRoute: true,
+86
View File
@@ -0,0 +1,86 @@
import {HyperionConfig} from "../../interfaces/hyperionConfig";
import {createHash} from "crypto";
import {FastifyRequest} from "fastify";
export interface CacheConfig {
ttl: number;
}
export interface CachedEntry {
data: string;
exp: number;
}
export class CacheManager {
v1CacheConfigs: Map<string, CacheConfig> = new Map<string, CacheConfig>();
v1Caches: Map<string, Map<string, CachedEntry>> = new Map<string, Map<string, CachedEntry>>();
constructor(conf: HyperionConfig) {
if (conf.api.v1_chain_cache) {
conf.api.v1_chain_cache.forEach(value => {
this.v1CacheConfigs.set(value.path, {
ttl: value.ttl
});
this.v1Caches.set(value.path, new Map<string, CachedEntry>());
});
}
setInterval(() => {
try {
// remove expired entries
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++;
}
});
});
if (removeCount > 0) {
console.log(`${removeCount} expired cache entries removed`);
}
} catch (e) {
console.log(e);
}
}, 5000);
}
hashPayload(input: string): string {
return createHash('sha256').update(input).digest().toString('hex');
}
setCachedData(hash: string, path: string, payload: any): void {
if (this.v1CacheConfigs.has(path) && this.v1Caches.has(path)) {
this.v1Caches.get(path).set(hash, {
data: payload,
exp: this.v1CacheConfigs.get(path).ttl + Date.now()
});
}
}
getCachedData(request: FastifyRequest): [string | null, string, string] {
const urlParts = request.url.split("?");
const pathComponents = urlParts[0].split('/');
const path = pathComponents.at(-1);
let payload = '';
if (request.method === 'POST') {
payload = JSON.stringify(request.body);
} else if (request.method === 'GET') {
payload = request.url;
}
const hashedString = this.hashPayload(payload);
if (this.v1Caches.has(path)) {
const entry = this.v1Caches.get(path).get(hashedString);
if (entry && entry.exp > Date.now()) {
return [entry.data, hashedString, path];
} else {
return [null, hashedString, path];
}
} else {
return [null, hashedString, path];
}
}
}
+389 -363
View File
@@ -1,433 +1,459 @@
import {createHash} from "crypto";
import * as _ from "lodash";
import {FastifyInstance, FastifyReply, FastifyRequest, HTTPMethod, RouteSchema} from "fastify";
import {ServerResponse} from "http";
import {FastifyInstance, FastifyReply, FastifyRequest, FastifySchema, HTTPMethods} from "fastify";
import got from "got";
export function extendResponseSchema(responseProps: any) {
const props = {
query_time_ms: {type: "number"},
cached: {type: "boolean"},
hot_only: {type: "boolean"},
lib: {type: "number"},
total: {
type: "object",
properties: {
value: {type: "number"},
relation: {type: "string"}
}
}
};
for (const p in responseProps) {
if (responseProps.hasOwnProperty(p)) {
props[p] = responseProps[p];
}
}
return {
200: {
type: 'object',
properties: props
}
};
const props = {
query_time_ms: {type: "number"},
cached: {type: "boolean"},
hot_only: {type: "boolean"},
lib: {type: "number"},
total: {
type: "object",
properties: {
value: {type: "number"},
relation: {type: "string"}
}
}
};
for (const p in responseProps) {
if (responseProps.hasOwnProperty(p)) {
props[p] = responseProps[p];
}
}
return {
200: {
type: 'object',
properties: props
}
};
}
export function extendQueryStringSchema(queryParams: any, required?: string[]) {
const params = {
limit: {
description: 'limit of [n] results per page',
type: 'integer',
minimum: 1
},
skip: {
description: 'skip [n] results',
type: 'integer',
minimum: 0
}
};
for (const p in queryParams) {
if (queryParams.hasOwnProperty(p)) {
params[p] = queryParams[p];
}
}
const schema = {
type: 'object',
properties: params
}
if (required && required.length > 0) {
schema["required"] = required;
}
return schema;
const params = {
limit: {
description: 'limit of [n] results per page',
type: 'integer',
minimum: 1
},
skip: {
description: 'skip [n] results',
type: 'integer',
minimum: 0
}
};
for (const p in queryParams) {
if (queryParams.hasOwnProperty(p)) {
params[p] = queryParams[p];
}
}
const schema = {
type: 'object',
properties: params
}
if (required && required.length > 0) {
schema["required"] = required;
}
return schema;
}
export async function getCacheByHash(redis, key, chain) {
const hash = createHash('sha256');
const query_hash = hash.update(chain + "-" + key).digest('hex');
return [await redis.get(query_hash), query_hash];
const hash = createHash('sha256');
const query_hash = hash.update(chain + "-" + key).digest('hex');
return [await redis.get(query_hash), query_hash];
}
export function mergeActionMeta(action) {
const name = action.act.name;
if (action['@' + name]) {
action['act']['data'] = _.merge(action['@' + name], action['act']['data']);
delete action['@' + name];
}
action['timestamp'] = action['@timestamp'];
// delete action['@timestamp'];
const name = action.act.name;
if (action['@' + name]) {
action['act']['data'] = _.merge(action['@' + name], action['act']['data']);
delete action['@' + name];
}
action['timestamp'] = action['@timestamp'];
// delete action['@timestamp'];
}
export function mergeDeltaMeta(delta: any) {
const name = delta.table;
if (delta["@" + name]) {
delta['data'] = _.merge(delta['@' + name], delta['data']);
delete delta['@' + name];
}
delta['timestamp'] = delta['@timestamp'];
delete delta['@timestamp'];
return delta;
const name = delta.table;
if (delta["@" + name]) {
delta['data'] = _.merge(delta['@' + name], delta['data']);
delete delta['@' + name];
}
delta['timestamp'] = delta['@timestamp'];
delete delta['@timestamp'];
return delta;
}
export function setCacheByHash(fastify, hash, response, expiration?: number) {
if (fastify.manager.config.api.enable_caching) {
let exp;
if (expiration) {
exp = expiration;
} else {
exp = fastify.manager.config.api.cache_life;
}
fastify.redis.set(hash, JSON.stringify(response), 'EX', exp).catch(console.log);
}
if (fastify.manager.config.api.enable_caching) {
let exp;
if (expiration) {
exp = expiration;
} else {
exp = fastify.manager.config.api.cache_life;
}
fastify.redis.set(hash, JSON.stringify(response), 'EX', exp).catch(console.log);
}
}
export function getRouteName(filename: string) {
const arr = filename.split("/");
return arr[arr.length - 2];
const arr = filename.split("/");
return arr[arr.length - 2];
}
export function addApiRoute(
fastifyInstance: FastifyInstance,
method: HTTPMethod | HTTPMethod[],
routeName: string,
routeBuilder: (fastify: FastifyInstance, route: string) => (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => Promise<void>, schema: RouteSchema) {
fastifyInstance.route({
url: '/' + routeName,
method,
handler: routeBuilder(fastifyInstance, routeName),
schema
});
fastifyInstance: FastifyInstance,
method: HTTPMethods | HTTPMethods[],
routeName: string,
routeBuilder: (fastify: FastifyInstance, route: string) => (request: FastifyRequest, reply: FastifyReply) => Promise<void>,
schema: FastifySchema
) {
fastifyInstance.route({
url: '/' + routeName,
method,
handler: routeBuilder(fastifyInstance, routeName),
schema
});
}
export async function getCachedResponse(server: FastifyInstance, route: string, key: any) {
const chain = server.manager.chain;
let resp, hash;
if (server.manager.config.api.enable_caching) {
[resp, hash] = await getCacheByHash(server.redis, route + JSON.stringify(key), chain);
if (resp) {
resp = JSON.parse(resp);
resp['cached'] = true;
return [resp, hash];
} else {
return [null, hash];
}
} else {
return [null, null];
}
const chain = server.manager.chain;
let resp, hash;
if (server.manager.config.api.enable_caching) {
[resp, hash] = await getCacheByHash(server.redis, route + JSON.stringify(key), chain);
if (resp) {
resp = JSON.parse(resp);
resp['cached'] = true;
return [resp, hash];
} else {
return [null, hash];
}
} else {
return [null, null];
}
}
export function getTrackTotalHits(query) {
let trackTotalHits: number | boolean = 10000;
if (query?.track) {
if (query.track === 'true') {
trackTotalHits = true;
} else if (query.track === 'false') {
trackTotalHits = false;
} else {
const parsed = parseInt(query.track, 10);
if (parsed > 0) {
trackTotalHits = parsed;
} else {
throw new Error('failed to parse track param');
}
}
}
return trackTotalHits;
let trackTotalHits: number | boolean = 10000;
if (query?.track) {
if (query.track === 'true') {
trackTotalHits = true;
} else if (query.track === 'false') {
trackTotalHits = false;
} else {
const parsed = parseInt(query.track, 10);
if (parsed > 0) {
trackTotalHits = parsed;
} else {
throw new Error('failed to parse track param');
}
}
}
return trackTotalHits;
}
function bigint2Milliseconds(input: bigint) {
return parseFloat((parseInt(input.toString()) / 1000000).toFixed(3));
return parseFloat((parseInt(input.toString()) / 1000000).toFixed(3));
}
const defaultRouteCacheMap = {
get_resource_usage: 3600,
get_creator: 3600 * 24
get_resource_usage: 3600,
get_creator: 3600 * 24
}
export async function timedQuery(
queryFunction: (fastify: FastifyInstance, request: FastifyRequest) => Promise<any>,
fastify: FastifyInstance, request: FastifyRequest, route: string): Promise<any> {
queryFunction: (fastify: FastifyInstance, request: FastifyRequest) => Promise<any>,
fastify: FastifyInstance, request: FastifyRequest, route: string): Promise<any> {
// get reference time in nanoseconds
const t0 = process.hrtime.bigint();
// get reference time in nanoseconds
const t0 = process.hrtime.bigint();
// check for cached data, return the response hash if caching is enabled
const [cachedResponse, hash] = await getCachedResponse(
fastify,
route,
request.req.method === 'POST' ? request.body : request.query
);
// check for cached data, return the response hash if caching is enabled
const [cachedResponse, hash] = await getCachedResponse(
fastify,
route,
request.method === 'POST' ? request.body : request.query
);
if (cachedResponse && !request.query.ignoreCache) {
// add cached query time
cachedResponse['query_time_ms'] = bigint2Milliseconds(process.hrtime.bigint() - t0);
return cachedResponse;
}
if (cachedResponse && !request.query["ignoreCache"]) {
// add cached query time
cachedResponse['query_time_ms'] = bigint2Milliseconds(process.hrtime.bigint() - t0);
return cachedResponse;
}
// call query function
const response = await queryFunction(fastify, request);
// call query function
const response = await queryFunction(fastify, request);
// save response to cash
if (hash) {
let EX = null;
if (defaultRouteCacheMap[route]) {
EX = defaultRouteCacheMap[route];
}
setCacheByHash(fastify, hash, response, EX);
}
// save response to cash
if (hash) {
let EX = null;
if (defaultRouteCacheMap[route]) {
EX = defaultRouteCacheMap[route];
}
setCacheByHash(fastify, hash, response, EX);
}
// add normal query time
if (response) {
response['query_time_ms'] = bigint2Milliseconds(process.hrtime.bigint() - t0);
return response;
} else {
return {};
}
// add normal query time
if (response) {
response['query_time_ms'] = bigint2Milliseconds(process.hrtime.bigint() - t0);
return response;
} else {
return {};
}
}
export function chainApiHandler(fastify: FastifyInstance) {
return async (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => {
await handleChainApiRedirect(request, reply, fastify);
}
return async (request: FastifyRequest, reply: FastifyReply) => {
// check cache
const [cachedData, hash, path] = fastify.cacheManager.getCachedData(request);
if (cachedData) {
// console.log('cache hit:', path, hash);
reply.headers({'hyperion-cached': true}).send(cachedData);
} else {
// call actual request
const apiResponse = await handleChainApiRedirect(request, reply, fastify);
// console.log('cache miss:', path, hash);
fastify.cacheManager.setCachedData(hash, path, apiResponse);
}
}
}
export async function handleChainApiRedirect(
request: FastifyRequest,
reply: FastifyReply<ServerResponse>,
fastify: FastifyInstance
) {
const urlParts = request.req.url.split("?");
let reqUrl = fastify.chain_api + urlParts[0];
request: FastifyRequest,
reply: FastifyReply,
fastify: FastifyInstance
): Promise<string> {
const urlParts = request.url.split("?");
let reqUrl = fastify.chain_api + urlParts[0];
if (urlParts[0] === '/v1/chain/push_transaction' && fastify.push_api && fastify.push_api !== "") {
reqUrl = fastify.push_api + urlParts[0];
}
// const pathComponents = urlParts[0].split('/');
// const path = pathComponents.at(-1);
const opts = {};
if (request.req.method === 'POST') {
if (request.body) {
if (typeof request.body === 'string') {
opts['body'] = request.body;
} else if (typeof request.body === 'object') {
opts['body'] = JSON.stringify(request.body);
}
} else {
opts['body'] = "";
}
} else if (request.req.method === 'GET') {
opts['json'] = request.query;
}
if (urlParts[0] === '/v1/chain/push_transaction' && fastify.push_api && fastify.push_api !== "") {
reqUrl = fastify.push_api + urlParts[0];
}
try {
const apiResponse = await got.post(reqUrl, opts);
reply.headers({"Content-Type": "application/json"});
if (request.req.method === 'HEAD') {
reply.headers({"Content-Length": apiResponse.body.length});
reply.send("");
} else {
reply.send(apiResponse.body);
}
} catch (error) {
if (error.response) {
reply.status(error.response.statusCode).send(error.response.body);
} else {
console.log(error);
reply.status(500).send();
}
if (fastify.manager.config.api.chain_api_error_log) {
try {
if (error.response) {
const error_msg = JSON.parse(error.response.body).error.details[0].message;
console.log(`endpoint: ${request.req.url} | status: ${error.response.statusCode} | error: ${error_msg}`);
} else {
console.log(error);
}
// if (request.req.url === '/v1/chain/push_transaction') {
// const packedTrx = JSON.parse(opts['body']).packed_trx;
// const trxBuffer = Buffer.from(packedTrx, 'hex');
// const trxData = await fastify.eosjs.api.deserializeTransactionWithActions(trxBuffer);
// console.log(trxData);
// }
} catch (e) {
console.log(e);
}
}
}
const opts = {};
if (request.method === 'POST') {
if (request.body) {
if (typeof request.body === 'string') {
opts['body'] = request.body;
} else if (typeof request.body === 'object') {
opts['body'] = JSON.stringify(request.body);
}
} else {
opts['body'] = "";
}
} else if (request.method === 'GET') {
opts['json'] = request.query;
}
try {
const apiResponse = await got.post(reqUrl, opts);
reply.headers({"Content-Type": "application/json"});
if (request.method === 'HEAD') {
reply.headers({"Content-Length": apiResponse.body.length});
reply.send("");
return '';
} else {
reply.send(apiResponse.body);
return apiResponse.body;
}
} catch (error) {
if (error.response) {
reply.status(error.response.statusCode).send(error.response.body);
} else {
console.log(error);
reply.status(500).send();
}
if (fastify.manager.config.api.chain_api_error_log) {
try {
if (error.response) {
const error_msg = JSON.parse(error.response.body).error.details[0].message;
console.log(`endpoint: ${request.raw.url} | status: ${error.response.statusCode} | error: ${error_msg}`);
} else {
console.log(error);
}
// if (request.req.url === '/v1/chain/push_transaction') {
// const packedTrx = JSON.parse(opts['body']).packed_trx;
// const trxBuffer = Buffer.from(packedTrx, 'hex');
// const trxData = await fastify.eosjs.api.deserializeTransactionWithActions(trxBuffer);
// console.log(trxData);
// }
} catch (e) {
console.log(e);
}
}
return '';
}
}
export function addChainApiRoute(fastify: FastifyInstance, routeName, description, props?, required?) {
const baseSchema = {
description: description,
summary: description,
tags: ['chain']
};
addApiRoute(
fastify,
['GET', 'HEAD'],
routeName,
chainApiHandler,
{
...baseSchema,
querystring: props ? {
type: 'object',
properties: props,
required: required
} : undefined
}
);
addApiRoute(
fastify,
'POST',
routeName,
chainApiHandler,
{
...baseSchema,
body: props ? {
type: ['object', 'string'],
properties: props,
required: required
} : undefined
}
);
const baseSchema = {
description: description,
summary: description,
tags: ['chain']
};
addApiRoute(
fastify,
['GET', 'HEAD'],
routeName,
chainApiHandler,
{
...baseSchema,
querystring: props ? {
type: 'object',
properties: props,
required: required
} : undefined
}
);
addApiRoute(
fastify,
'POST',
routeName,
chainApiHandler,
{
...baseSchema,
body: props ? {
type: ['object', 'string'],
properties: props,
required: required
} : undefined
}
);
}
export function addSharedSchemas(fastify: FastifyInstance) {
fastify.addSchema({
$id: "WholeNumber",
description: "A whole number",
anyOf: [
{
type: "string",
pattern: "^\\d+$"
},
{
type: "integer"
}
],
});
fastify.addSchema({
$id: "WholeNumber",
title: "Integer",
description: "Integer or String",
anyOf: [
{
type: "string",
pattern: "^\\d+$"
},
{
type: "integer"
}
],
});
fastify.addSchema({
$id: "Symbol",
type: "string",
description: "A symbol composed of capital letters between 1-7.",
pattern: "^([A-Z]{1,7})$",
title: "Symbol"
});
fastify.addSchema({
$id: "Symbol",
type: "string",
description: "A symbol composed of capital letters between 1-7.",
pattern: "^([A-Z]{1,7})$",
title: "Symbol"
});
fastify.addSchema({
$id: "Signature",
type: "string",
description: "String representation of an EOSIO compatible cryptographic signature",
pattern: "^SIG_([RK]1|WA)_[1-9A-HJ-NP-Za-km-z]+$",
title: "Signature"
})
fastify.addSchema({
$id: "Signature",
type: "string",
description: "String representation of an EOSIO compatible cryptographic signature",
pattern: "^SIG_([RK]1|WA)_[1-9A-HJ-NP-Za-km-z]+$",
title: "Signature"
})
fastify.addSchema({
$id: "AccountName",
"anyOf": [
{
"type": "string",
"description": "String representation of privileged EOSIO name type",
"pattern": "^(eosio[\\.][a-z1-5]{1,6})([a-j]{1})?$",
"title": "NamePrivileged"
},
{
"type": "string",
"description": "String representation of basic EOSIO name type, must be 12 characters and contain only a-z and 0-5",
"pattern": "^([a-z]{1}[a-z1-5]{11})([a-j]{1})?$",
"title": "NameBasic"
},
{
"type": "string",
"description": "String representation of EOSIO bid name type, 1-12 characters and only a-z and 0-5 are allowed",
"pattern": "^([a-z1-5]{1,12})([a-j]{1})?$",
"title": "NameBid"
},
{
"type": "string",
"description": "String representation of EOSIO name type",
"pattern": "^([a-z1-5]{1}[a-z1-5\\.]{0,10}[a-z1-5]{1})([a-j]{1})?$",
"title": "NameCatchAll"
}
],
"title": "Name"
});
fastify.addSchema({
$id: "AccountName",
description: "String representation of an EOSIO compatible account name",
"anyOf": [
{
"type": "string",
"description": "String representation of privileged EOSIO name type",
"pattern": "^(eosio[\\.][a-z1-5]{1,6})([a-j]{1})?$",
"title": "NamePrivileged"
},
{
"type": "string",
"description": "String representation of basic EOSIO name type, must be 12 characters and contain only a-z and 0-5",
"pattern": "^([a-z]{1}[a-z1-5]{11})([a-j]{1})?$",
"title": "NameBasic"
},
{
"type": "string",
"description": "String representation of EOSIO bid name type, 1-12 characters and only a-z and 0-5 are allowed",
"pattern": "^([a-z1-5]{1,12})([a-j]{1})?$",
"title": "NameBid"
},
{
"type": "string",
"description": "String representation of EOSIO name type",
"pattern": "^([a-z1-5]{1}[a-z1-5\\.]{0,10}[a-z1-5]{1})([a-j]{1})?$",
"title": "NameCatchAll"
}
],
"title": "Name"
});
fastify.addSchema({
$id: "Expiration",
"description": "Time that transaction must be confirmed by.",
"type": "string",
"pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}$",
"title": "DateTime"
});
fastify.addSchema({
$id: "Expiration",
"description": "Time that transaction must be confirmed by.",
"type": "string",
"pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}$",
"title": "DateTime"
});
fastify.addSchema({
$id: "BlockExtensions",
"type": "array",
"items": {
"anyOf": [{"type": "integer"}, {"type": "string"}]
},
"title": "Extension"
})
fastify.addSchema({
$id: "BlockExtensions",
"type": "array",
"items": {
"anyOf": [
{"type": "integer"},
{"type": "string"}
]
},
"title": "Extension"
})
fastify.addSchema({
$id: "ActionItems",
"type": "object",
"additionalProperties": false,
"minProperties": 5,
"required": [
"account",
"name",
"authorization",
"data",
"hex_data"
],
"properties": {
"account": 'AccountName#',
"name": 'AccountName#',
"authorization": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"minProperties": 2,
"required": [
"actor",
"permission"
],
"properties": {
"actor": 'AccountName#',
"permission": 'AccountName#'
},
"title": "Authority"
}
},
"data": {
"type": "object",
"additionalProperties": true
},
"hex_data": {
"type": "string"
}
},
"title": "Action"
});
fastify.addSchema({
$id: "ActionItems",
"type": "object",
"additionalProperties": false,
"minProperties": 5,
"required": [
"account",
"name",
"authorization",
"data",
"hex_data"
],
"properties": {
"account": {$ref: 'AccountName#'},
"name": {$ref: 'AccountName#'},
"authorization": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"minProperties": 2,
"required": [
"actor",
"permission"
],
"properties": {
"actor": {$ref: 'AccountName#'},
"permission": {$ref: 'AccountName#'}
},
"title": "Authority"
}
},
"data": {
"type": "object",
"additionalProperties": true
},
"hex_data": {
"type": "string"
}
},
"title": "Action"
});
}
+22 -12
View File
@@ -2,22 +2,32 @@ import * as Fastify from "fastify";
import {IncomingMessage, Server, ServerResponse} from "http";
// fastify plugins
import * as fastify_elasticsearch from 'fastify-elasticsearch';
import * as fastify_oas from 'fastify-oas';
import * as fastify_cors from 'fastify-cors';
import * as fastify_formbody from 'fastify-formbody';
import * as fastify_redis from 'fastify-redis';
import * as fastify_rate_limit from 'fastify-rate-limit';
import * as fastifyElasticsearch from 'fastify-elasticsearch';
import fastifySwagger from 'fastify-swagger';
import fastifyCors from 'fastify-cors';
import fastifyFormbody from 'fastify-formbody';
import fastifyRedis from 'fastify-redis';
import fastifyRateLimit from 'fastify-rate-limit';
// custom plugins
import fastify_eosjs from "./plugins/fastify-eosjs";
export function registerPlugins(server: Fastify.FastifyInstance<Server, IncomingMessage, ServerResponse>, params: any) {
server.register(fastify_elasticsearch, params.fastify_elasticsearch);
server.register(fastify_oas, params.fastify_oas);
server.register(fastify_cors);
server.register(fastify_formbody);
server.register(fastify_redis, params.fastify_redis);
server.register(fastifyElasticsearch, params.fastify_elasticsearch);
if (params.fastify_swagger) {
server.register(fastifySwagger, params.fastify_swagger);
}
server.register(fastifyCors);
server.register(fastifyFormbody);
server.register(fastifyRedis, params.fastify_redis);
if (params.fastify_rate_limit) {
server.register(fastifyRateLimit, params.fastify_rate_limit);
}
server.register(fastify_eosjs, params.fastify_eosjs);
server.register(fastify_rate_limit, params.fastify_rate_limit);
}
-21
View File
@@ -1,21 +0,0 @@
const fp = require('fastify-plugin');
const {Api} = require("eosjs");
const {ConnectionManager} = require('../../connections/manager');
const manager = new ConnectionManager();
module.exports = fp(async (fastify, options, next) => {
const rpc = 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});
next();
}, {
fastify: '>=2.0.0',
name: 'fastify-eosjs'
});
+15 -16
View File
@@ -1,20 +1,19 @@
import * as fp from 'fastify-plugin';
import {FastifyInstance} from "fastify";
import {FastifyInstance, FastifyPluginOptions} from "fastify";
import {Api} from "eosjs/dist";
import fp from "fastify-plugin";
export default fp(async (fastify: FastifyInstance, options, next) => {
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});
next();
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});
}, {
fastify: '>=2.0.0',
name: 'fastify-eosjs'
fastify: '>=2.0.0',
name: 'fastify-eosjs'
});
-30
View File
@@ -1,30 +0,0 @@
const fp = require('fastify-plugin');
const WebSocket = require('ws');
module.exports = fp((fastify, opts, next) => {
const wss = new WebSocket.Server({
server: fastify.server
});
const ws = new WebSocket('ws://127.0.0.1:7001', [], {
perMessageDeflate: false
});
ws.on('open', function open() {
console.log('router connected');
});
fastify.decorate('wss', wss);
fastify.decorate('ws', ws);
fastify.addHook('onClose', (fastify, done) => {
fastify.ws.close(done);
fastify.wss.close(done);
});
next();
}, {
fastify: '2.x',
name: 'fastify-websocket'
});
+63 -86
View File
@@ -1,32 +1,52 @@
import * as fastify_static from "fastify-static";
import {join} from "path";
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import {createReadStream, existsSync, readFileSync, unlinkSync} from "fs";
import * as AutoLoad from "fastify-autoload";
import {FastifyError, FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {createReadStream} from "fs";
import {addSharedSchemas, handleChainApiRedirect} from "./helpers/functions";
import autoLoad from 'fastify-autoload';
import got from "got";
function addRedirect(server: FastifyInstance, url: string, redirectTo: string) {
server.route({
url,
method: 'GET',
schema: {hide: true},
handler: async (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => {
schema: {
hide: true
},
handler: async (request: FastifyRequest, reply: FastifyReply) => {
reply.redirect(redirectTo);
}
});
}
function addRoute(server: FastifyInstance, handlersPath: string, prefix: string) {
server.register(AutoLoad, {
server.register(autoLoad, {
dir: join(__dirname, 'routes', handlersPath),
ignorePattern: /.*(handler|schema).js/,
dirNameRoutePrefix: false,
options: {prefix}
});
}
export function registerRoutes(server: FastifyInstance) {
// build internal map of routes
const routeSet = new Set<string>();
server.decorate('routeSet', routeSet);
const ignoreList = [
'/v2',
'/v2/history',
'/v2/state',
'/v1/chain/*',
'/v1/chain'
];
server.addHook('onRoute', opts => {
if (!ignoreList.includes(opts.url)) {
if (opts.url.startsWith('/v')) {
routeSet.add(opts.url);
}
}
});
// Register fastify api routes
addRoute(server, 'v2', '/v2');
addRoute(server, 'v2-history', '/v2/history');
@@ -41,6 +61,8 @@ export function registerRoutes(server: FastifyInstance) {
// chain api redirects
addRoute(server, 'v1-chain', '/v1/chain');
// other v1 requests
server.route({
url: '/v1/chain/*',
method: ["GET", "POST"],
@@ -48,93 +70,48 @@ export function registerRoutes(server: FastifyInstance) {
summary: "Wildcard chain api handler",
tags: ["chain"]
},
handler: async (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => {
handler: async (request: FastifyRequest, reply: FastifyReply) => {
console.log(request.url);
await handleChainApiRedirect(request, reply, server);
}
});
server.addHook('onError', (request, reply, error, done) => {
console.log(`[${request.req.headers['x-real-ip']}] ${request.req.method} ${request.req.url} failed with error: ${error.message}`);
// /v1/node/get_supported_apis
server.route({
url: '/v1/node/get_supported_apis',
method: ["GET"],
schema: {
summary: "Get list of supported APIs",
tags: ["node"]
},
handler: async (request: FastifyRequest, reply: FastifyReply) => {
const data = await got.get(`${server.chain_api}/v1/node/get_supported_apis`).json() as any;
if (data.apis && data.apis.length > 0) {
const apiSet = new Set(server.routeSet);
data.apis.forEach((a) => apiSet.add(a));
reply.send({apis: [...apiSet]});
} else {
reply.send({apis: [...server.routeSet], error: 'nodeos did not send any data'});
}
}
});
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();
});
// server.addHook('onResponse', (request, reply, done) => {
// if (reply.res.statusCode !== 200) {
// console.log(`${request.req.url} - code: ${reply.res.statusCode}`);
// }
// done();
// });
// Serve integrated explorer
if (server.manager.config.api.enable_explorer) {
server.register(require('fastify-compress'), {global: false});
try {
const _data = readFileSync(join(__dirname, '..', 'hyperion-explorer', 'src', 'manifest.webmanifest'));
const tempPath = join(__dirname, '..', 'hyperion-explorer', 'dist', 'manifest.webmanifest');
if (existsSync(tempPath)) {
unlinkSync(tempPath);
}
const baseManifest = JSON.parse(_data.toString());
baseManifest.name = `Hyperion Explorer - ${server.manager.config.api.chain_name}`;
baseManifest.short_name = baseManifest.name;
server.get('/v2/explore/manifest.webmanifest', (request, reply) => {
reply.send(baseManifest);
});
} catch (e) {
console.log(e);
}
server.register(fastify_static, {
root: join(__dirname, '..', 'hyperion-explorer', 'dist'),
redirect: true,
wildcard: false,
prefix: '/v2/explore',
setHeaders: (res: ServerResponse, path) => {
if (path.endsWith('/ngsw-worker.js')) {
res.setHeader('Service-Worker-Allowed', '/');
}
}
});
server.get(
'/v2/explore/**/*',
{
schema: {
tags: ['internal']
}
},
(request: FastifyRequest, reply: FastifyReply<ServerResponse>) => {
reply.sendFile('index.html', join(__dirname, '..', 'hyperion-explorer', 'dist'));
}
);
server.get(
'/v2/explorer_metadata',
{
schema: {
tags: ['internal']
}
},
(request: FastifyRequest, reply: FastifyReply<ServerResponse>) => {
reply.send({
logo: server.manager.config.api.chain_logo_url,
provider: server.manager.config.api.provider_name,
provider_url: server.manager.config.api.provider_url,
chain_name: server.manager.config.api.chain_name,
chain_id: server.manager.conn.chains[server.manager.chain].chain_id,
custom_core_token: server.manager.config.api.custom_core_token
});
});
}
if (server.manager.config.features.streaming) {
// steam client lib
server.get('/stream-client.js', {schema: {tags: ['internal']}}, (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => {
const stream = createReadStream('./client_bundle.js');
reply.type('application/javascript').send(stream);
});
server.get(
'/stream-client.js',
{schema: {tags: ['internal']}},
(request: FastifyRequest, reply: FastifyReply) => {
const stream = createReadStream('./hyperion-stream-client.js');
reply.type('application/javascript').send(stream);
});
}
// Redirect routes to documentation
-200
View File
@@ -1,200 +0,0 @@
const {getCacheByHash} = require("../../helpers/functions");
const {getTransactedAccountsSchema} = require("../../schemas");
async function getTransactedAccounts(fastify, request) {
const t0 = Date.now();
const {redis, elastic} = fastify;
const [cachedResponse, hash] = await getCacheByHash(redis, JSON.stringify(request.query));
if (cachedResponse) {
return cachedResponse;
}
const {account, min, max, direction, limit, contract, symbol} = request.query;
let _limit = 100;
if (limit) {
_limit = parseInt(limit, 10);
if (_limit > 500) {
_limit = 500;
}
}
let _min;
if (min) {
_min = parseInt(min, 10);
if (_min < 0) {
_min = 0;
}
}
let _max;
if (max) {
_max = parseInt(max, 10);
if (_max < 0) {
_max = null;
}
}
const response = {
query_time: null,
account: account
};
const must_array = [
{term: {"notified": account}},
{term: {"act.name": "transfer"}}
];
if (min || max) {
const _range = {
"@transfer.amount": {}
};
if (min) {
_range["@transfer.amount"]["gte"] = _min;
}
if (max) {
_range["@transfer.amount"]["lt"] = _max;
}
must_array.push({range: _range});
}
if (contract) {
must_array.push({term: {"act.account": contract}});
response['contract'] = contract;
}
if (symbol) {
must_array.push({term: {"@transfer.symbol": symbol}});
response['symbol'] = symbol;
}
const sum_agg = {
"total_amount": {
sum: {
field: "@transfer.amount"
}
}
};
let filter_array = [];
if (request.query['after'] || request.query['before']) {
let _lte = "now";
let _gte = 0;
if (request.query['before']) {
_lte = request.query['before'];
}
if (request.query['after']) {
_gte = request.query['after'];
}
filter_array.push({
range: {
"@timestamp": {
"gte": _gte,
"lte": _lte
}
}
});
} else {
filter_array.push({
range: {
"block_num": {
"gte": 0
}
}
});
}
if (direction === 'out' || direction === 'both') {
const outResults = await elastic.search({
index: process.env.CHAIN + '-action-*',
body: {
size: 0,
aggs: {
"receivers": {
terms: {
field: "@transfer.to",
size: _limit,
order: {
"total_amount": "desc"
}
},
aggs: sum_agg
}
},
query: {
bool: {
must: must_array,
filter: filter_array,
must_not: [
{term: {"@transfer.to": account}}
]
}
}
}
});
response['total_out'] = 0;
response['outputs'] = outResults['body']['aggregations']['receivers']['buckets'].map((bucket) => {
const _sum = parseFloat(bucket.total_amount.value.toFixed(4));
response['total_out'] += _sum;
return {
account: bucket.key,
sum: _sum,
transfers: bucket['doc_count'],
average: parseFloat((bucket.total_amount.value / bucket['doc_count']).toFixed(4))
};
});
response['total_out'] = parseFloat(response['total_out'].toFixed(4));
}
if (direction === 'in' || direction === 'both') {
const inResults = await elastic.search({
index: process.env.CHAIN + '-action-*',
body: {
size: 0,
aggs: {
"senders": {
terms: {
field: "@transfer.from",
size: _limit,
order: {
"total_amount": "desc"
}
},
aggs: sum_agg
}
},
query: {
bool: {
must: must_array,
filter: filter_array,
must_not: [
{term: {"@transfer.from": account}}
]
}
}
}
});
response['total_in'] = 0;
response['inputs'] = inResults['body']['aggregations']['senders']['buckets'].map((bucket) => {
const _sum = parseFloat(bucket.total_amount.value.toFixed(4));
response['total_in'] += _sum;
return {
account: bucket.key,
sum: _sum,
transfers: bucket['doc_count'],
average: parseFloat((bucket.total_amount.value / bucket['doc_count']).toFixed(4))
};
});
response['total_in'] = parseFloat(response['total_in'].toFixed(4));
}
response['query_time'] = Date.now() - t0;
redis.set(hash, JSON.stringify(response), 'EX', 600);
return response;
}
module.exports = function (fastify, opts, next) {
fastify.get('/get_transacted_accounts', {
schema: getTransactedAccountsSchema.GET
}, async (request, reply) => {
reply.send(await getTransactedAccounts(fastify, request));
});
next()
};
-98
View File
@@ -1,98 +0,0 @@
const {getTransfersSchema} = require("../../schemas");
const _ = require('lodash');
const {getCacheByHash} = require("../../helpers/functions");
const route = '/get_transfers';
const maxActions = 1000;
function processActions(results) {
const action_traces = results['body']['hits']['hits'];
const actions = [];
let sum = 0;
for (const aTrace of action_traces) {
const action = aTrace['_source'];
const name = action.act.name;
if (action['@' + name]) {
if (action['@transfer']) {
sum += parseFloat(action['@transfer']['amount']);
}
action['act']['data'] = _.merge(action['@' + name], action['act']['data']);
delete action['@' + name];
}
actions.push(action);
}
return [sum, actions];
}
async function getTransfers(fastify, request) {
const {redis, elastic} = fastify;
const [cachedResponse, hash] = await getCacheByHash(redis, route + JSON.stringify(request.query));
if (cachedResponse) {
return cachedResponse;
}
const must_array = [];
must_array.push({"term": {"act.name": {"value": "transfer"}}});
if (request.query['from']) {
must_array.push({"term": {"@transfer.from": {"value": request.query['from'].toLowerCase()}}});
}
if (request.query['to']) {
must_array.push({"term": {"@transfer.to": {"value": request.query['to'].toLowerCase()}}});
}
if (request.query['symbol']) {
must_array.push({"term": {"@transfer.symbol": {"value": request.query['symbol'].toUpperCase()}}});
}
if (request.query['contract']) {
must_array.push({"term": {"act.account": {"value": request.query['contract'].toLowerCase()}}});
}
if (request.query['after'] || request.query['before']) {
let _lte = "now";
let _gte = 0;
if (request.query['before']) {
_lte = request.query['before'];
}
if (request.query['after']) {
_gte = request.query['after'];
}
must_array.push({
range: {
"@timestamp": {
"gte": _gte,
"lte": _lte
}
}
});
}
let limit, skip;
limit = parseInt(request.query.limit, 10);
if (limit < 1) {
return 'invalid limit parameter';
}
skip = parseInt(request.query.skip, 10);
if (skip < 0) {
return 'invalid skip parameter';
}
const body = {"query": {"bool": {"must": must_array}}};
const results = await elastic.search({
"index": process.env.CHAIN + '-action-*',
"from": skip || 0,
"size": (limit > maxActions ? maxActions : limit) || 10,
"body": body
});
const [sum, _actions] = processActions(results);
const response = {
"action_count": results['body']['hits']['total']['value'],
"total_amount": sum,
"actions": _actions
};
redis.set(hash, JSON.stringify(response), 'EX', 30);
return response;
}
module.exports = function (fastify, opts, next) {
fastify.get(route, {
schema: getTransfersSchema.GET
}, async (request, reply) => {
reply.send(await getTransfers(fastify, request));
});
next()
};
+2 -2
View File
@@ -7,8 +7,8 @@ export default function (fastify: FastifyInstance, opts: any, next) {
getRouteName(__filename),
'Returns an object containing rows from the specified table.',
{
"code": 'AccountName#',
"action": 'AccountName#',
"code": {$ref: 'AccountName#'},
"action": {$ref: 'AccountName#'},
"binargs": {
"type": "string",
"pattern": "^(0x)(([0-9a-f][0-9a-f])+)?$",
+1 -1
View File
@@ -6,7 +6,7 @@ export default function (fastify: FastifyInstance, opts: any, next) {
fastify,
getRouteName(__filename),
'Retrieves the ABI for a contract based on its account name',
{"account_name": 'AccountName#'},
{"account_name": {$ref: 'AccountName#'}},
["account_name"]
);
next();
+3 -1
View File
@@ -6,7 +6,9 @@ export default function (fastify: FastifyInstance, opts: any, next) {
fastify,
getRouteName(__filename),
'Returns an object containing various details about a specific account on the blockchain.',
{"account_name": 'AccountName#'},
{
"account_name": {$ref: 'AccountName#'}
},
["account_name"]
);
next();
+1 -1
View File
@@ -7,7 +7,7 @@ export default function (fastify: FastifyInstance, opts: any, next) {
getRouteName(__filename),
'Retrieves contract code',
{
"account_name": 'AccountName#',
"account_name": {$ref: 'AccountName#'},
"code_as_wasm": {
"type": "integer",
"default": 1,
@@ -7,9 +7,9 @@ export default function (fastify: FastifyInstance, opts: any, next) {
getRouteName(__filename),
'Retrieves the current balance',
{
"code": 'AccountName#',
"account": 'AccountName#',
"symbol": 'Symbol#',
"code": {$ref: 'AccountName#'},
"account": {$ref: 'AccountName#'},
"symbol": {$ref: 'Symbol#'}
},
["code", "account", "symbol"]
);
@@ -7,12 +7,7 @@ export default function (fastify: FastifyInstance, opts: any, next) {
getRouteName(__filename),
'Retrieves currency stats',
{
"code": {
description: 'contract name',
type: 'string',
minLength: 1,
maxLength: 12
},
"code": {$ref: 'AccountName#'},
"symbol": {
description: 'token symbol',
type: 'string',
+1 -1
View File
@@ -7,7 +7,7 @@ export default function (fastify: FastifyInstance, opts: any, next) {
getRouteName(__filename),
'Retrieves raw ABI for a contract based on account name',
{
"account_name": 'AccountName#'
"account_name": {$ref: 'AccountName#'}
},
["account_name"]
);
@@ -7,7 +7,7 @@ export default function (fastify: FastifyInstance, opts: any, next) {
getRouteName(__filename),
'Retrieves raw code and ABI for a contract based on account name',
{
"account_name": 'AccountName#'
"account_name": {$ref: 'AccountName#'}
},
["account_name"]
);
+2 -1
View File
@@ -36,7 +36,8 @@ export default function (fastify: FastifyInstance, opts: any, next) {
"lower_bound": {
"type": "string"
}
}
},
["code", "table", "scope"]
);
next();
}
@@ -17,7 +17,7 @@ export default function (fastify: FastifyInstance, opts: any, next) {
signatures: {
"type": "array",
"description": "array of signatures required to authorize transaction",
"items": 'Signature#'
"items": {$ref: 'Signature#'}
},
compression: {
"type": "boolean",
+50 -41
View File
@@ -2,45 +2,54 @@ import {FastifyInstance} from "fastify";
import {addApiRoute, chainApiHandler, getRouteName} from "../../../helpers/functions";
export default function (fastify: FastifyInstance, opts: any, next) {
addApiRoute(
fastify,
'POST',
getRouteName(__filename),
chainApiHandler,
{
description: "This method expects a transaction in JSON format and will attempt to apply it to the blockchain.",
summary: "This method expects a transaction in JSON format and will attempt to apply it to the blockchain.",
tags: ['chain'],
body: {
type: ['array', 'object', 'string'],
items: {
type: ['object', 'string'],
additionalProperties: false,
minProperties: 8,
required: [
"expiration",
"ref_block_num",
"ref_block_prefix",
"max_net_usage_words",
"max_cpu_usage_ms",
"delay_sec",
"context_free_actions",
"actions"
],
properties: {
"expiration": 'Expiration#',
"ref_block_num": {"type": "integer"},
"ref_block_prefix": {"type": "integer"},
"max_net_usage_words": 'WholeNumber#',
"max_cpu_usage_ms": 'WholeNumber#',
"delay_sec": {"type": "integer"},
"context_free_actions": {"type": "array", "items": 'ActionItems#'},
"actions": {"type": "array", "items": 'ActionItems#'},
"transaction_extensions": {"type": "array", "items": 'BlockExtensions#'}
},
}
}
}
);
next();
addApiRoute(
fastify,
'POST',
getRouteName(__filename),
chainApiHandler,
{
description: "This method expects a transaction in JSON format and will attempt to apply it to the blockchain.",
summary: "This method expects a transaction in JSON format and will attempt to apply it to the blockchain.",
tags: ['chain'],
body: {
type: ['array', 'object', 'string'],
items: {
type: ['object', 'string'],
additionalProperties: false,
minProperties: 8,
required: [
"expiration",
"ref_block_num",
"ref_block_prefix",
"max_net_usage_words",
"max_cpu_usage_ms",
"delay_sec",
"context_free_actions",
"actions"
],
properties: {
"expiration": {$ref: 'Expiration#'},
"ref_block_num": {"type": "integer"},
"ref_block_prefix": {"type": "integer"},
"max_net_usage_words": {$ref: 'WholeNumber#'},
"max_cpu_usage_ms": {$ref: 'WholeNumber#'},
"delay_sec": {"type": "integer"},
"context_free_actions": {
"type": "array",
"items": {$ref: 'ActionItems#'}
},
"actions": {
"type": "array",
"items": {$ref: 'ActionItems#'}
},
"transaction_extensions": {
"type": "array",
"items": {$ref: 'BlockExtensions#'}
}
},
}
}
}
);
next();
}
@@ -17,7 +17,7 @@ export default function (fastify: FastifyInstance, opts: any, next) {
signatures: {
"type": "array",
"description": "array of signatures required to authorize transaction",
"items": 'Signature#'
"items": {$ref: 'Signature#'}
},
compression: {
"type": "boolean",
@@ -1,7 +1,6 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import {mergeActionMeta, timedQuery} from "../../../helpers/functions";
import * as flatstr from 'flatstr';
import flatstr from 'flatstr';
const terms = ["notified", "act.authorization.actor"];
const extendedActions = new Set(["transfer", "newaccount", "updateauth"]);
@@ -12,7 +11,7 @@ async function getActions(fastify: FastifyInstance, request: FastifyRequest) {
request.body = JSON.parse(request.body);
}
const reqBody = request.body;
const reqBody = request.body as any;
const should_array = [];
for (const entry of terms) {
const tObj = {term: {}};
@@ -191,7 +190,7 @@ async function getActions(fastify: FastifyInstance, request: FastifyRequest) {
}
export function getActionsHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => {
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getActions, fastify, request, route));
}
}
@@ -1,72 +1,72 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import {timedQuery} from "../../../helpers/functions";
import {ApiResponse} from "@elastic/elasticsearch";
async function getControlledAccounts(fastify: FastifyInstance, request: FastifyRequest) {
if (typeof request.body === 'string') {
request.body = JSON.parse(request.body)
}
let controlling_account = request.body.controlling_account;
const results: ApiResponse = await fastify.elastic.search({
index: fastify.manager.chain + '-action-*',
size: 100,
body: {
query: {
bool: {
should: [
{
term: {"@updateauth.auth.accounts.permission.actor": controlling_account}},
{
bool: {
must: [
{term: {"act.account": "eosio"}},
{term: {"act.name": "newaccount"}},
{term: {"act.authorization.actor": controlling_account}}
]
}
}
],
minimum_should_match: 1
}
},
sort: [
{"global_sequence": {"order": "desc"}}
]
}
});
if (typeof request.body === 'string') {
request.body = JSON.parse(request.body)
}
let controlling_account = request.body["controlling_account"];
const results: ApiResponse = await fastify.elastic.search({
index: fastify.manager.chain + '-action-*',
size: 100,
body: {
query: {
bool: {
should: [
{
term: {"@updateauth.auth.accounts.permission.actor": controlling_account}
},
{
bool: {
must: [
{term: {"act.account": "eosio"}},
{term: {"act.name": "newaccount"}},
{term: {"act.authorization.actor": controlling_account}}
]
}
}
],
minimum_should_match: 1
}
},
sort: [
{"global_sequence": {"order": "desc"}}
]
}
});
const response = {
controlled_accounts: []
};
const response = {
controlled_accounts: []
};
const hits = results.body.hits.hits;
const hits = results.body.hits.hits;
if (hits.length > 0) {
response.controlled_accounts = hits.map((v) => {
if (v._source.act.name === 'newaccount') {
if (v._source['@newaccount'].newact) {
return v._source['@newaccount'].newact;
} else if (v._source.act.data.newact) {
return v._source.act.data.newact;
} else {
return null;
}
} else if (v._source.act.name === 'updateauth') {
return v._source.act.data.account;
} else {
return null;
}
});
}
if (response.controlled_accounts.length > 0) {
response.controlled_accounts = [...(new Set(response.controlled_accounts))];
}
return response;
if (hits.length > 0) {
response.controlled_accounts = hits.map((v) => {
if (v._source.act.name === 'newaccount') {
if (v._source['@newaccount'].newact) {
return v._source['@newaccount'].newact;
} else if (v._source.act.data.newact) {
return v._source.act.data.newact;
} else {
return null;
}
} else if (v._source.act.name === 'updateauth') {
return v._source.act.data.account;
} else {
return null;
}
});
}
if (response.controlled_accounts.length > 0) {
response.controlled_accounts = [...(new Set(response.controlled_accounts))];
}
return response;
}
export function getControlledAccountsHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => {
reply.send(await timedQuery(getControlledAccounts, fastify, request, route));
}
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getControlledAccounts, fastify, request, route));
}
}
@@ -1,31 +1,21 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import {mergeActionMeta, timedQuery} from "../../../helpers/functions";
import {createHash} from "crypto";
import * as flatstr from 'flatstr';
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 pResults = await Promise.all([fastify.eosjs.rpc.get_info(), fastify.elastic['search']({
"index": fastify.manager.chain + '-action-*',
"body": {
"query": {
"bool": {
must: [
{term: {"trx_id": request.body.id.toLowerCase()}}
]
}
},
"sort": {
"global_sequence": "asc"
}
}
})]);
const results = pResults[1];
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": request.body.id,
"id": body.id,
"trx": {
"receipt": {
"status": "executed",
@@ -42,11 +32,89 @@ async function getTransaction(fastify: FastifyInstance, request: FastifyRequest)
},
"block_num": 0,
"block_time": "",
"last_irreversible_block": pResults[0].last_irreversible_block_num,
"last_irreversible_block": undefined,
"traces": []
};
const hits = results['body']['hits']['hits'];
let hits;
// 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);
});
}
});
});
// 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;
@@ -98,7 +166,7 @@ async function getTransaction(fastify: FastifyInstance, request: FastifyRequest)
except: null,
inline_traces: [],
producer_block_id: "",
trx_id: request.body.id,
trx_id: body.id,
notified: action.notified
};
let hash = createHash('sha256');
@@ -157,7 +225,7 @@ async function getTransaction(fastify: FastifyInstance, request: FastifyRequest)
except: null,
inline_traces: [],
producer_block_id: "",
trx_id: request.body.id,
trx_id: body.id,
};
traces[action.global_sequence].inline_traces.unshift(trace);
response.traces.push(trace);
@@ -166,7 +234,7 @@ async function getTransaction(fastify: FastifyInstance, request: FastifyRequest)
}
});
} else {
const errmsg = "Transaction " + request.body.id.toLowerCase() + " not found in history and no block hint was given";
const errmsg = "Transaction " + body.id.toLowerCase() + " not found in history and no block hint was given";
return {
code: 500,
message: "Internal Service Error",
@@ -189,7 +257,7 @@ async function getTransaction(fastify: FastifyInstance, request: FastifyRequest)
}
export function getTransactionHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => {
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getTransaction, fastify, request, route));
}
}
@@ -9,7 +9,10 @@ export default function (fastify: FastifyInstance, opts: any, next) {
tags: ['history'],
body: {
type: ['object', 'string'],
properties: {id: {description: 'transaction id', type: 'string'}},
properties: {
id: {description: 'transaction id', type: 'string'},
block_num_hint: {description: 'block number hint', type: 'integer'},
},
required: ["id"]
}
});
+2 -3
View File
@@ -1,5 +1,4 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import {timedQuery} from "../../../helpers/functions";
import * as _ from "lodash";
@@ -19,7 +18,7 @@ async function getBlockTrace(fastify: FastifyInstance, request: FastifyRequest)
request.body = JSON.parse(request.body);
}
const reqBody = request.body;
const reqBody: any = request.body;
const targetBlock = parseInt(reqBody.block_num);
let searchBody;
@@ -121,7 +120,7 @@ async function getBlockTrace(fastify: FastifyInstance, request: FastifyRequest)
}
export function getBlockTraceHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => {
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getBlockTrace, fastify, request, route));
}
}
@@ -0,0 +1,33 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {timedQuery} from "../../../helpers/functions";
async function checkTransaction(fastify: FastifyInstance, request: FastifyRequest) {
const query: any = request.query;
const txId = query.id.toLowerCase();
const data = await fastify.redis.get(txId) as any;
if (data) {
const jsonObj = JSON.parse(data);
const response = {
id: txId,
status: jsonObj.status,
block_num: jsonObj.b,
root_action: jsonObj.a,
signatures: [],
};
if (jsonObj.s && jsonObj.s.length > 0) {
response.signatures = jsonObj.s;
}
return response;
} else {
return {
id: txId,
status: 'unknown'
};
}
}
export function checkTransactionHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(checkTransaction, fastify, request, route));
}
}
@@ -0,0 +1,55 @@
import {FastifyInstance} from "fastify";
import {addApiRoute, extendResponseSchema, getRouteName} from "../../../helpers/functions";
import {checkTransactionHandler} from "./check_transaction";
export default function (fastify: FastifyInstance, opts: any, next) {
const schema = {
description: 'check if a transaction was included in a block',
summary: 'check if a transaction was included in a block',
tags: ['history'],
querystring: {
type: 'object',
properties: {
"id": {
description: 'transaction id',
type: 'string',
minLength: 64,
maxLength: 64
}
},
required: ["id"]
},
response: extendResponseSchema({
"id": {type: "string"},
"status": {type: "string"},
"block_num": {type: "number"},
"root_action": {
type: "object",
properties: {
"account": {type: "string"},
"name": {type: "string"},
"authorization": {
type: "array",
items: {
type: 'object',
properties: {
"actor": {type: "string"},
"permission": {type: "string"}
}
}
},
"data": {type: "string"}
}
},
"signatures": {type: "array", items: {type: 'string'}}
})
};
addApiRoute(
fastify,
'GET',
getRouteName(__filename),
checkTransactionHandler,
schema
);
next();
}
@@ -0,0 +1,12 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {timedQuery} from "../../../helpers/functions";
async function exportActions(fastify: FastifyInstance, request: FastifyRequest) {
}
export function exportActionsHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(exportActions, fastify, request, route));
}
}
@@ -0,0 +1,19 @@
import {FastifyInstance, FastifySchema} from "fastify";
import {addApiRoute, getRouteName} from "../../../helpers/functions";
import {getCreatorHandler} from "../get_creator/get_creator";
export default function (fastify: FastifyInstance, opts: any, next) {
const schema: FastifySchema = {
description: 'request large action data export',
summary: 'request large action data export',
tags: ['history']
};
addApiRoute(
fastify,
'GET',
getRouteName(__filename),
getCreatorHandler,
schema
);
next();
}
@@ -1,5 +1,4 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import {timedQuery} from "../../../helpers/functions";
async function getAbiSnapshot(fastify: FastifyInstance, request: FastifyRequest) {
@@ -8,9 +7,11 @@ async function getAbiSnapshot(fastify: FastifyInstance, request: FastifyRequest)
block_num: null
};
const code = request.query.contract;
const block = request.query.block;
const should_fetch = request.query.fetch;
const query: any = request.query;
const code = query.contract;
const block = query.block;
const should_fetch = query.fetch;
const mustArray = [];
@@ -21,7 +22,7 @@ async function getAbiSnapshot(fastify: FastifyInstance, request: FastifyRequest)
}
const results = await fastify.elastic.search({
index: fastify.manager.chain + '-abi',
index: fastify.manager.chain + '-abi-*',
size: 1,
body: {
query: {bool: {must: mustArray}},
@@ -44,7 +45,7 @@ async function getAbiSnapshot(fastify: FastifyInstance, request: FastifyRequest)
}
export function getAbiSnapshotHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => {
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getAbiSnapshot, fastify, request, route));
}
}
+204 -183
View File
@@ -1,215 +1,236 @@
import {extendedActions, primaryTerms, terms} from "./definitions";
import {primaryTerms, terms} from "./definitions";
export function addSortedBy(query, queryBody, sort_direction) {
if (query['sortedBy']) {
const opts = query['sortedBy'].split(":");
const sortedByObj = {};
sortedByObj[opts[0]] = opts[1];
queryBody['sort'] = sortedByObj;
} else {
queryBody['sort'] = {
"global_sequence": sort_direction
};
}
if (query['sortedBy']) {
const opts = query['sortedBy'].split(":");
const sortedByObj = {};
sortedByObj[opts[0]] = opts[1];
queryBody['sort'] = sortedByObj;
} else {
queryBody['sort'] = {
"global_sequence": sort_direction
};
}
}
export function processMultiVars(queryStruct, parts, field) {
const must = [];
const mustNot = [];
const must = [];
const mustNot = [];
parts.forEach(part => {
if (part.startsWith("!")) {
mustNot.push(part.replace("!", ""));
} else {
must.push(part);
}
});
parts.forEach(part => {
if (part.startsWith("!")) {
mustNot.push(part.replace("!", ""));
} else {
must.push(part);
}
});
if (must.length > 1) {
queryStruct.bool.must.push({
bool: {
should: must.map(elem => {
const _q = {};
_q[field] = elem;
return {term: _q}
})
}
});
} else if (must.length === 1) {
const mustQuery = {};
mustQuery[field] = must[0];
queryStruct.bool.must.push({term: mustQuery});
}
if (must.length > 1) {
queryStruct.bool.must.push({
bool: {
should: must.map(elem => {
const _q = {};
_q[field] = elem;
return {term: _q}
})
}
});
} else if (must.length === 1) {
const mustQuery = {};
mustQuery[field] = must[0];
queryStruct.bool.must.push({term: mustQuery});
}
if (mustNot.length > 1) {
queryStruct.bool.must_not.push({
bool: {
should: mustNot.map(elem => {
const _q = {};
_q[field] = elem;
return {term: _q}
})
}
});
} else if (mustNot.length === 1) {
const mustNotQuery = {};
mustNotQuery[field] = mustNot[0].replace("!", "");
queryStruct.bool.must_not.push({term: mustNotQuery});
}
if (mustNot.length > 1) {
queryStruct.bool.must_not.push({
bool: {
should: mustNot.map(elem => {
const _q = {};
_q[field] = elem;
return {term: _q}
})
}
});
} else if (mustNot.length === 1) {
const mustNotQuery = {};
mustNotQuery[field] = mustNot[0].replace("!", "");
queryStruct.bool.must_not.push({term: mustNotQuery});
}
}
function addRangeQuery(queryStruct, prop, pkey, query) {
const _termQuery = {};
const parts = query[prop].split("-");
_termQuery[pkey] = {
"gte": parts[0],
"lte": parts[1]
};
queryStruct.bool.must.push({range: _termQuery});
const _termQuery = {};
const parts = query[prop].split("-");
_termQuery[pkey] = {
"gte": parts[0],
"lte": parts[1]
};
queryStruct.bool.must.push({range: _termQuery});
}
export function applyTimeFilter(query, queryStruct) {
if (query['after'] || query['before']) {
let _lte = "now";
let _gte = "0";
if (query['before']) {
_lte = query['before'];
if (!_lte.endsWith("Z")) {
_lte += "Z";
}
}
if (query['after']) {
_gte = query['after'];
if (!_gte.endsWith("Z")) {
_gte += "Z";
}
}
if (!queryStruct.bool['filter']) {
queryStruct.bool['filter'] = [];
}
queryStruct.bool['filter'].push({
range: {
"@timestamp": {
"gte": _gte,
"lte": _lte
}
}
});
}
if (query['after'] || query['before']) {
let _lte = "now";
let _gte = "0";
if (query['before']) {
try {
_lte = new Date(query['before']).toISOString();
} catch (e) {
throw new Error(e.message + ' [before]');
}
}
if (query['after']) {
try {
_gte = new Date(query['after']).toISOString();
} catch (e) {
throw new Error(e.message + ' [after]');
}
}
if (!queryStruct.bool['filter']) {
queryStruct.bool['filter'] = [];
}
queryStruct.bool['filter'].push({
range: {
"@timestamp": {
"gte": _gte,
"lte": _lte
}
}
});
}
}
export function applyGenericFilters(query, queryStruct) {
for (const prop in query) {
if (Object.prototype.hasOwnProperty.call(query, prop)) {
const pair = prop.split(".");
if (pair.length > 1 || primaryTerms.includes(pair[0])) {
let pkey;
if (pair.length > 1) {
pkey = extendedActions.has(pair[0]) ? "@" + prop : prop;
} else {
pkey = prop;
}
if (query[prop].indexOf("-") !== -1) {
addRangeQuery(queryStruct, prop, pkey, query);
} else {
const _termQuery = {};
const parts = query[prop].split(",");
if (parts.length > 1) {
processMultiVars(queryStruct, parts, prop);
} else if (parts.length === 1) {
const andParts = parts[0].split(" ");
if (andParts.length > 1) {
andParts.forEach(value => {
const _q = {};
console.log(value);
_q[pkey] = value;
queryStruct.bool.must.push({term: _q});
});
} else {
if (parts[0].startsWith("!")) {
_termQuery[pkey] = parts[0].replace("!", "");
queryStruct.bool.must_not.push({term: _termQuery});
} else {
_termQuery[pkey] = parts[0];
queryStruct.bool.must.push({term: _termQuery});
}
}
}
}
}
}
}
export function applyGenericFilters(query, queryStruct, allowedExtraParams: Set<string>) {
for (const prop in query) {
if (Object.prototype.hasOwnProperty.call(query, prop)) {
const pair = prop.split(".");
if (pair.length > 1 || primaryTerms.includes(pair[0])) {
let pkey;
if (pair.length > 1 && allowedExtraParams) {
pkey = allowedExtraParams.has(pair[0]) ? "@" + prop : prop;
} else {
pkey = prop;
}
if (query[prop].indexOf("-") !== -1) {
addRangeQuery(queryStruct, prop, pkey, query);
} else {
const _qObj = {};
const parts = query[prop].split(",");
if (parts.length > 1) {
processMultiVars(queryStruct, parts, prop);
} else if (parts.length === 1) {
// @transfer.memo special case
if (pkey === '@transfer.memo') {
_qObj[pkey] = {
query: parts[0]
};
if (query.match_fuzziness) {
_qObj[pkey].fuzziness = query.match_fuzziness;
}
if (query.match_operator) {
_qObj[pkey].operator = query.match_operator;
}
queryStruct.bool.must.push({
match: _qObj
});
} else {
const andParts = parts[0].split(" ");
if (andParts.length > 1) {
andParts.forEach(value => {
const _q = {};
_q[pkey] = value;
queryStruct.bool.must.push({term: _q});
});
} else {
if (parts[0].startsWith("!")) {
_qObj[pkey] = parts[0].replace("!", "");
queryStruct.bool.must_not.push({term: _qObj});
} else {
_qObj[pkey] = parts[0];
queryStruct.bool.must.push({term: _qObj});
}
}
}
}
}
}
}
}
}
export function makeShouldArray(query) {
const should_array = [];
for (const entry of terms) {
const tObj = {term: {}};
tObj.term[entry] = query.account;
should_array.push(tObj);
}
return should_array;
const should_array = [];
for (const entry of terms) {
const tObj = {term: {}};
tObj.term[entry] = query.account;
should_array.push(tObj);
}
return should_array;
}
export function applyCodeActionFilters(query, queryStruct) {
let filterObj = [];
if (query.filter) {
for (const filter of query.filter.split(',')) {
if (filter !== '*:*') {
const _arr = [];
const parts = filter.split(':');
if (parts.length === 2) {
const [code, method] = parts;
if (code && code !== "*") {
_arr.push({'term': {'act.account': code}});
}
if (method && method !== "*") {
_arr.push({'term': {'act.name': method}});
}
}
if (_arr.length > 0) {
filterObj.push({bool: {must: _arr}});
}
}
}
if (filterObj.length > 0) {
queryStruct.bool['should'] = filterObj;
queryStruct.bool['minimum_should_match'] = 1;
}
}
let filterObj = [];
if (query.filter) {
for (const filter of query.filter.split(',')) {
if (filter !== '*:*') {
const _arr = [];
const parts = filter.split(':');
if (parts.length === 2) {
const [code, method] = parts;
if (code && code !== "*") {
_arr.push({'term': {'act.account': code}});
}
if (method && method !== "*") {
_arr.push({'term': {'act.name': method}});
}
}
if (_arr.length > 0) {
filterObj.push({bool: {must: _arr}});
}
}
}
if (filterObj.length > 0) {
queryStruct.bool['should'] = filterObj;
queryStruct.bool['minimum_should_match'] = 1;
}
}
}
export function getSkipLimit(query, max?: number) {
let skip, limit;
skip = parseInt(query.skip, 10);
if (skip < 0) {
throw new Error('invalid skip parameter');
}
limit = parseInt(query.limit, 10);
if (limit < 1) {
throw new Error('invalid limit parameter');
} else if (limit > max) {
throw new Error(`limit too big, maximum: ${max}`);
}
return {skip, limit};
let skip, limit;
skip = parseInt(query.skip, 10);
if (skip < 0) {
throw new Error('invalid skip parameter');
}
limit = parseInt(query.limit, 10);
if (limit < 1) {
throw new Error('invalid limit parameter');
} else if (limit > max) {
throw new Error(`limit too big, maximum: ${max}`);
}
return {skip, limit};
}
export function getSortDir(query) {
let sort_direction = 'desc';
if (query.sort) {
if (query.sort === 'asc' || query.sort === '1') {
sort_direction = 'asc';
} else if (query.sort === 'desc' || query.sort === '-1') {
sort_direction = 'desc'
} else {
throw new Error('invalid sort direction');
}
}
return sort_direction;
let sort_direction = 'desc';
if (query.sort) {
if (query.sort === 'asc' || query.sort === '1') {
sort_direction = 'asc';
} else if (query.sort === 'desc' || query.sort === '-1') {
sort_direction = 'desc'
} else {
throw new Error('invalid sort direction');
}
}
return sort_direction;
}
export function applyAccountFilters(query, queryStruct) {
if (query.account) {
queryStruct.bool.must.push({"bool": {should: makeShouldArray(query)}});
}
if (query.account) {
queryStruct.bool.must.push({"bool": {should: makeShouldArray(query)}});
}
}
@@ -1,119 +1,118 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import {getTrackTotalHits, mergeActionMeta, timedQuery} from "../../../helpers/functions";
import {
addSortedBy,
applyAccountFilters,
applyCodeActionFilters,
applyGenericFilters,
applyTimeFilter,
getSkipLimit,
getSortDir
addSortedBy,
applyAccountFilters,
applyCodeActionFilters,
applyGenericFilters,
applyTimeFilter,
getSkipLimit,
getSortDir
} from "./functions";
async function getActions(fastify: FastifyInstance, request: FastifyRequest) {
const query = request.query;
const maxActions = fastify.manager.config.api.limits.get_actions;
const queryStruct = {
"bool": {
must: [],
must_not: [],
boost: 1.0
}
};
const query: any = request.query;
const maxActions = fastify.manager.config.api.limits.get_actions;
const queryStruct = {
"bool": {
must: [],
must_not: [],
boost: 1.0
}
};
const {skip, limit} = getSkipLimit(query, maxActions);
const sort_direction = getSortDir(query);
applyAccountFilters(query, queryStruct);
applyGenericFilters(query, queryStruct);
applyTimeFilter(query, queryStruct);
applyCodeActionFilters(query, queryStruct);
// allow precise counting of total hits
const trackTotalHits = getTrackTotalHits(query);
const {skip, limit} = getSkipLimit(query, maxActions);
const sort_direction = getSortDir(query);
applyAccountFilters(query, queryStruct);
applyGenericFilters(query, queryStruct, fastify.allowedActionQueryParamSet);
applyTimeFilter(query, queryStruct);
applyCodeActionFilters(query, queryStruct);
// allow precise counting of total hits
const trackTotalHits = getTrackTotalHits(query);
// Prepare query body
const query_body = {
"track_total_hits": trackTotalHits,
"query": queryStruct
};
// Prepare query body
const query_body = {
"track_total_hits": trackTotalHits,
"query": queryStruct
};
// Include sorting
addSortedBy(query, query_body, sort_direction);
// Include sorting
addSortedBy(query, query_body, sort_direction);
// Perform search
// Perform search
let indexPattern = fastify.manager.chain + '-action-*';
if (query.hot_only) {
indexPattern = fastify.manager.chain + '-action';
}
let indexPattern = fastify.manager.chain + '-action-*';
if (query.hot_only) {
indexPattern = fastify.manager.chain + '-action';
}
const esResults = await fastify.elastic.search({
"index": indexPattern,
"from": skip || 0,
"size": (limit > maxActions ? maxActions : limit) || 10,
"body": query_body
});
const esResults = await fastify.elastic.search({
"index": indexPattern,
"from": skip || 0,
"size": (limit > maxActions ? maxActions : limit) || 10,
"body": query_body
});
const results = esResults['body']['hits'];
const response: any = {
cached: false,
lib: 0,
total: results['total']
};
const results = esResults['body']['hits'];
const response: any = {
cached: false,
lib: 0,
total: results['total']
};
if (query.hot_only) {
response.hot_only = true;
}
if (query.hot_only) {
response.hot_only = true;
}
if (query.checkLib) {
response.lib = (await fastify.eosjs.rpc.get_info()).last_irreversible_block_num;
}
if (query.checkLib) {
response.lib = (await fastify.eosjs.rpc.get_info()).last_irreversible_block_num;
}
if (query.simple) {
response['simple_actions'] = [];
} else {
response['actions'] = [];
}
if (query.simple) {
response['simple_actions'] = [];
} else {
response['actions'] = [];
}
if (results['hits'].length > 0) {
const actions = results['hits'];
for (let action of actions) {
action = action._source;
mergeActionMeta(action);
if (results['hits'].length > 0) {
const actions = results['hits'];
for (let action of actions) {
action = action._source;
mergeActionMeta(action);
if (query.noBinary === true) {
for (const key in action['act']['data']) {
if (action['act']['data'].hasOwnProperty(key)) {
if (typeof action['act']['data'][key] === 'string' && action['act']['data'][key].length > 256) {
action['act']['data'][key] = action['act']['data'][key].slice(0, 32) + "...";
}
}
}
}
if (query.noBinary === true) {
for (const key in action['act']['data']) {
if (action['act']['data'].hasOwnProperty(key)) {
if (typeof action['act']['data'][key] === 'string' && action['act']['data'][key].length > 256) {
action['act']['data'][key] = action['act']['data'][key].slice(0, 32) + "...";
}
}
}
}
if (query.simple) {
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(','),
contract: action['act']['account'],
action: action['act']['name'],
data: action['act']['data']
});
if (query.simple) {
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(','),
contract: action['act']['account'],
action: action['act']['name'],
data: action['act']['data']
});
} else {
response.actions.push(action);
}
}
}
return response;
} else {
response.actions.push(action);
}
}
}
return response;
}
export function getActionsHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => {
reply.send(await timedQuery(getActions, fastify, request, route));
}
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getActions, fastify, request, route));
}
}
+124 -123
View File
@@ -1,131 +1,132 @@
import {FastifyInstance, RouteSchema} from "fastify";
import {FastifyInstance, FastifySchema} from "fastify";
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"},
"trx_id": {type: "string"},
"act": {
type: 'object',
properties: {
"account": {type: "string"},
"name": {type: "string"}
},
additionalProperties: true
},
"notified": {
type: "array", items: {type: "string"}
},
"cpu_usage_us": {type: "number"},
"net_usage_words": {type: "number"},
"account_ram_deltas": {
type: "array",
items: {
type: "object",
properties: {
"account": {type: "string"},
"delta": {type: "number"}
},
additionalProperties: true
}
},
"global_sequence": {type: "number"},
"receiver": {type: 'string'},
"producer": {type: "string"},
"parent": {type: "number"},
"action_ordinal": {type: 'number'},
"creator_action_ordinal": {type: 'number'}
"@timestamp": {type: "string"},
"timestamp": {type: "string"},
"block_num": {type: "number"},
"trx_id": {type: "string"},
"act": {
type: 'object',
properties: {
"account": {type: "string"},
"name": {type: "string"}
},
additionalProperties: true
},
"notified": {
type: "array", items: {type: "string"}
},
"cpu_usage_us": {type: "number"},
"net_usage_words": {type: "number"},
"account_ram_deltas": {
type: "array",
items: {
type: "object",
properties: {
"account": {type: "string"},
"delta": {type: "number"}
},
additionalProperties: true
}
},
"global_sequence": {type: "number"},
"receiver": {type: 'string'},
"producer": {type: "string"},
"parent": {type: "number"},
"action_ordinal": {type: 'number'},
"creator_action_ordinal": {type: 'number'},
"signatures": {type: "array", items: {type: 'string'}}
};
export default function (fastify: FastifyInstance, opts: any, next) {
const schema: RouteSchema = {
description: 'get actions based on notified account. this endpoint also accepts generic filters based on indexed fields' +
' (e.g. act.authorization.actor=eosio or act.name=delegatebw), if included they will be combined with a AND operator',
summary: 'get root actions',
tags: ['history'],
querystring: extendQueryStringSchema({
"account": {
description: 'notified account',
type: 'string',
minLength: 1,
maxLength: 12
},
"track": {
description: 'total results to track (count) [number or true]',
type: 'string'
},
"filter": {
description: 'code:name filter',
type: 'string',
minLength: 3
},
"sort": {
description: 'sort direction',
enum: ['desc', 'asc', '1', '-1'],
type: 'string'
},
"after": {
description: 'filter after specified date (ISO8601)',
type: 'string'
},
"before": {
description: 'filter before specified date (ISO8601)',
type: 'string'
},
"simple": {
description: 'simplified output mode',
type: 'boolean'
},
"hot_only": {
description: 'search only the latest hot index',
type: 'boolean'
},
"noBinary": {
description: "exclude large binary data",
type: 'boolean'
},
"checkLib": {
description: "perform reversibility check",
type: 'boolean'
},
}),
response: extendResponseSchema({
"simple_actions": {
type: "array",
items: {
type: "object",
properties: {
"block": {type: "number"},
"timestamp": {type: "string"},
"irreversible": {type: "boolean"},
"contract": {type: "string"},
"action": {type: "string"},
"actors": {type: "string"},
"notified": {type: "string"},
"transaction_id": {type: "string"},
"data": {
additionalProperties: true
}
}
}
},
"actions": {
type: "array",
items: {
type: 'object',
properties: getActionResponseSchema
}
}
})
};
addApiRoute(
fastify,
'GET',
getRouteName(__filename),
getActionsHandler,
schema
);
next();
const schema: FastifySchema = {
description: 'get actions based on notified account. this endpoint also accepts generic filters based on indexed fields' +
' (e.g. act.authorization.actor=eosio or act.name=delegatebw), if included they will be combined with a AND operator',
summary: 'get root actions',
tags: ['history'],
querystring: extendQueryStringSchema({
"account": {
description: 'notified account',
type: 'string',
minLength: 1,
maxLength: 12
},
"track": {
description: 'total results to track (count) [number or true]',
type: 'string'
},
"filter": {
description: 'code:name filter',
type: 'string',
minLength: 3
},
"sort": {
description: 'sort direction',
enum: ['desc', 'asc', '1', '-1'],
type: 'string'
},
"after": {
description: 'filter after specified date (ISO8601)',
type: 'string'
},
"before": {
description: 'filter before specified date (ISO8601)',
type: 'string'
},
"simple": {
description: 'simplified output mode',
type: 'boolean'
},
"hot_only": {
description: 'search only the latest hot index',
type: 'boolean'
},
"noBinary": {
description: "exclude large binary data",
type: 'boolean'
},
"checkLib": {
description: "perform reversibility check",
type: 'boolean'
},
}),
response: extendResponseSchema({
"simple_actions": {
type: "array",
items: {
type: "object",
properties: {
"block": {type: "number"},
"timestamp": {type: "string"},
"irreversible": {type: "boolean"},
"contract": {type: "string"},
"action": {type: "string"},
"actors": {type: "string"},
"notified": {type: "string"},
"transaction_id": {type: "string"},
"data": {
additionalProperties: true
}
}
}
},
"actions": {
type: "array",
items: {
type: 'object',
properties: getActionResponseSchema
}
}
})
};
addApiRoute(
fastify,
'GET',
getRouteName(__filename),
getActionsHandler,
schema
);
next();
}
@@ -1,58 +1,58 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import {timedQuery} from "../../../helpers/functions";
import {getSkipLimit} from "../get_actions/functions";
async function getCreatedAccounts(fastify: FastifyInstance, request: FastifyRequest) {
const {skip, limit} = getSkipLimit(request.query);
const maxActions = fastify.manager.config.api.limits.get_created_accounts;
const results = await fastify.elastic.search({
"index": fastify.manager.chain + '-action-*',
"from": skip || 0,
"size": (limit > maxActions ? maxActions : limit) || 100,
"body": {
"query": {
"bool": {
must: [
{term: {"act.authorization.actor": request.query.account.toLowerCase()}},
{term: {"act.name": "newaccount"}},
{term: {"act.account": "eosio"}}
]
}
},
sort: {
"global_sequence": "desc"
}
}
});
const query: any = request.query;
const {skip, limit} = getSkipLimit(query);
const maxActions = fastify.manager.config.api.limits.get_created_accounts;
const results = await fastify.elastic.search({
"index": fastify.manager.chain + '-action-*',
"from": skip || 0,
"size": (limit > maxActions ? maxActions : limit) || 100,
"body": {
"query": {
"bool": {
must: [
{term: {"act.authorization.actor": query.account.toLowerCase()}},
{term: {"act.name": "newaccount"}},
{term: {"act.account": "eosio"}}
]
}
},
sort: {
"global_sequence": "desc"
}
}
});
const response = {accounts: []};
const response = {accounts: []};
if (results['body']['hits']['hits'].length > 0) {
const actions = results['body']['hits']['hits'];
for (let action of actions) {
action = action._source;
const _tmp = {};
if (action['act']['data']['newact']) {
_tmp['name'] = action['act']['data']['newact'];
} else if (action['@newaccount']['newact']) {
_tmp['name'] = action['@newaccount']['newact'];
} else {
console.log(action);
}
_tmp['trx_id'] = action['trx_id'];
_tmp['timestamp'] = action['@timestamp'];
response.accounts.push(_tmp);
}
}
if (results['body']['hits']['hits'].length > 0) {
const actions = results['body']['hits']['hits'];
for (let action of actions) {
action = action._source;
const _tmp = {};
if (action['act']['data']['newact']) {
_tmp['name'] = action['act']['data']['newact'];
} else if (action['@newaccount']['newact']) {
_tmp['name'] = action['@newaccount']['newact'];
} else {
console.log(action);
}
_tmp['trx_id'] = action['trx_id'];
_tmp['timestamp'] = action['@timestamp'];
response.accounts.push(_tmp);
}
}
return response;
return response;
}
export function getCreatedAccountsHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => {
reply.send(await timedQuery(getCreatedAccounts, fastify, request, route));
}
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getCreatedAccounts, fastify, request, route));
}
}
@@ -1,44 +1,44 @@
import {FastifyInstance, RouteSchema} from "fastify";
import {FastifyInstance, FastifySchema} from "fastify";
import {getCreatedAccountsHandler} from "./get_created_accounts";
import {addApiRoute, extendQueryStringSchema, extendResponseSchema, getRouteName} from "../../../helpers/functions";
export default function (fastify: FastifyInstance, opts: any, next) {
const schema: RouteSchema = {
description: 'get all accounts created by one creator',
summary: 'get created accounts',
tags: ['accounts'],
querystring: extendQueryStringSchema({
"account": {
description: 'creator account',
type: 'string',
minLength: 1,
maxLength: 12
}
}, ["account"]),
response: extendResponseSchema({
"query_time": {
type: "number"
},
"total": {
type: "object",
properties: {
"value": {type: "number"},
"relation": {type: "string"}
}
},
"accounts": {
type: "array",
items: {
type: 'object',
properties: {
'name': {type: 'string'},
'timestamp': {type: 'string'},
'trx_id': {type: 'string'}
}
}
}
})
};
addApiRoute(fastify, 'GET', getRouteName(__filename), getCreatedAccountsHandler, schema);
next();
const schema: FastifySchema = {
description: 'get all accounts created by one creator',
summary: 'get created accounts',
tags: ['accounts'],
querystring: extendQueryStringSchema({
"account": {
description: 'creator account',
type: 'string',
minLength: 1,
maxLength: 12
}
}, ["account"]),
response: extendResponseSchema({
"query_time": {
type: "number"
},
"total": {
type: "object",
properties: {
"value": {type: "number"},
"relation": {type: "string"}
}
},
"accounts": {
type: "array",
items: {
type: 'object',
properties: {
'name': {type: 'string'},
'timestamp': {type: 'string'},
'trx_id': {type: 'string'}
}
}
}
})
};
addApiRoute(fastify, 'GET', getRouteName(__filename), getCreatedAccountsHandler, schema);
next();
}
@@ -1,18 +1,19 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import {timedQuery} from "../../../helpers/functions";
async function getCreator(fastify: FastifyInstance, request: FastifyRequest) {
const query: any = request.query;
const response = {
account: request.query.account,
account: query.account,
creator: '',
timestamp: '',
block_num: 0,
trx_id: '',
};
if (request.query.account === fastify.manager.config.settings.eosio_alias) {
if (query.account === fastify.manager.config.settings.eosio_alias) {
const genesisBlock = await fastify.eosjs.rpc.get_block(1);
if (genesisBlock) {
response.creator = '__self__';
@@ -31,7 +32,7 @@ async function getCreator(fastify: FastifyInstance, request: FastifyRequest) {
size: 1,
query: {
bool: {
must: [{term: {"@newaccount.newact": request.query.account}}]
must: [{term: {"@newaccount.newact": query.account}}]
}
}
}
@@ -47,7 +48,7 @@ async function getCreator(fastify: FastifyInstance, request: FastifyRequest) {
} else {
let accountInfo;
try {
accountInfo = await fastify.eosjs.rpc.get_account(request.query.account);
accountInfo = await fastify.eosjs.rpc.get_account(query.account);
} catch (e) {
throw new Error("account not found");
}
@@ -73,7 +74,7 @@ async function getCreator(fastify: FastifyInstance, request: FastifyRequest) {
const actions = transaction.trx.transaction.actions;
for (const act of actions) {
if (act.name === 'newaccount') {
if (act.data.name === request.query.account) {
if (act.data.name === query.account) {
response.creator = act.data.creator;
response.trx_id = transaction.id;
return response;
@@ -93,7 +94,7 @@ async function getCreator(fastify: FastifyInstance, request: FastifyRequest) {
}
export function getCreatorHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => {
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getCreator, fastify, request, route));
}
}
+40 -40
View File
@@ -1,45 +1,45 @@
import {FastifyInstance, RouteSchema} from "fastify";
import {FastifyInstance, FastifySchema} from "fastify";
import {getCreatorHandler} from "./get_creator";
import {addApiRoute, extendResponseSchema, getRouteName} from "../../../helpers/functions";
export default function (fastify: FastifyInstance, opts: any, next) {
const schema: RouteSchema = {
description: 'get account creator',
summary: 'get account creator',
tags: ['accounts'],
querystring: {
type: 'object',
properties: {
"account": {
description: 'created account',
type: 'string',
minLength: 1,
maxLength: 12
}
},
required: ["account"]
},
response: extendResponseSchema({
"account": {
type: "string"
},
"creator": {
type: "string"
},
"timestamp": {
type: "string"
},
"block_num": {
type: "integer"
},
"trx_id": {
type: "string"
},
"indirect_creator": {
type: "string"
}
})
};
addApiRoute(fastify, 'GET', getRouteName(__filename), getCreatorHandler, schema);
next();
const schema: FastifySchema = {
description: 'get account creator',
summary: 'get account creator',
tags: ['accounts'],
querystring: {
type: 'object',
properties: {
"account": {
description: 'created account',
type: 'string',
minLength: 1,
maxLength: 12
}
},
required: ["account"]
},
response: extendResponseSchema({
"account": {
type: "string"
},
"creator": {
type: "string"
},
"timestamp": {
type: "string"
},
"block_num": {
type: "integer"
},
"trx_id": {
type: "string"
},
"indirect_creator": {
type: "string"
}
})
};
addApiRoute(fastify, 'GET', getRouteName(__filename), getCreatorHandler, schema);
next();
}
@@ -1,5 +1,4 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import {mergeDeltaMeta, timedQuery} from "../../../helpers/functions";
import {applyTimeFilter} from "../get_actions/functions";
@@ -7,9 +6,10 @@ async function getDeltas(fastify: FastifyInstance, request: FastifyRequest) {
let skip, limit;
let sort_direction = 'desc';
const mustArray = [];
for (const param in request.query) {
if (Object.prototype.hasOwnProperty.call(request.query, param)) {
const value = request.query[param];
const query: any = request.query;
for (const param in query) {
if (Object.prototype.hasOwnProperty.call(query, param)) {
const value = query[param];
switch (param) {
case 'limit': {
limit = parseInt(value, 10);
@@ -42,7 +42,7 @@ async function getDeltas(fastify: FastifyInstance, request: FastifyRequest) {
break;
}
default: {
const values = request.query[param].split(",");
const values = query[param].split(",");
if (values.length > 1) {
const terms = {};
terms[param] = values;
@@ -61,7 +61,7 @@ async function getDeltas(fastify: FastifyInstance, request: FastifyRequest) {
const maxDeltas = fastify.manager.config.api.limits.get_deltas ?? 1000;
const queryStruct = {bool: {must: mustArray}};
applyTimeFilter(request.query, queryStruct);
applyTimeFilter(query, queryStruct);
const results = await fastify.elastic.search({
"index": fastify.manager.chain + '-delta-*',
@@ -85,7 +85,7 @@ async function getDeltas(fastify: FastifyInstance, request: FastifyRequest) {
}
export function getDeltasHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => {
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getDeltas, fastify, request, route));
}
}
+8 -4
View File
@@ -1,9 +1,9 @@
import {FastifyInstance, RouteSchema} from "fastify";
import {FastifyInstance, FastifySchema} from "fastify";
import {getDeltasHandler} from "./get_deltas";
import {addApiRoute, extendQueryStringSchema, extendResponseSchema, getRouteName} from "../../../helpers/functions";
export default function (fastify: FastifyInstance, opts: any, next) {
const schema: RouteSchema = {
const schema: FastifySchema = {
description: 'get state deltas',
summary: 'get state deltas',
tags: ['history'],
@@ -31,7 +31,11 @@ export default function (fastify: FastifyInstance, opts: any, next) {
"before": {
description: 'filter before specified date (ISO8601)',
type: 'string'
}
},
"present": {
description: 'delta present flag',
type: 'number'
},
}),
response: extendResponseSchema({
"deltas": {
@@ -40,12 +44,12 @@ export default function (fastify: FastifyInstance, opts: any, next) {
type: 'object',
properties: {
"timestamp": {type: 'string'},
"present": {type: 'number'},
"code": {type: 'string'},
"scope": {type: 'string'},
"table": {type: 'string'},
"primary_key": {type: 'string'},
"payer": {type: 'string'},
"present": {type: 'boolean'},
"block_num": {type: 'number'},
"block_id": {type: 'string'},
"data": {
@@ -1,71 +1,73 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import {timedQuery} from "../../../helpers/functions";
import {createHash} from "crypto";
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 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 (request.query.version) {
searchParams.body.query.bool.must.push({"term": {"new_producers.version": {"value": request.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: []
};
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;
}
export function getScheduleHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => {
reply.send(await timedQuery(getSchedule, fastify, request, route));
}
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getSchedule, fastify, request, route));
}
}
@@ -0,0 +1,88 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {mergeDeltaMeta, timedQuery} from "../../../helpers/functions";
async function getTableState(fastify: FastifyInstance, request: FastifyRequest) {
const query: any = request.query;
const response = {
...query,
results: [],
next_key: null
};
const mustArray: any[] = [
{"term": {"code": query.code}},
{"term": {"table": query.table}}
];
if (query.block_num) {
mustArray.push({"range": {"block_num": {"lte": query.block_num}}});
}
let after_key = "";
if (query.after_key) {
after_key = query.after_key;
}
const results = await fastify.elastic.search({
index: fastify.manager.chain + '-delta-*',
body: {
"query": {
"bool": {
"must": mustArray
}
},
"size": 0,
"sort": [
{"block_num": "desc"},
{"primary_key": "desc"}
],
"aggs": {
"scope_buckets": {
"composite": {
"size": 25,
"after": {
"scope_pk": after_key
},
"sources": [
{
"scope_pk": {
"terms": {
"script": {
"source": "return doc['scope'].value + '-' + doc['primary_key'].value",
"lang": "painless"
}
}
}
}
]
},
"aggs": {
"last_doc": {
"top_hits": {
"size": 1,
"sort": [{"block_num": "desc"}],
"_source": {
"excludes": ["code"]
}
}
}
}
}
}
}
});
const scope_buckets = results.body.aggregations.scope_buckets;
if (scope_buckets && scope_buckets.buckets.length > 0) {
if (scope_buckets.after_key) {
response.next_key = scope_buckets.after_key.scope_pk;
}
response.results = scope_buckets.buckets.map(bucket => {
const row = mergeDeltaMeta(bucket.last_doc.hits.hits[0]._source);
delete row.table;
return row;
});
}
return response;
}
export function getTableStateHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getTableState, fastify, request, route));
}
}
@@ -0,0 +1,69 @@
import {FastifyInstance} from "fastify";
import {addApiRoute, extendResponseSchema, getRouteName} from "../../../helpers/functions";
import {getTableStateHandler} from "./get_table_state";
export default function (fastify: FastifyInstance, opts: any, next) {
const schema = {
description: 'get table state at a specific block height',
summary: 'get table state at a specific block height',
tags: ['history'],
querystring: {
type: 'object',
required: ["code","table"],
properties: {
"code": {
description: 'search by contract',
type: 'string'
},
"table": {
description: 'search by key',
type: 'string'
},
"block_num": {
description: 'target block',
type: 'integer',
minimum: 1
},
"after_key": {
description: 'last key for pagination',
type: 'string'
}
}
},
response: extendResponseSchema({
"code": {type: 'string'},
"table": {type: 'string'},
"block_num": {type: 'number'},
"after_key": {type: 'string'},
"next_key": {type: 'string'},
"results": {
type: "array",
items: {
type: 'object',
properties: {
"scope": {type: 'string'},
"primary_key": {type: 'string'},
"payer": {type: 'string'},
"timestamp": {type: 'string'},
"block_num": {type: 'number'},
"block_id": {type: 'string'},
"present": {type: 'number'},
"data": {
type: 'object',
additionalProperties: true
}
},
additionalProperties: true
}
}
})
};
addApiRoute(
fastify,
'GET',
getRouteName(__filename),
getTableStateHandler,
schema
);
next();
}
@@ -1,90 +1,128 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import {mergeActionMeta, timedQuery} from "../../../helpers/functions";
import {GetInfoResult} from "eosjs/dist/eosjs-rpc-interfaces";
async function getTransaction(fastify: FastifyInstance, request: FastifyRequest) {
const _size = fastify.manager.config.api.limits.get_trx_actions || 100;
const redis = fastify.redis;
const query: any = request.query;
const trxId = query.id.toLowerCase();
const conf = fastify.manager.config;
const cachedData = await redis.hgetall('trx_' + trxId);
const response = {
query_time_ms: undefined,
executed: false,
cached: undefined,
cache_expires_in: undefined,
trx_id: query.id,
lib: undefined,
cached_lib: false,
actions: undefined,
generated: undefined,
error: undefined
};
let hits;
let indexPattern = fastify.manager.chain + '-action-*';
if (request.query.hot_only) {
indexPattern = fastify.manager.chain + '-action';
}
// 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) {
response.cached_lib = true;
resolve(JSON.parse(value));
} else {
fastify.eosjs.rpc.get_info().then(value1 => {
fastify.redis.set(key, JSON.stringify(value1), 'EX', 6);
response.cached_lib = false;
resolve(value1);
}).catch((reason) => {
console.log(reason);
response.error = 'failed to get last_irreversible_block_num'
resolve(null);
});
}
});
});
const pResults = await Promise.all([
fastify.eosjs.rpc.get_info(),
fastify.elastic.search({
index: indexPattern,
size: _size,
body: {
query: {
bool: {
must: [
{term: {trx_id: request.query.id.toLowerCase()}}
]
}
},
sort: {
global_sequence: "asc"
}
}
}),
fastify.elastic.search({
index: fastify.manager.chain + '-gentrx-*',
size: _size,
body: {
query: {
bool: {
must: [
{term: {trx_id: request.query.id.toLowerCase()}}
]
}
}
}
})
]);
// 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.lib = promiseResults[1].last_irreversible_block_num;
}
const results = pResults[1];
const genTrxRes = pResults[2];
// search on ES if cache is not present
if (!hits) {
const _size = conf.api.limits.get_trx_actions || 100;
const blockHint = parseInt(query.block_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 {
const response = {
"executed": false,
"hot_only": false,
"trx_id": request.query.id,
"lib": pResults[0].last_irreversible_block_num,
"actions": [],
"generated": undefined
};
// build search request
const $search = fastify.elastic.search({
index: indexPattern,
size: _size,
body: {
query: {bool: {must: [{term: {trx_id: trxId}}]}},
sort: {global_sequence: "asc"}
}
});
if (request.query.hot_only) {
response.hot_only = true;
}
// execute in parallel
pResults = await Promise.all([$getInfo, $search]);
} catch (e) {
console.log(e.message);
if (e.meta.statusCode === 404) {
response.error = 'no data near block_hint'
return response;
}
}
hits = pResults[1]['body']['hits']['hits'];
response.lib = pResults[0].last_irreversible_block_num;
}
const hits = results['body']['hits']['hits'];
if (hits.length > 0) {
for (let action of hits) {
action = action._source;
mergeActionMeta(action);
response.actions.push(action);
}
response.executed = true;
}
const hits2 = genTrxRes['body']['hits']['hits'];
if (hits2 && hits2.length > 0) {
if (hits2[0]._source['@timestamp']) {
hits2[0]._source['timestamp'] = hits2[0]._source['@timestamp'];
delete hits2[0]._source['@timestamp'];
}
response.generated = hits2[0]._source;
}
return response;
if (hits.length > 0) {
let highestBlockNum = 0;
for (let action of hits) {
if (action._source.block_num > highestBlockNum) {
highestBlockNum = action._source.block_num;
}
}
response.actions = [];
for (let action of hits) {
if (action._source.block_num === highestBlockNum) {
mergeActionMeta(action._source);
response.actions.push(action._source);
}
}
response.executed = true;
}
return response;
}
export function getTransactionHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => {
reply.send(await timedQuery(getTransaction, fastify, request, route));
}
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getTransaction, fastify, request, route));
}
}
@@ -10,9 +10,13 @@ export default function (fastify: FastifyInstance, opts: any, next) {
querystring: {
type: 'object',
properties: {
"id": {
id: {
description: 'transaction id',
type: 'string'
},
block_hint: {
description: 'block hint to speed up tx recovery',
type: 'integer'
}
},
required: ["id"]
@@ -1,10 +1,12 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import got from "got";
import {timedQuery} from "../../../helpers/functions";
async function getAccount(fastify: FastifyInstance, request: FastifyRequest) {
const query: any = request.query;
const response = {
account: null,
actions: null,
@@ -13,7 +15,7 @@ async function getAccount(fastify: FastifyInstance, request: FastifyRequest) {
links: null
};
const account = request.query.account;
const account = query.account;
const reqQueue = [];
try {
@@ -45,7 +47,7 @@ async function getAccount(fastify: FastifyInstance, request: FastifyRequest) {
}
export function getAccountHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => {
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getAccount, fastify, request, route));
}
}
@@ -1,137 +1,139 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import {timedQuery} from "../../../helpers/functions";
import {Numeric} from "eosjs/dist";
import {getSkipLimit} from "../../v2-history/get_actions/functions";
function invalidKey() {
const err: any = new Error();
err.statusCode = 400;
err.message = 'invalid public key';
throw err;
const err: any = new Error();
err.statusCode = 400;
err.message = 'invalid public key';
throw err;
}
async function getKeyAccounts(fastify: FastifyInstance, request: FastifyRequest) {
let publicKey;
if (typeof request.body === 'string' && request.req.method === 'POST') {
request.body = JSON.parse(request.body);
}
let publicKey;
if (typeof request.body === 'string' && request.method === 'POST') {
request.body = JSON.parse(request.body);
}
const {skip, limit} = getSkipLimit(request.query);
const maxDocs = fastify.manager.config.api.limits.get_key_accounts ?? 1000;
const body: any = request.body;
const query: any = request.query;
const public_Key = request.req.method === 'POST' ? request.body.public_key : request.query.public_key;
const {skip, limit} = getSkipLimit(request.query);
const maxDocs = fastify.manager.config.api.limits.get_key_accounts ?? 1000;
if (public_Key.startsWith("PUB_")) {
publicKey = public_Key;
} else if (public_Key.startsWith("EOS")) {
try {
publicKey = Numeric.convertLegacyPublicKey(public_Key);
} catch (e) {
console.log(e.message);
invalidKey();
}
} else {
invalidKey();
}
const public_Key = request.method === 'POST' ? body.public_key : query.public_key;
const response = {
account_names: []
} as any;
if (public_Key.startsWith("PUB_")) {
publicKey = public_Key;
} else if (public_Key.startsWith("EOS")) {
try {
publicKey = Numeric.convertLegacyPublicKey(public_Key);
} catch (e) {
console.log(e.message);
invalidKey();
}
} else {
invalidKey();
}
if (request.req.method === 'GET' && request.query.details) {
response.permissions = [];
}
const response = {
account_names: []
} as any;
try {
if (request.method === 'GET' && query.details) {
response.permissions = [];
}
const permTableResults = await fastify.elastic.search({
index: fastify.manager.chain + '-perm-*',
size: (limit > maxDocs ? maxDocs : limit) || 100,
from: skip || 0,
body: {
query: {
bool: {
must: [
{
term: {
"auth.keys.key.keyword": publicKey
}
}
],
}
}
}
});
try {
if (permTableResults.body.hits.hits.length > 0) {
for (const perm of permTableResults.body.hits.hits) {
response.account_names.push(perm._source.owner);
if (request.req.method === 'GET' && request.query.details) {
response.permissions.push(perm._source);
}
}
}
const permTableResults = await fastify.elastic.search({
index: fastify.manager.chain + '-perm-*',
size: (limit > maxDocs ? maxDocs : limit) || 100,
from: skip || 0,
body: {
query: {
bool: {
must: [
{
term: {
"auth.keys.key.keyword": publicKey
}
}
],
}
}
}
});
} catch (e) {
console.log(e);
}
if (permTableResults.body.hits.hits.length > 0) {
for (const perm of permTableResults.body.hits.hits) {
response.account_names.push(perm._source.owner);
if (request.method === 'GET' && query.details) {
response.permissions.push(perm._source);
}
}
}
if (response.account_names.length > 0) {
response.account_names = [...(new Set(response.account_names))];
return response;
}
} catch (e) {
console.log(e);
}
// Fallback to action search
const _body = {
query: {
bool: {
should: [
{term: {"@updateauth.auth.keys.key.keyword": publicKey}},
{term: {"@newaccount.active.keys.key.keyword": publicKey}},
{term: {"@newaccount.owner.keys.key.keyword": publicKey}}
],
minimum_should_match: 1
}
},
sort: [{"global_sequence": {"order": "desc"}}]
};
if (response.account_names.length > 0) {
response.account_names = [...(new Set(response.account_names))];
return response;
}
const results = await fastify.elastic.search({
index: fastify.manager.chain + '-action-*',
size: (limit > maxDocs ? maxDocs : limit) || 100,
from: skip || 0,
body: _body
});
// Fallback to action search
const _body = {
query: {
bool: {
should: [
{term: {"@updateauth.auth.keys.key.keyword": publicKey}},
{term: {"@newaccount.active.keys.key.keyword": publicKey}},
{term: {"@newaccount.owner.keys.key.keyword": publicKey}}
],
minimum_should_match: 1
}
},
sort: [{"global_sequence": {"order": "desc"}}]
};
if (results['body']['hits']['hits'].length > 0) {
response.account_names = results['body']['hits']['hits'].map((v) => {
if (v._source.act.name === 'newaccount') {
if (v._source['@newaccount'].newact) {
return v._source['@newaccount'].newact;
} else if (v._source.act.data.newact) {
return v._source.act.data.newact;
} else {
return null;
}
} else if (v._source.act.name === 'updateauth') {
return v._source.act.data.account;
} else {
return null;
}
});
}
const results = await fastify.elastic.search({
index: fastify.manager.chain + '-action-*',
size: (limit > maxDocs ? maxDocs : limit) || 100,
from: skip || 0,
body: _body
});
if (response.account_names.length > 0) {
response.account_names = [...(new Set(response.account_names))];
}
if (results['body']['hits']['hits'].length > 0) {
response.account_names = results['body']['hits']['hits'].map((v) => {
if (v._source.act.name === 'newaccount') {
if (v._source['@newaccount'].newact) {
return v._source['@newaccount'].newact;
} else if (v._source.act.data.newact) {
return v._source.act.data.newact;
} else {
return null;
}
} else if (v._source.act.name === 'updateauth') {
return v._source.act.data.account;
} else {
return null;
}
});
}
return response;
if (response.account_names.length > 0) {
response.account_names = [...(new Set(response.account_names))];
}
return response;
}
export function getKeyAccountsHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => {
reply.send(await timedQuery(getKeyAccounts, fastify, request, route));
}
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getKeyAccounts, fastify, request, route));
}
}
@@ -38,7 +38,7 @@ export default function (fastify: FastifyInstance, opts: any, next) {
last_updated: {type: 'string'},
auth: {},
name: {type: 'string'},
present: {type: 'boolean'}
present: {type: 'number'}
}
}
}
+52 -52
View File
@@ -1,71 +1,71 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import {getTrackTotalHits, timedQuery} from "../../../helpers/functions";
import {ApiResponse} from "@elastic/elasticsearch";
import {getSkipLimit} from "../../v2-history/get_actions/functions";
async function getLinks(fastify: FastifyInstance, request: FastifyRequest) {
const query = request.query;
const {account, code, action, permissions} = query;
const {skip, limit} = getSkipLimit(query);
const query: any = request.query;
const {account, code, action, permissions} = query;
const {skip, limit} = getSkipLimit(query);
const queryStruct = {
"bool": {
must: []
}
};
const queryStruct = {
"bool": {
must: [],
must_not: []
}
};
if (account) {
queryStruct.bool.must.push({'term': {'account': account}});
}
if (account) {
queryStruct.bool.must.push({'term': {'account': account}});
}
if (code) {
queryStruct.bool.must.push({'term': {'code': code}});
}
if (code) {
queryStruct.bool.must.push({'term': {'code': code}});
}
if (action) {
queryStruct.bool.must.push({'term': {'action': action}});
}
if (action) {
queryStruct.bool.must.push({'term': {'action': action}});
}
if (permissions) {
queryStruct.bool.must.push({'term': {'permissions': permissions}});
}
if (permissions) {
queryStruct.bool.must.push({'term': {'permissions': permissions}});
}
// only present deltas
queryStruct.bool.must.push({'term': {'present': true}});
// only present deltas
queryStruct.bool.must_not.push({'term': {'present': 0}});
// Prepare query body
const query_body = {
track_total_hits: getTrackTotalHits(query),
query: queryStruct,
sort: {block_num: 'desc'}
};
// Prepare query body
const query_body = {
track_total_hits: getTrackTotalHits(query),
query: queryStruct,
sort: {block_num: 'desc'}
};
const maxLinks = fastify.manager.config.api.limits.get_links;
const results: ApiResponse = await fastify.elastic.search({
index: fastify.manager.chain + '-link-*',
from: skip || 0,
size: (limit > maxLinks ? maxLinks : limit) || 50,
body: query_body
});
const hits = results.body.hits.hits;
const response: any = {
cached: false,
total: results.body.hits.total,
links: []
};
const maxLinks = fastify.manager.config.api.limits.get_links;
const results: ApiResponse = await fastify.elastic.search({
index: fastify.manager.chain + '-link-*',
from: skip || 0,
size: (limit > maxLinks ? maxLinks : limit) || 50,
body: query_body
});
const hits = results.body.hits.hits;
const response: any = {
cached: false,
total: results.body.hits.total,
links: []
};
for (const hit of hits) {
const link = hit._source;
link.timestamp = link['@timestamp'];
delete link['@timestamp'];
response.links.push(link);
}
return response;
for (const hit of hits) {
const link = hit._source;
link.timestamp = link['@timestamp'];
delete link['@timestamp'];
response.links.push(link);
}
return response;
}
export function getLinksHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => {
reply.send(await timedQuery(getLinks, fastify, request, route));
}
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getLinks, fastify, request, route));
}
}
@@ -1,16 +1,17 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import {getTrackTotalHits, timedQuery} from "../../../helpers/functions";
async function getProposals(fastify: FastifyInstance, request: FastifyRequest) {
const query: any = request.query;
// Pagination
let skip, limit;
skip = parseInt(request.query.skip, 10);
skip = parseInt(query.skip, 10);
if (skip < 0) {
return 'invalid skip parameter';
}
limit = parseInt(request.query.limit, 10);
limit = parseInt(query.limit, 10);
if (limit < 1) {
return 'invalid limit parameter';
}
@@ -22,8 +23,8 @@ async function getProposals(fastify: FastifyInstance, request: FastifyRequest) {
};
// Filter by accounts
if (request.query.account) {
const accounts = request.query.account.split(',');
if (query.account) {
const accounts = query.account.split(',');
for(const acc of accounts) {
queryStruct.bool.must.push({
"bool": {
@@ -37,28 +38,28 @@ async function getProposals(fastify: FastifyInstance, request: FastifyRequest) {
}
// Filter by proposer account
if (request.query.proposer) {
queryStruct.bool.must.push({"term": {"proposer": request.query.proposer}});
if (query.proposer) {
queryStruct.bool.must.push({"term": {"proposer": query.proposer}});
}
// Filter by proposal name
if (request.query.proposal) {
queryStruct.bool.must.push({"term": {"proposal_name": request.query.proposal}});
if (query.proposal) {
queryStruct.bool.must.push({"term": {"proposal_name": query.proposal}});
}
// Filter by execution status
if (typeof request.query.executed !== 'undefined') {
queryStruct.bool.must.push({"term": {"executed": request.query.executed}});
if (typeof query.executed !== 'undefined') {
queryStruct.bool.must.push({"term": {"executed": query.executed}});
}
// Filter by requested actors
if (request.query.requested) {
queryStruct.bool.must.push({"term": {"requested_approvals.actor": request.query.requested}});
if (query.requested) {
queryStruct.bool.must.push({"term": {"requested_approvals.actor": query.requested}});
}
// Filter by provided actors
if (request.query.provided) {
queryStruct.bool.must.push({"term": {"provided_approvals.actor": request.query.provided}});
if (query.provided) {
queryStruct.bool.must.push({"term": {"provided_approvals.actor": query.provided}});
}
// If no filter switch to full match
@@ -97,7 +98,7 @@ async function getProposals(fastify: FastifyInstance, request: FastifyRequest) {
}
export function getProposalsHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => {
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getProposals, fastify, request, route));
}
}
+15 -7
View File
@@ -1,4 +1,3 @@
import {ServerResponse} from "http";
import {timedQuery} from "../../../helpers/functions";
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {getSkipLimit} from "../../v2-history/get_actions/functions";
@@ -6,7 +5,9 @@ import {getSkipLimit} from "../../v2-history/get_actions/functions";
async function getTokens(fastify: FastifyInstance, request: FastifyRequest) {
const response = {'account': request.query.account, 'tokens': []};
const query: any = request.query;
const response = {'account': query.account, 'tokens': []};
const {skip, limit} = getSkipLimit(request.query);
const maxDocs = fastify.manager.config.api.limits.get_tokens ?? 100;
@@ -18,19 +19,26 @@ async function getTokens(fastify: FastifyInstance, request: FastifyRequest) {
"body": {
query: {
bool: {
filter: [{term: {"scope": request.query.account}}]
filter: [{term: {"scope": query.account}}]
}
}
}
});
const testSet = new Set();
for (const hit of stateResult.body.hits.hits) {
const data = hit._source;
if (typeof data.present !== "undefined" && data.present === false) {
if (typeof data.present !== "undefined" && data.present === 0) {
continue;
}
let precision;
const key = `${data.code}_${data.symbol}`;
if (testSet.has(key)) {
continue;
}
testSet.add(key);
if (!fastify.tokenCache) {
fastify.tokenCache = new Map<string, any>();
}
@@ -39,7 +47,7 @@ async function getTokens(fastify: FastifyInstance, request: FastifyRequest) {
} else {
let token_data;
try {
token_data = await fastify.eosjs.rpc.get_currency_balance(data.code, request.query.account, data.symbol);
token_data = await fastify.eosjs.rpc.get_currency_balance(data.code, query.account, data.symbol);
if (token_data.length > 0) {
const [amount, symbol] = token_data[0].split(" ");
const amount_arr = amount.split(".");
@@ -50,7 +58,7 @@ async function getTokens(fastify: FastifyInstance, request: FastifyRequest) {
}
}
} catch (e) {
console.log(`get_currency_balance error - contract:${data.code} - account:${request.query.account}`);
console.log(`get_currency_balance error - contract:${data.code} - account:${query.account}`);
}
}
@@ -66,7 +74,7 @@ async function getTokens(fastify: FastifyInstance, request: FastifyRequest) {
}
export function getTokensHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => {
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getTokens, fastify, request, route));
}
}
+48 -48
View File
@@ -1,63 +1,63 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import {timedQuery} from "../../../helpers/functions";
import {getSkipLimit} from "../../v2-history/get_actions/functions";
async function getVoters(fastify: FastifyInstance, request: FastifyRequest) {
const {skip, limit} = getSkipLimit(request.query);
const query: any = request.query;
const {skip, limit} = getSkipLimit(request.query);
const response = {
voter_count: 0,
'voters': []
};
let queryStruct: any = {
"bool": {
"must": []
}
};
const response = {
voter_count: 0,
'voters': []
};
let queryStruct: any = {
"bool": {
"must": []
}
};
if (request.query.producer) {
for (const bp of request.query.producer.split(",")) {
queryStruct.bool.must.push({"term": {"producers": bp}});
}
}
if (query.producer) {
for (const bp of query.producer.split(",")) {
queryStruct.bool.must.push({"term": {"producers": bp}});
}
}
if (request.query.proxy === 'true') {
queryStruct.bool.must.push({"term": {"is_proxy": true}});
}
if (query.proxy === 'true') {
queryStruct.bool.must.push({"term": {"is_proxy": true}});
}
if (queryStruct.bool.must.length === 0) {
queryStruct = {
"match_all": {}
};
}
if (queryStruct.bool.must.length === 0) {
queryStruct = {
"match_all": {}
};
}
const maxDocs = fastify.manager.config.api.limits.get_voters ?? 100;
const results = await fastify.elastic.search({
"index": fastify.manager.chain + '-table-voters-*',
"from": skip || 0,
"size": (limit > maxDocs ? maxDocs : limit) || 10,
"body": {
"query": queryStruct,
"sort": [{"last_vote_weight": "desc"}]
}
});
const hits = results['body']['hits']['hits'];
for (const hit of hits) {
const voter = hit._source;
response.voters.push({
account: voter.voter,
weight: voter.last_vote_weight,
last_vote: voter.block_num
});
}
response.voter_count = results['body']['hits']['total']['value'];
return response;
const maxDocs = fastify.manager.config.api.limits.get_voters ?? 100;
const results = await fastify.elastic.search({
"index": fastify.manager.chain + '-table-voters-*',
"from": skip || 0,
"size": (limit > maxDocs ? maxDocs : limit) || 10,
"body": {
"query": queryStruct,
"sort": [{"last_vote_weight": "desc"}]
}
});
const hits = results['body']['hits']['hits'];
for (const hit of hits) {
const voter = hit._source;
response.voters.push({
account: voter.voter,
weight: voter.last_vote_weight,
last_vote: voter.block_num
});
}
response.voter_count = results['body']['hits']['total']['value'];
return response;
}
export function getVotersHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => {
reply.send(await timedQuery(getVoters, fastify, request, route));
}
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getVoters, fastify, request, route));
}
}
@@ -1,121 +1,120 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import {timedQuery} from "../../../helpers/functions";
async function getLastSeq(fastify: FastifyInstance, date: string) {
const req = await fastify.elastic.search({
index: fastify.manager.chain + '-action-*',
body: {
"size": 1,
"query": {
"bool": {
"must": [
{"range": {"@timestamp": {"lt": date}}}
]
}
},
"_source": {
"includes": ["global_sequence"]
},
"sort": [{"global_sequence": "desc"}]
}
});
return req.body.hits.hits[0]._source.global_sequence;
const req = await fastify.elastic.search({
index: fastify.manager.chain + '-action-*',
body: {
"size": 1,
"query": {
"bool": {
"must": [
{"range": {"@timestamp": {"lt": date}}}
]
}
},
"_source": {
"includes": ["global_sequence"]
},
"sort": [{"global_sequence": "desc"}]
}
});
return req.body.hits.hits[0]._source.global_sequence;
}
async function getTxCount(fastify: FastifyInstance, dateFrom: string, dateTo: string) {
const req = await fastify.elastic.count({
index: fastify.manager.chain + '-action-*',
body: {
"query": {
"bool": {
"must": [
{"term": {"creator_action_ordinal": 0}},
{"range": {"@timestamp": {"gt": dateFrom, "lt": dateTo}}}
]
}
}
}
});
return req.body.count;
const req = await fastify.elastic.count({
index: fastify.manager.chain + '-action-*',
body: {
"query": {
"bool": {
"must": [
{"term": {"creator_action_ordinal": 0}},
{"range": {"@timestamp": {"gt": dateFrom, "lt": dateTo}}}
]
}
}
}
});
return req.body.count;
}
async function getUniqueActors(fastify: FastifyInstance, dateFrom: string, dateTo: string) {
const req = await fastify.elastic.search({
index: fastify.manager.chain + '-action-*',
size: 0,
body: {
"aggs": {
"unique_actors": {
"cardinality": {
"field": "act.authorization.actor"
}
}
},
"query": {
"bool": {
"filter": [{"range": {"@timestamp": {"gt": dateFrom, "lt": dateTo}}}]
}
}
}
});
return req.body.aggregations.unique_actors.value;
const req = await fastify.elastic.search({
index: fastify.manager.chain + '-action-*',
size: 0,
body: {
"aggs": {
"unique_actors": {
"cardinality": {
"field": "act.authorization.actor"
}
}
},
"query": {
"bool": {
"filter": [{"range": {"@timestamp": {"gt": dateFrom, "lt": dateTo}}}]
}
}
}
});
return req.body.aggregations.unique_actors.value;
}
async function getActionUsage(fastify: FastifyInstance, request: FastifyRequest) {
const query = request.query;
let now = new Date();
let expiration = 86400;
if (query.end_date) {
now = new Date(query.end_date);
}
now.setMinutes(0, 0, 0);
let period;
if (query.period === '1h') {
period = 3600000;
} else if (query.period === '24h' || query.period === '1d') {
period = 86400000;
} else {
return {};
}
const query: any = request.query;
let now = new Date();
let expiration = 86400;
if (query.end_date) {
now = new Date(query.end_date);
}
now.setMinutes(0, 0, 0);
let period;
if (query.period === '1h') {
period = 3600000;
} else if (query.period === '24h' || query.period === '1d') {
period = 86400000;
} else {
return {};
}
if (!query.end_date) {
expiration = Math.floor((period - (Date.now() - now.getTime())) / 1000);
}
if (!query.end_date) {
expiration = Math.floor((period - (Date.now() - now.getTime())) / 1000);
}
const response = {} as any;
const periodStart = new Date(now.getTime() - period);
const pStart = periodStart.toISOString();
const pEnd = now.toISOString();
const response = {} as any;
const periodStart = new Date(now.getTime() - period);
const pStart = periodStart.toISOString();
const pEnd = now.toISOString();
const cacheKey = `${fastify.manager.chain}_act_stats_${pStart}_${pEnd}_${query.unique_actors}`;
const cachedValue = await fastify.redis.get(cacheKey);
if (cachedValue) {
const cachedResponse = JSON.parse(cachedValue);
cachedResponse.cached = true;
cachedResponse.cache_exp = expiration;
return cachedResponse;
}
const cacheKey = `${fastify.manager.chain}_act_stats_${pStart}_${pEnd}_${query.unique_actors}`;
const cachedValue = await fastify.redis.get(cacheKey);
if (cachedValue) {
const cachedResponse = JSON.parse(cachedValue);
cachedResponse.cached = true;
cachedResponse.cache_exp = expiration;
return cachedResponse;
}
const firstReq = await getLastSeq(fastify, pStart);
const lastReq = await getLastSeq(fastify, pEnd);
response.action_count = lastReq - firstReq;
response.tx_count = await getTxCount(fastify, pStart, pEnd);
const firstReq = await getLastSeq(fastify, pStart);
const lastReq = await getLastSeq(fastify, pEnd);
response.action_count = lastReq - firstReq;
response.tx_count = await getTxCount(fastify, pStart, pEnd);
if (query.unique_actors) {
response.unique_actors = await getUniqueActors(fastify, pStart, pEnd);
}
if (query.unique_actors) {
response.unique_actors = await getUniqueActors(fastify, pStart, pEnd);
}
response.period = query.period;
response.from = pStart;
response.to = pEnd;
response.period = query.period;
response.from = pStart;
response.to = pEnd;
await fastify.redis.set(cacheKey, JSON.stringify(response), 'EX', expiration);
return response;
await fastify.redis.set(cacheKey, JSON.stringify(response), 'EX', expiration);
return response;
}
export function getActionUsageHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => {
reply.send(await timedQuery(getActionUsage, fastify, request, route));
}
}
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getActionUsage, fastify, request, route));
}
}
@@ -1,64 +1,64 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {timedQuery} from "../../../helpers/functions";
import {ServerResponse} from "http";
import {Search} from "@elastic/elasticsearch/api/requestParams";
import {applyTimeFilter} from "../../v2-history/get_actions/functions";
async function getMissedBlocks(fastify: FastifyInstance, request: FastifyRequest) {
const response = {
stats: {
by_producer: {}
},
events: []
};
const searchParams: Search<any> = {
track_total_hits: true,
index: fastify.manager.chain + "-logs-*",
size: 100,
body: {
query: {
bool: {
must: [
{term: {"type": "missed_blocks"}}
]
}
},
sort: {
"@timestamp": "desc"
}
}
};
const query: any = request.query;
const response = {
stats: {
by_producer: {}
},
events: []
};
const searchParams: Search<any> = {
track_total_hits: true,
index: fastify.manager.chain + "-logs-*",
size: 100,
body: {
query: {
bool: {
must: [
{term: {"type": "missed_blocks"}}
]
}
},
sort: {
"@timestamp": "desc"
}
}
};
let minBlocks = 0;
if (request.query.min_blocks) {
minBlocks = parseInt(request.query.min_blocks);
searchParams.body.query.bool.must.push({range: {"missed_blocks.size": {gte: minBlocks}}})
}
let minBlocks = 0;
if (query.min_blocks) {
minBlocks = parseInt(query.min_blocks);
searchParams.body.query.bool.must.push({range: {"missed_blocks.size": {gte: minBlocks}}})
}
if (request.query.producer) {
searchParams.body.query.bool.must.push({term: {"missed_blocks.producer": request.query.producer}});
}
if (query.producer) {
searchParams.body.query.bool.must.push({term: {"missed_blocks.producer": query.producer}});
}
applyTimeFilter(request.query, searchParams.body.query);
applyTimeFilter(query, searchParams.body.query);
const apiResponse = await fastify.elastic.search(searchParams);
apiResponse.body.hits.hits.forEach(v => {
const ev = v._source;
response.events.push({
"@timestamp": ev["@timestamp"],
...ev.missed_blocks
});
if (!response.stats.by_producer[ev.missed_blocks.producer]) {
response.stats.by_producer[ev.missed_blocks.producer] = ev.missed_blocks.size;
} else {
response.stats.by_producer[ev.missed_blocks.producer] += ev.missed_blocks.size;
}
});
return response;
const apiResponse = await fastify.elastic.search(searchParams);
apiResponse.body.hits.hits.forEach(v => {
const ev = v._source;
response.events.push({
"@timestamp": ev["@timestamp"],
...ev.missed_blocks
});
if (!response.stats.by_producer[ev.missed_blocks.producer]) {
response.stats.by_producer[ev.missed_blocks.producer] = ev.missed_blocks.size;
} else {
response.stats.by_producer[ev.missed_blocks.producer] += ev.missed_blocks.size;
}
});
return response;
}
export function getMissedBlocksHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => {
reply.send(await timedQuery(getMissedBlocks, fastify, request, route));
}
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getMissedBlocks, fastify, request, route));
}
}
@@ -1,16 +1,16 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import {timedQuery} from "../../../helpers/functions";
const percentiles = [1, 5, 25, 50, 75, 95, 99];
async function getResourceUsage(fastify: FastifyInstance, request: FastifyRequest) {
const query: any = request.query;
const searchBody: any = {
query: {
bool: {
must: [
{term: {"act.account": request.query.code}},
{term: {"act.name": request.query.action}},
{term: {"act.account": query.code}},
{term: {"act.name": query.action}},
{term: {"action_ordinal": {"value": 1}}},
{
range: {
@@ -69,7 +69,7 @@ async function getResourceUsage(fastify: FastifyInstance, request: FastifyReques
}
export function getResourceUsageHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => {
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getResourceUsage, fastify, request, route));
}
}
+68 -69
View File
@@ -1,91 +1,90 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import {connect} from "amqplib";
import {timedQuery} from "../../../helpers/functions";
import {getLastIndexedBlockWithTotalBlocks} from "../../../../helpers/common_functions";
async function checkRabbit(fastify: FastifyInstance) {
try {
const connection = await connect(fastify.manager.ampqUrl);
await connection.close();
return createHealth('RabbitMq', 'OK');
} catch (e) {
console.log(e);
return createHealth('RabbitMq', 'Error');
}
try {
const connection = await connect(fastify.manager.ampqUrl);
await connection.close();
return createHealth('RabbitMq', 'OK');
} catch (e) {
console.log(e);
return createHealth('RabbitMq', 'Error');
}
}
async function checkNodeos(fastify: FastifyInstance) {
const rpc = fastify.manager.nodeosJsonRPC;
try {
const results = await rpc.get_info();
if (results) {
const diff = (new Date().getTime()) - (new Date(results.head_block_time + '+00:00').getTime());
return createHealth('NodeosRPC', 'OK', {
head_block_num: results.head_block_num,
head_block_time: results.head_block_time,
time_offset: diff,
last_irreversible_block: results.last_irreversible_block_num,
chain_id: results.chain_id
});
} else {
return createHealth('NodeosRPC', 'Error');
}
} catch (e) {
return createHealth('NodeosRPC', 'Error');
}
const rpc = fastify.manager.nodeosJsonRPC;
try {
const results = await rpc.get_info();
if (results) {
const diff = (new Date().getTime()) - (new Date(results.head_block_time + '+00:00').getTime());
return createHealth('NodeosRPC', 'OK', {
head_block_num: results.head_block_num,
head_block_time: results.head_block_time,
time_offset: diff,
last_irreversible_block: results.last_irreversible_block_num,
chain_id: results.chain_id
});
} else {
return createHealth('NodeosRPC', 'Error');
}
} catch (e) {
return createHealth('NodeosRPC', 'Error');
}
}
async function checkElastic(fastify: FastifyInstance) {
try {
let esStatus = await fastify.elastic.cat.health({format: 'json', v: true});
let indexedBlocks = await getLastIndexedBlockWithTotalBlocks(fastify.elastic, fastify.manager.chain);
const data = {
last_indexed_block: indexedBlocks[0],
total_indexed_blocks: indexedBlocks[1],
active_shards: esStatus.body[0]['active_shards_percent']
};
let stat = 'OK';
esStatus.body.forEach(status => {
if (status.status === 'yellow' && stat !== 'Error') {
stat = 'Warning'
} else if (status.status === 'red') {
stat = 'Error'
}
});
return createHealth('Elasticsearch', stat, data);
} catch (e) {
console.log(e, 'Elasticsearch Error');
return createHealth('Elasticsearch', 'Error');
}
try {
let esStatus = await fastify.elastic.cat.health({format: 'json', v: true});
let indexedBlocks = await getLastIndexedBlockWithTotalBlocks(fastify.elastic, fastify.manager.chain);
const data = {
last_indexed_block: indexedBlocks[0],
total_indexed_blocks: indexedBlocks[1] + 1,
active_shards: esStatus.body[0]['active_shards_percent']
};
let stat = 'OK';
esStatus.body.forEach(status => {
if (status.status === 'yellow' && stat !== 'Error') {
stat = 'Warning'
} else if (status.status === 'red') {
stat = 'Error'
}
});
return createHealth('Elasticsearch', stat, data);
} catch (e) {
console.log(e, 'Elasticsearch Error');
return createHealth('Elasticsearch', 'Error');
}
}
function createHealth(name: string, status, data?: any) {
let time = Date.now();
return {
service: name,
status: status,
service_data: data,
time: time
}
let time = Date.now();
return {
service: name,
status: status,
service_data: data,
time: time
}
}
async function getHealthQuery(fastify: FastifyInstance, request: FastifyRequest) {
let response = {
version: fastify.manager.current_version,
version_hash: fastify.manager.getServerHash(),
host: fastify.manager.config.api.server_name,
features: fastify.manager.config.features,
health: []
};
response.health.push(await checkRabbit(fastify));
response.health.push(await checkNodeos(fastify));
response.health.push(await checkElastic(fastify));
return response;
let response = {
version: fastify.manager.current_version,
version_hash: fastify.manager.getServerHash(),
host: fastify.manager.config.api.server_name,
health: [],
features: fastify.manager.config.features
};
response.health.push(await checkRabbit(fastify));
response.health.push(await checkNodeos(fastify));
response.health.push(await checkElastic(fastify));
return response;
}
export function healthHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => {
reply.send(await timedQuery(getHealthQuery, fastify, request, route));
}
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getHealthQuery, fastify, request, route));
}
}
+13 -13
View File
@@ -1,18 +1,18 @@
import {FastifyInstance, RouteSchema} from "fastify";
import {FastifyInstance, FastifySchema} from "fastify";
import {addApiRoute, getRouteName} from "../../../helpers/functions";
import {healthHandler} from "./health";
export default function (fastify: FastifyInstance, opts: any, next) {
const schema: RouteSchema = {
tags: ['status'],
summary: "API Service Health Report"
};
addApiRoute(
fastify,
'GET',
getRouteName(__filename),
healthHandler,
schema
);
next();
const schema: FastifySchema = {
tags: ['status'],
summary: "API Service Health Report"
};
addApiRoute(
fastify,
'GET',
getRouteName(__filename),
healthHandler,
schema
);
next();
}
-26
View File
@@ -1,26 +0,0 @@
exports.GET = {
description: 'fetch contract abi at specific block',
summary: 'fetch abi at specific block',
tags: ['history'],
querystring: {
type: 'object',
properties: {
"contract": {
description: 'contract account',
type: 'string',
minLength: 1,
maxLength: 12
},
"block": {
description: 'target block',
type: 'integer',
minimum: 1
},
"fetch": {
description: 'should fetch the ABI',
type: 'boolean'
}
},
required: ["contract"]
}
};
-14
View File
@@ -1,14 +0,0 @@
exports.GET = {
description: 'get account data',
summary: 'get account summary',
tags: ['accounts', 'state'],
querystring: {
type: 'object',
properties: {
"account": {
description: 'account name',
type: 'string'
}
}
}
};
-132
View File
@@ -1,132 +0,0 @@
exports.GET = {
description: 'get actions based on notified account. this endpoint also accepts generic filters based on indexed fields' +
' (e.g. act.authorization.actor=eosio or act.name=delegatebw), if included they will be combined with a AND operator',
summary: 'get root actions',
tags: ['actions', 'history'],
querystring: {
type: 'object',
properties: {
"account": {
description: 'notified account',
type: 'string',
minLength: 1,
maxLength: 12
},
"track": {
description: 'total results to track (count) [number or true]',
type: 'string'
},
"filter": {
description: 'code:name filter',
type: 'string',
minLength: 3
},
"skip": {
description: 'skip [n] actions (pagination)',
type: 'integer',
minimum: 0
},
"limit": {
description: 'limit of [n] actions per page',
type: 'integer',
minimum: 1
},
"sort": {
description: 'sort direction',
enum: ['desc', 'asc', '1', '-1'],
type: 'string'
},
"after": {
description: 'filter after specified date (ISO8601)',
type: 'string'
},
"before": {
description: 'filter before specified date (ISO8601)',
type: 'string'
},
"simple": {
description: 'simplified output mode',
type: 'boolean'
}
}
},
response: {
200: {
type: 'object',
properties: {
"query_time": {
type: "number"
},
"cached": {type: "boolean"},
"lib": {
type: "number"
},
"total": {
type: "object",
properties: {
"value": {type: "number"},
"relation": {type: "string"}
}
},
"simple_actions": {
type: "array",
items: {
type: "object",
properties: {
"block": {type: "number"},
"timestamp": {type: "string"},
"irreversible": {type: "boolean"},
"contract": {type: "string"},
"action": {type: "string"},
"actors": {type: "string"},
"notified": {type: "string"},
"transaction_id": {type: "string"},
"data": {
additionalProperties: true
}
}
}
},
"actions": {
type: "array",
items: {
type: 'object',
properties: {
"act": {
type: 'object',
properties: {
"account": {type: "string"},
"name": {type: "string"}
},
additionalProperties: true
},
"cpu_usage_us": {type: "number"},
"net_usage_words": {type: "number"},
"account_ram_deltas": {
type: "array",
items: {
type: "object",
properties: {
"account": {type: "string"},
"delta": {type: "number"}
},
additionalProperties: true
}
},
"global_sequence": {type: "number"},
"receiver": {type: 'string'},
"@timestamp": {type: "string"},
"block_num": {type: "number"},
"producer": {type: "string"},
"trx_id": {type: "string"},
"parent": {type: "number"},
"action_ordinal": {type: 'number'},
"creator_action_ordinal": {type: 'number'},
"notified": {type: "array", items: {type: "string"}}
}
}
}
}
}
}
};
-20
View File
@@ -1,20 +0,0 @@
exports.GET = {
description: 'get block range',
summary: 'get block range',
tags: ['history'],
querystring: {
type: 'object',
properties: {
"from": {
description: 'starting block',
type: 'integer',
minimum: 1
},
"to": {
description: 'last block',
type: 'integer',
minimum: 1
}
}
}
};
-44
View File
@@ -1,44 +0,0 @@
exports.GET = {
description: 'get all accounts created by one creator',
summary: 'get created accounts',
tags: ['accounts', 'history'],
querystring: {
type: 'object',
properties: {
"account": {
description: 'creator account',
type: 'string',
minLength: 1,
maxLength: 12
}
}
},
response: {
200: {
type: 'object',
properties: {
"query_time": {
type: "number"
},
"total": {
type: "object",
properties: {
"value": {type: "number"},
"relation": {type: "string"}
}
},
"accounts": {
type: "array",
items: {
type: 'object',
properties: {
'name': {type: 'string'},
'timestamp': {type: 'string'},
'trx_id': {type: 'string'}
}
}
}
}
}
}
};
-41
View File
@@ -1,41 +0,0 @@
exports.GET = {
description: 'get account creator',
summary: 'get account creator',
tags: ['accounts', 'history'],
querystring: {
type: 'object',
properties: {
"account": {
description: 'created account',
type: 'string',
minLength: 1,
maxLength: 12
}
}
},
response: {
200: {
type: 'object',
properties: {
"account": {
type: "string"
},
"creator": {
type: "string"
},
"timestamp": {
type: "string"
},
"block_num": {
type: "integer"
},
"trx_id": {
type: "string"
},
"indirect_creator": {
type: "string"
}
}
}
}
};
-26
View File
@@ -1,26 +0,0 @@
exports.GET = {
description: 'get state deltas',
summary: 'get state deltas',
tags: ['history'],
querystring: {
type: 'object',
properties: {
"code": {
description: 'contract account',
type: 'string'
},
"scope": {
description: 'table scope',
type: 'string'
},
"table": {
description: 'table name',
type: 'string'
},
"payer": {
description: 'payer account',
type: 'string'
}
}
}
};
-58
View File
@@ -1,58 +0,0 @@
module.exports = {
GET: {
description: 'get accounts by public key',
summary: 'get accounts by public key',
tags: ['accounts','state'],
querystring: {
type: 'object',
properties: {
"public_key": {
description: 'public key',
type: 'string'
},
},
required: ["public_key"]
},
response: {
200: {
type: 'object',
properties: {
"account_names": {
type: "array",
items: {
type: "string"
}
}
}
}
}
},
POST: {
description: 'get accounts by public key',
summary: 'get accounts by public key',
tags: ['accounts','state'],
body: {
type: 'object',
properties: {
"public_key": {
description: 'public key',
type: 'string'
},
},
required: ["public_key"]
},
response: {
200: {
type: 'object',
properties: {
"account_names": {
type: "array",
items: {
type: "string"
}
}
}
}
}
}
};
-48
View File
@@ -1,48 +0,0 @@
exports.GET = {
description: 'get proposals',
summary: 'get proposals',
tags: ['state'],
querystring: {
type: 'object',
properties: {
"proposer": {
description: 'filter by proposer',
type: 'string'
},
"proposal": {
description: 'filter by proposal name',
type: 'string'
},
"account": {
description: 'filter by either requested or provided account',
type: 'string'
},
"requested": {
description: 'filter by requested account',
type: 'string'
},
"provided": {
description: 'filter by provided account',
type: 'string'
},
"executed": {
description: 'filter by execution status',
type: 'boolean'
},
"track": {
description: 'total results to track (count) [number or true]',
type: 'string'
},
"skip": {
description: 'skip [n] actions (pagination)',
type: 'integer',
minimum: 0
},
"limit": {
description: 'limit of [n] actions per page',
type: 'integer',
minimum: 1
}
}
}
};
-37
View File
@@ -1,37 +0,0 @@
exports.GET = {
description: 'get tokens from account',
summary: 'get tokens from account',
tags: ['accounts', 'state'],
querystring: {
type: 'object',
properties: {
"account": {
description: 'account',
type: 'string'
},
},
required: ["account"]
},
response: {
200: {
type: 'object',
properties: {
"query_time": {type: "number"},
"cached": {type: "boolean"},
"account": {type: "string"},
"tokens": {
type: "array",
items: {
type: "object",
properties: {
"symbol": {type: "string"},
"precision": {type: "number"},
"amount": {type: "number"},
"contract": {type: "string"}
}
}
}
}
}
}
};
-52
View File
@@ -1,52 +0,0 @@
exports.GET = {
description: 'get all account that interacted with the source account provided',
summary: 'get interactions based on transfers',
tags: ['history'],
querystring: {
type: 'object',
properties: {
"account": {
description: 'source account',
type: 'string'
},
"symbol": {
description: 'token symbol',
type: 'string',
minLength: 1,
maxLength: 7
},
"contract": {
description: 'token contract',
type: 'string',
minLength: 1,
maxLength: 12
},
"direction": {
description: 'search direction',
enum: ['in', 'out', 'both'],
type: 'string'
},
"min": {
description: 'minimum value',
type: 'number'
},
"max": {
description: 'maximum value',
type: 'number'
},
"limit": {
description: 'query limit',
type: 'number'
},
"after": {
description: 'filter after specified date (ISO8601) or block number',
type: 'string'
},
"before": {
description: 'filter before specified date (ISO8601) or block number',
type: 'string'
}
},
required: ["account", "direction"]
}
};
-15
View File
@@ -1,15 +0,0 @@
exports.GET = {
description: 'get all actions belonging to the same transaction',
summary: 'get transaction by id',
tags: ['transactions','history'],
querystring: {
type: 'object',
properties: {
"id": {
description: 'transaction id',
type: 'string'
}
},
required: ["id"]
}
};
-96
View File
@@ -1,96 +0,0 @@
exports.GET = {
description: 'get token transfers utilizing the eosio.token standard',
summary: 'get token transfers',
tags: ['history'],
querystring: {
type: 'object',
properties: {
"from": {
description: 'source account',
type: 'string',
minLength: 1,
maxLength: 12
},
"to": {
description: 'destination account',
type: 'string',
minLength: 1,
maxLength: 12
},
"symbol": {
description: 'token symbol',
type: 'string',
minLength: 1,
maxLength: 7
},
"contract": {
description: 'token contract',
type: 'string',
minLength: 1,
maxLength: 12
},
"skip": {
description: 'skip [n] actions (pagination)',
type: 'integer',
minimum: 0
},
"limit": {
description: 'limit of [n] actions per page',
type: 'integer',
minimum: 1
},
"after": {
description: 'filter after specified date (ISO8601)',
type: 'string',
format: 'date-time'
},
"before": {
description: 'filter before specified date (ISO8601)',
type: 'string',
format: 'date-time'
},
},
"anyOf": [
{required: ["from"]},
{required: ["to"]},
{required: ["symbol"]},
{required: ["contract"]}
]
},
response: {
200: {
type: 'object',
properties: {
"action_count": {
type: "number"
},
"total_amount": {
type: "number"
},
"actions": {
type: "array",
items: {
type: 'object',
properties: {
"act": {
type: 'object',
properties: {
"account": {type: "string"},
"name": {type: "string"}
},
additionalProperties: true
},
"@timestamp": {type: "string"},
"block_num": {type: "number"},
"producer": {type: "string"},
"trx_id": {type: "string"},
"parent": {type: "number"},
"global_sequence": {type: "number"},
"notified": {type: "array", items: {type: "string"}}
}
}
}
}
}
}
};
-24
View File
@@ -1,24 +0,0 @@
exports.GET = {
description: 'get voters',
summary: 'get voters',
tags: ['accounts','state'],
querystring: {
type: 'object',
properties: {
"producer": {
description: 'filter by voted producer (comma separated)',
type: 'string'
},
"skip": {
description: 'skip [n] actions (pagination)',
type: 'integer',
minimum: 0
},
"limit": {
description: 'limit of [n] actions per page',
type: 'integer',
minimum: 1
}
}
}
};
-16
View File
@@ -1,16 +0,0 @@
module.exports = {
getTransfersSchema: require('./get_transfers'),
getAbiSnapshotSchema: require('./get_abi_snapshot'),
getKeyAccountsSchema: require('./get_key_accounts'),
getActionsSchema: require('./get_actions'),
getTransactedAccountsSchema: require('./get_transacted_accounts'),
getTransactionSchema: require('./get_transaction'),
getCreatorSchema: require('./get_creator'),
getTokensSchema: require('./get_tokens'),
getDeltasSchema: require('./get_deltas'),
getCreatedAccountsSchema: require('./get_created_accounts'),
getVotersSchema: require('./get_voters'),
getProposalsSchema: require('./get_proposals'),
getBlocksSchema: require('./get_blocks'),
getAccountSchema: require('./get_account')
};
+119 -86
View File
@@ -2,103 +2,145 @@ import {hLog} from "../helpers/common_functions";
import {ConfigurationModule} from "../modules/config";
import {ConnectionManager} from "../connections/manager.class";
import {HyperionConfig} from "../interfaces/hyperionConfig";
import {IncomingMessage, Server, ServerResponse} from "http";
import * as Fastify from 'fastify';
import * as Redis from 'ioredis';
import IORedis from 'ioredis';
import fastify from 'fastify'
import {registerPlugins} from "./plugins";
import {AddressInfo} from "net";
import {registerRoutes} from "./routes";
import {generateOpenApiConfig} from "./config/open_api";
import {createWriteStream, existsSync, mkdirSync, writeFileSync} from "fs";
import {createWriteStream, existsSync, mkdirSync, readFileSync} from "fs";
import {SocketManager} from "./socketManager";
import got from "got";
import {join} from "path";
import * as io from 'socket.io-client';
import {HyperionModuleLoader} from "../modules/loader";
import {extendedActions} from "./routes/v2-history/get_actions/definitions";
import {io, Socket} from "socket.io-client";
import {CacheManager} from "./helpers/cacheManager";
import {bootstrap} from 'global-agent';
bootstrap();
class HyperionApiServer {
private conf: HyperionConfig;
private readonly manager: ConnectionManager;
private readonly fastify: Fastify.FastifyInstance<Server, IncomingMessage, ServerResponse>;
private hub: Socket;
private readonly fastify;
private readonly chain: string;
socketManager: SocketManager;
private readonly conf: HyperionConfig;
private readonly manager: ConnectionManager;
private readonly cacheManager: CacheManager;
private hub: SocketIOClient.Socket;
socketManager: SocketManager;
mLoader: HyperionModuleLoader;
constructor() {
const package_json = JSON.parse(readFileSync('./package.json').toString());
hLog(`--------- Hyperion API ${package_json.version} ---------`);
const cm = new ConfigurationModule();
this.conf = cm.config;
this.chain = this.conf.settings.chain;
process.title = `hyp-${this.chain}-api`;
this.manager = new ConnectionManager(cm);
this.manager.calculateServerHash();
this.manager.getHyperionVersion();
this.mLoader = new HyperionModuleLoader(cm);
this.cacheManager = new CacheManager(this.conf);
if (!existsSync('./logs/' + this.chain)) {
mkdirSync('./logs/' + this.chain, {recursive: true});
}
const logStream = createWriteStream('./logs/' + this.chain + '/api.access.log');
this.fastify = Fastify({
const loggerOpts = {
stream: logStream,
redact: ['req.headers.authorization'],
level: 'info',
prettyPrint: true,
serializers: {
res: (reply) => {
return {
statusCode: reply.statusCode
};
},
req: (request) => {
return {
method: request.method,
url: request.url,
ip: request.headers['x-real-ip']
}
}
}
};
this.fastify = fastify({
ignoreTrailingSlash: false,
trustProxy: true,
pluginTimeout: 5000,
logger: this.conf.api.access_log ? {
stream: logStream,
redact: ['req.headers.authorization'],
level: 'info',
serializers: {
res: (res) => {
return {
status: res.statusCode
};
},
req: (req) => {
return {
method: req.method,
url: req.url,
ip: req.headers['x-real-ip']
}
}
}
} : false
logger: this.conf.api.access_log ? loggerOpts : false
});
this.fastify.decorate('cacheManager', this.cacheManager);
this.fastify.decorate('manager', this.manager);
if (this.conf.api.chain_api && this.conf.api.chain_api !== "") {
this.fastify.decorate('chain_api', this.conf.api.chain_api);
} else {
this.fastify.decorate('chain_api', this.manager.conn.chains[this.chain].http);
// import get_actions query params from custom modules
const extendedActionsSet: Set<string> = new Set([...extendedActions]);
for (const qPrefix of this.mLoader.extendedActions) {
extendedActionsSet.add(qPrefix);
}
this.fastify.decorate('allowedActionQueryParamSet', extendedActionsSet);
if (this.conf.api.push_api && this.conf.api.push_api !== "") {
// define chain api url for /v1/chain/ redirects
let chainApiUrl: string = this.conf.api.chain_api;
if (chainApiUrl === null || chainApiUrl === "") {
chainApiUrl = this.manager.conn.chains[this.chain].http;
}
this.fastify.decorate('chain_api', chainApiUrl);
// define optional push api url for /v1/chain/push_transaction
if (this.conf.api.push_api) {
this.fastify.decorate('push_api', this.conf.api.push_api);
}
console.log(`Chain API URL: ${this.fastify.chain_api}`);
console.log(`Push API URL: ${this.fastify.push_api}`);
hLog(`Chain API URL: "${this.fastify.chain_api}" | Push API URL: "${this.fastify.push_api}"`);
const ioRedisClient = new Redis(this.manager.conn.redis);
const api_rate_limit = {
max: 1000,
whitelist: ['127.0.0.1'],
timeWindow: '1 minute',
redis: ioRedisClient
};
const ioRedisClient = new IORedis(this.manager.conn.redis);
const pluginParams = {
fastify_elasticsearch: {
client: this.manager.elasticsearchClient
},
fastify_redis: this.manager.conn.redis,
fastify_eosjs: this.manager,
} as any;
if (!this.conf.api.disable_rate_limit) {
let rateLimiterWhitelist = ['127.0.0.1'];
if (this.conf.api.rate_limit_allow && this.conf.api.rate_limit_allow.length > 0) {
const tempSet = new Set<string>(['127.0.0.1', ...this.conf.api.rate_limit_allow]);
rateLimiterWhitelist = [...tempSet];
}
let rateLimiterRPM = 1000;
if (this.conf.api.rate_limit_rpm) {
rateLimiterRPM = this.conf.api.rate_limit_rpm;
}
pluginParams.fastify_rate_limit = {
max: rateLimiterRPM,
allowList: rateLimiterWhitelist,
timeWindow: '1 minute',
redis: ioRedisClient
}
}
if (this.conf.features.streaming.enable) {
this.activateStreaming();
}
registerPlugins(this.fastify, {
fastify_elasticsearch: {
client: this.manager.elasticsearchClient
},
fastify_oas: generateOpenApiConfig(this.manager.config),
fastify_rate_limit: api_rate_limit,
fastify_redis: this.manager.conn.redis,
fastify_eosjs: this.manager,
});
const docsConfig = generateOpenApiConfig(this.manager.config);
if (docsConfig) {
pluginParams.fastify_swagger = docsConfig;
}
registerPlugins(this.fastify, pluginParams);
this.addGenericTypeParsing();
}
@@ -128,15 +170,15 @@ class HyperionApiServer {
}
private addGenericTypeParsing() {
this.fastify.addContentTypeParser('*', (req, done) => {
this.fastify.addContentTypeParser('*', (request, payload, done) => {
let data = '';
req.on('data', chunk => {
payload.on('data', chunk => {
data += chunk;
});
req.on('end', () => {
payload.on('end', () => {
done(null, data);
});
req.on('error', (err) => {
payload.on('error', (err) => {
console.log('---- Content Parsing Error -----');
console.log(err);
});
@@ -145,15 +187,24 @@ class HyperionApiServer {
async init() {
await this.fetchChainLogo();
await this.mLoader.init();
// add custom plugin routes
for (const plugin of this.mLoader.plugins) {
if (plugin.hasApiRoutes) {
hLog(`Adding routes for plugin: ${plugin.internalPluginName}`);
plugin.addRoutes(this.fastify);
plugin.chainName = this.chain;
}
}
registerRoutes(this.fastify);
// register documentation when ready
this.fastify.ready().then(async () => {
await this.fastify.oas();
console.log(this.chain + ' api ready!');
await this.fastify.swagger();
}, (err) => {
console.log('an error happened', err)
hLog('an error happened', err)
});
try {
@@ -161,28 +212,14 @@ class HyperionApiServer {
host: this.conf.api.server_addr,
port: this.conf.api.server_port
});
console.log(`server listening on ${(this.fastify.server.address() as AddressInfo).port}`);
hLog(`${this.chain} hyperion api ready and listening on port ${(this.fastify.server.address() as AddressInfo).port}`);
this.startHyperionHub();
} catch (err) {
console.log(err);
hLog(err);
process.exit(1)
}
}
async fetchChainLogo() {
try {
if (this.conf.api.chain_logo_url && this.conf.api.enable_explorer) {
console.log(`Downloading chain logo from ${this.conf.api.chain_logo_url}...`);
const chainLogo = await got(this.conf.api.chain_logo_url);
const path = join(__dirname, '..', 'hyperion-explorer', 'dist', 'assets', this.chain + '_logo.png');
writeFileSync(path, chainLogo.rawBody);
this.conf.api.chain_logo_url = 'https://' + this.conf.api.server_name + '/v2/explore/assets/' + this.chain + '_logo.png';
}
} catch (e) {
console.log(e);
}
}
startHyperionHub() {
if (this.conf.hub) {
const url = this.conf.hub.inform_url;
@@ -190,17 +227,13 @@ class HyperionApiServer {
this.hub = io(url, {
query: {
key: this.conf.hub.publisher_key,
client_mode: false
client_mode: 'false'
}
});
this.hub.on('connect', () => {
hLog(`Hyperion Hub connected!`);
this.emitHubApiUpdate();
});
// this.hub.on('reconnect', () => {
// hLog(`Reconnecting...`);
// this.emitHubApiUpdate();
// });
}
}
@@ -211,7 +244,7 @@ class HyperionApiServer {
location: this.conf.hub.location,
chainId: this.manager.conn.chains[this.chain].chain_id,
providerName: this.conf.api.provider_name,
explorerEnabled: this.conf.api.enable_explorer,
explorerEnabled: this.conf.plugins.explorer?.enabled,
providerUrl: this.conf.api.provider_url,
providerLogo: this.conf.api.provider_logo,
chainLogo: this.conf.api.chain_logo_url,
+322 -315
View File
@@ -1,377 +1,384 @@
import {checkFilter, hLog} from '../helpers/common_functions';
import * as sockets from 'socket.io';
import * as IOClient from 'socket.io-client';
import {Server, Socket} from 'socket.io';
import {createAdapter} from 'socket.io-redis';
import {io} from 'socket.io-client';
import {FastifyInstance} from "fastify";
import IORedis from "ioredis";
export interface StreamDeltasRequest {
code: string;
table: string;
scope: string;
payer: string;
start_from: number | string;
read_until: number | string;
code: string;
table: string;
scope: string;
payer: string;
start_from: number | string;
read_until: number | string;
}
export interface RequestFilter {
field: string;
value: string;
field: string;
value: string;
}
export interface StreamActionsRequest {
contract: string;
account: string;
action: string;
filters: RequestFilter[];
start_from: number | string;
read_until: number | string;
contract: string;
account: string;
action: string;
filters: RequestFilter[];
start_from: number | string;
read_until: number | string;
}
async function addBlockRangeOpts(data, search_body, fastify: FastifyInstance) {
let timeRange;
let blockRange;
let head;
let timeRange;
let blockRange;
let head;
if (typeof data['start_from'] === 'string' && data['start_from'] !== '') {
if (!timeRange) {
timeRange = {"@timestamp": {}};
}
timeRange["@timestamp"]['gte'] = data['start_from'];
}
if (typeof data['start_from'] === 'string' && data['start_from'] !== '') {
if (!timeRange) {
timeRange = {"@timestamp": {}};
}
timeRange["@timestamp"]['gte'] = data['start_from'];
}
if (typeof data['read_until'] === 'string' && data['read_until'] !== '') {
if (!timeRange) {
timeRange = {"@timestamp": {}};
}
timeRange["@timestamp"]['lte'] = data['read_until'];
}
if (typeof data['read_until'] === 'string' && data['read_until'] !== '') {
if (!timeRange) {
timeRange = {"@timestamp": {}};
}
timeRange["@timestamp"]['lte'] = data['read_until'];
}
if (typeof data['start_from'] === 'number' && data['start_from'] !== 0) {
if (!blockRange) {
blockRange = {"block_num": {}};
}
if (data['start_from'] < 0) {
if (!head) {
head = (await fastify.eosjs.rpc.get_info()).head_block_num;
}
blockRange["block_num"]['gte'] = head + data['start_from'];
} else {
blockRange["block_num"]['gte'] = data['start_from'];
}
}
if (typeof data['start_from'] === 'number' && data['start_from'] !== 0) {
if (!blockRange) {
blockRange = {"block_num": {}};
}
if (data['start_from'] < 0) {
if (!head) {
head = (await fastify.eosjs.rpc.get_info()).head_block_num;
}
blockRange["block_num"]['gte'] = head + data['start_from'];
} else {
blockRange["block_num"]['gte'] = data['start_from'];
}
}
if (typeof data['read_until'] === 'number' && data['read_until'] !== 0) {
if (!blockRange) {
blockRange = {"block_num": {}};
}
if (data['read_until'] < 0) {
if (!head) {
head = (await fastify.eosjs.rpc.get_info()).head_block_num;
}
blockRange["block_num"]['lte'] = head + data['read_until'];
} else {
blockRange["block_num"]['lte'] = data['read_until'];
}
}
if (typeof data['read_until'] === 'number' && data['read_until'] !== 0) {
if (!blockRange) {
blockRange = {"block_num": {}};
}
if (data['read_until'] < 0) {
if (!head) {
head = (await fastify.eosjs.rpc.get_info()).head_block_num;
}
blockRange["block_num"]['lte'] = head + data['read_until'];
} else {
blockRange["block_num"]['lte'] = data['read_until'];
}
}
if (timeRange) {
search_body.query.bool.must.push({
range: timeRange,
});
}
if (blockRange) {
search_body.query.bool.must.push({
range: blockRange,
});
}
if (timeRange) {
search_body.query.bool.must.push({
range: timeRange,
});
}
if (blockRange) {
search_body.query.bool.must.push({
range: blockRange,
});
}
}
function addTermMatch(data, search_body, field) {
if (data[field] !== '*' && data[field] !== '') {
const termQuery = {};
termQuery[field] = data[field];
search_body.query.bool.must.push({'term': termQuery});
}
if (data[field] !== '*' && data[field] !== '') {
const termQuery = {};
termQuery[field] = data[field];
search_body.query.bool.must.push({'term': termQuery});
}
}
const deltaQueryFields = ['code', 'table', 'scope', 'payer'];
async function streamPastDeltas(fastify: FastifyInstance, socket, data) {
const search_body = {
query: {bool: {must: []}},
sort: {block_num: 'asc'},
};
await addBlockRangeOpts(data, search_body, fastify);
deltaQueryFields.forEach(f => {
addTermMatch(data, search_body, f);
});
const responseQueue = [];
let counter = 0;
const init_response = await fastify.elastic.search({
index: fastify.manager.chain + '-delta-*',
scroll: '30s',
size: 20,
body: search_body,
});
responseQueue.push(init_response);
while (responseQueue.length) {
const {body} = responseQueue.shift();
counter += body['hits']['hits'].length;
if (socket.connected) {
socket.emit('message', {
type: 'delta_trace',
mode: 'history',
messages: body['hits']['hits'].map(doc => doc._source),
});
} else {
console.log('LOST CLIENT');
break;
}
if (body['hits'].total.value === counter) {
console.log(`${counter} past deltas streamed to ${socket.id}`);
break;
}
const search_body = {
query: {bool: {must: []}},
sort: {block_num: 'asc'},
};
await addBlockRangeOpts(data, search_body, fastify);
deltaQueryFields.forEach(f => {
addTermMatch(data, search_body, f);
});
const responseQueue = [];
let counter = 0;
const init_response = await fastify.elastic.search({
index: fastify.manager.chain + '-delta-*',
scroll: '30s',
size: 20,
body: search_body,
});
responseQueue.push(init_response);
while (responseQueue.length) {
const {body} = responseQueue.shift();
counter += body['hits']['hits'].length;
if (socket.connected) {
socket.emit('message', {
type: 'delta_trace',
mode: 'history',
messages: body['hits']['hits'].map(doc => doc._source),
});
} else {
hLog('LOST CLIENT');
break;
}
if (body['hits'].total.value === counter) {
hLog(`${counter} past deltas streamed to ${socket.id}`);
break;
}
const next_response = await fastify.elastic.scroll({
body: {
scroll_id: body['_scroll_id'],
scroll: '30s'
}
});
responseQueue.push(next_response);
}
const next_response = await fastify.elastic.scroll({
body: {
scroll_id: body['_scroll_id'],
scroll: '30s'
}
});
responseQueue.push(next_response);
}
}
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);
await addBlockRangeOpts(data, search_body, fastify);
if (data.account !== '') {
search_body.query.bool.must.push({
bool: {
should: [
{term: {'notified': data.account}},
{term: {'act.authorization.actor': data.account}},
],
},
});
}
if (data.account !== '') {
search_body.query.bool.must.push({
bool: {
should: [
{term: {'notified': data.account}},
{term: {'act.authorization.actor': data.account}},
],
},
});
}
if (data.contract !== '*' && data.contract !== '') {
search_body.query.bool.must.push({'term': {'act.account': data.contract}});
}
if (data.contract !== '*' && data.contract !== '') {
search_body.query.bool.must.push({'term': {'act.account': data.contract}});
}
if (data.action !== '*' && data.action !== '') {
search_body.query.bool.must.push({'term': {'act.name': data.action}});
}
if (data.action !== '*' && data.action !== '') {
search_body.query.bool.must.push({'term': {'act.name': data.action}});
}
const onDemandFilters = [];
if (data.filters.length > 0) {
data.filters.forEach(f => {
if (f.field && f.value) {
if (f.field.startsWith('@') && !f.field.startsWith('act.data')) {
const _q = {};
_q[f.field] = f.value;
search_body.query.bool.must.push({'term': _q});
} else {
onDemandFilters.push(f);
}
}
});
}
const onDemandFilters = [];
if (data.filters.length > 0) {
data.filters.forEach(f => {
if (f.field && f.value) {
if (f.field.startsWith('@') && !f.field.startsWith('act.data')) {
const _q = {};
_q[f.field] = f.value;
search_body.query.bool.must.push({'term': _q});
} else {
onDemandFilters.push(f);
}
}
});
}
const responseQueue = [];
let counter = 0;
const responseQueue = [];
let counter = 0;
const init_response = await fastify.elastic.search({
index: fastify.manager.chain + '-action-*',
scroll: '30s',
size: 20,
body: search_body,
});
responseQueue.push(init_response);
while (responseQueue.length) {
const {body} = responseQueue.shift();
const enqueuedMessages = [];
counter += body['hits']['hits'].length;
for (const doc of body['hits']['hits']) {
let allow = false;
if (onDemandFilters.length > 0) {
allow = onDemandFilters.every(filter => {
return checkFilter(filter, doc._source);
});
} else {
allow = true;
}
if (allow) {
enqueuedMessages.push(doc._source);
}
}
if (socket.connected) {
socket.emit('message', {type: 'action_trace', mode: 'history', messages: enqueuedMessages});
} else {
console.log('LOST CLIENT');
break;
}
if (body['hits'].total.value === counter) {
console.log(`${counter} past actions streamed to ${socket.id}`);
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 {
console.log('Request too large!');
socket.emit('message', {type: 'action_trace', mode: 'history', messages: []});
}
}
const init_response = await fastify.elastic.search({
index: fastify.manager.chain + '-action-*',
scroll: '30s',
size: 20,
body: search_body,
});
responseQueue.push(init_response);
while (responseQueue.length) {
const {body} = responseQueue.shift();
const enqueuedMessages = [];
counter += body['hits']['hits'].length;
for (const doc of body['hits']['hits']) {
let allow = false;
if (onDemandFilters.length > 0) {
allow = onDemandFilters.every(filter => {
return checkFilter(filter, doc._source);
});
} else {
allow = true;
}
if (allow) {
enqueuedMessages.push(doc._source);
}
}
if (socket.connected) {
socket.emit('message', {type: 'action_trace', mode: 'history', messages: enqueuedMessages});
} else {
hLog('LOST CLIENT');
break;
}
if (body['hits'].total.value === counter) {
hLog(`${counter} past actions streamed to ${socket.id}`);
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: []});
}
}
}
export class SocketManager {
private io;
private relay;
relay_restored = true;
relay_down = false;
private readonly url;
private readonly server: FastifyInstance;
private io: Server;
private relay;
relay_restored = true;
relay_down = false;
private readonly url;
private readonly server: FastifyInstance;
constructor(fastify: FastifyInstance, url, redisOpts) {
this.server = fastify;
this.url = url;
constructor(fastify: FastifyInstance, url, redisOptions) {
this.server = fastify;
this.url = url;
this.io = sockets(fastify.server, {
transports: ['websocket', 'polling'],
});
this.io = new Server(fastify.server, {
allowEIO3: true,
transports: ['websocket', 'polling'],
});
this.io.on('connection', (socket) => {
const pubClient = new IORedis(redisOptions);
const subClient = pubClient.duplicate();
this.io.adapter(createAdapter({pubClient, subClient}));
if (socket.handshake.headers['x-forwarded-for']) {
console.log(`[socket] ${socket.id} connected via ${socket.handshake.headers['x-forwarded-for']}`);
}
this.io.on('connection', (socket: Socket) => {
socket.emit('message', {
event: 'handshake',
chain: fastify.manager.chain,
});
if (socket.handshake.headers['x-forwarded-for']) {
hLog(`[socket] ${socket.id} connected via ${socket.handshake.headers['x-forwarded-for']}`);
}
if (this.relay) {
this.relay.emit('event', {
type: 'client_count',
counter: Object.keys(this.io.sockets.connected).length,
});
}
socket.emit('message', {
event: 'handshake',
chain: fastify.manager.chain,
});
socket.on('delta_stream_request', async (data: StreamDeltasRequest, callback) => {
if (typeof callback === 'function' && data) {
try {
if (data.start_from) {
await streamPastDeltas(this.server, socket, data);
}
this.emitToRelay(data, 'delta_request', socket, callback);
} catch (e) {
console.log(e);
}
}
});
if (this.relay) {
this.relay.emit('event', {
type: 'client_count',
counter: this.io.sockets.sockets.size,
});
}
socket.on('action_stream_request', async (data: StreamActionsRequest, callback) => {
if (typeof callback === 'function' && data) {
try {
if (data.start_from) {
await streamPastActions(this.server, socket, data);
}
this.emitToRelay(data, 'action_request', socket, callback);
} catch (e) {
console.log(e);
}
}
});
socket.on('delta_stream_request', async (data: StreamDeltasRequest, callback) => {
if (typeof callback === 'function' && data) {
try {
if (data.start_from) {
await streamPastDeltas(this.server, socket, data);
}
this.emitToRelay(data, 'delta_request', socket, callback);
} catch (e) {
console.log(e);
}
}
});
socket.on('disconnect', (reason) => {
console.log(`[socket] ${socket.id} disconnected - ${reason}`);
this.relay.emit('event', {
type: 'client_disconnected',
id: socket.id,
reason,
});
});
});
console.log('Websocket manager loaded!');
}
socket.on('action_stream_request', async (data: StreamActionsRequest, callback) => {
if (typeof callback === 'function' && data) {
try {
if (data.start_from) {
await streamPastActions(this.server, socket, data);
}
this.emitToRelay(data, 'action_request', socket, callback);
} catch (e) {
console.log(e);
}
}
});
startRelay() {
console.log(`starting relay - ${this.url}`);
socket.on('disconnect', (reason) => {
hLog(`[socket] ${socket.id} disconnected - ${reason}`);
this.relay.emit('event', {
type: 'client_disconnected',
id: socket.id,
reason,
});
});
});
hLog('Websocket manager loaded!');
}
this.relay = IOClient(this.url, {path: '/router'});
startRelay() {
hLog(`starting relay - ${this.url}`);
this.relay.on('connect', () => {
console.log('Relay Connected!');
if (this.relay_down) {
this.relay_restored = true;
this.relay_down = false;
this.io.emit('status', 'relay_restored');
}
});
this.relay = io(this.url, {path: '/router'});
this.relay.on('disconnect', () => {
console.log('Relay disconnected!');
this.io.emit('status', 'relay_down');
this.relay_down = true;
this.relay_restored = false;
});
this.relay.on('connect', () => {
hLog('Relay Connected!');
if (this.relay_down) {
this.relay_restored = true;
this.relay_down = false;
this.io.emit('status', 'relay_restored');
}
});
this.relay.on('delta', (traceData) => {
this.emitToClient(traceData, 'delta_trace');
});
this.relay.on('disconnect', () => {
hLog('Relay disconnected!');
this.io.emit('status', 'relay_down');
this.relay_down = true;
this.relay_restored = false;
});
this.relay.on('trace', (traceData) => {
this.emitToClient(traceData, 'action_trace');
});
this.relay.on('delta', (traceData) => {
this.emitToClient(traceData, 'delta_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('trace', (traceData) => {
this.emitToClient(traceData, 'action_trace');
});
// Relay LIB info to clients;
this.relay.on('fork_event', (data) => {
console.log(data);
if (this.server.manager.conn.chains[this.server.manager.chain].chain_id === data.chain_id) {
this.io.emit('fork_event', data);
}
});
}
// 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);
}
});
emitToClient(traceData, type) {
if (this.io.sockets.connected[traceData.client]) {
this.io.sockets.connected[traceData.client].emit('message', {
type: type,
mode: 'live',
message: traceData.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);
}
});
}
emitToRelay(data, type, socket, callback) {
if (this.relay.connected) {
this.relay.emit('event', {
type: type,
client_socket: socket.id,
request: data,
}, (response) => {
callback(response);
});
} else {
callback('STREAMING_OFFLINE');
}
}
emitToClient(traceData, type) {
if (this.io.sockets.sockets.has(traceData.client)) {
this.io.sockets.sockets.get(traceData.client).emit('message', {
type: type,
mode: 'live',
message: traceData.message,
});
}
}
emitToRelay(data, type, socket, callback) {
if (this.relay.connected) {
this.relay.emit('event', {
type: type,
client_socket: socket.id,
request: data,
}, (response) => {
callback(response);
});
} else {
callback('STREAMING_OFFLINE');
}
}
}
+58 -34
View File
@@ -1,5 +1,8 @@
{
"api": {
"enabled": true,
"pm2_scaling": 1,
"node_max_old_space_size": 1024,
"chain_name": "EXAMPLE Chain",
"server_addr": "127.0.0.1",
"server_port": 7000,
@@ -19,17 +22,48 @@
"get_trx_actions": 200
},
"access_log": false,
"enable_explorer": false,
"chain_api_error_log": false,
"custom_core_token": "",
"enable_export_action": false
"enable_export_action": false,
"disable_rate_limit": false,
"rate_limit_rpm": 1000,
"rate_limit_allow": [],
"disable_tx_cache": false,
"tx_cache_expiration_sec": 3600,
"v1_chain_cache": [
{
"path": "get_block",
"ttl": 3000
},
{
"path": "get_info",
"ttl": 500
}
]
},
"indexer": {
"enabled": true,
"node_max_old_space_size": 4096,
"start_on": 0,
"stop_on": 0,
"rewrite": false,
"purge_queues": false,
"live_reader": false,
"live_only_mode": false,
"abi_scan_mode": true,
"fetch_block": true,
"fetch_traces": true,
"disable_reading": false,
"disable_indexing": false,
"process_deltas": true,
"disable_delta_rm": true
},
"settings": {
"preview": false,
"chain": "eos",
"eosio_alias": "eosio",
"parser": "1.8",
"auto_stop": 300,
"parser": "2.1",
"auto_stop": 0,
"index_version": "v1",
"debug": false,
"bp_logs": false,
@@ -37,12 +71,14 @@
"ipc_debug_rate": 60000,
"allow_custom_abi": false,
"rate_monitoring": true,
"max_ws_payload_kb": 256,
"max_ws_payload_mb": 256,
"ds_profiling": false,
"auto_mode_switch": false,
"hot_warm_policy": false,
"custom_policy": "",
"bypass_index_map": false
"bypass_index_map": false,
"index_partition_size": 10000000,
"es_replicas": 0
},
"blacklists": {
"actions": [],
@@ -55,34 +91,21 @@
"root_only": false
},
"scaling": {
"readers": 1,
"ds_queues": 1,
"ds_threads": 1,
"ds_pool_size": 1,
"indexing_queues": 1,
"ad_idx_queues": 1,
"max_autoscale": 4,
"batch_size": 5000,
"resume_trigger": 5000,
"readers": 1,
"ds_queues": 1,
"ds_threads": 1,
"ds_pool_size": 1,
"indexing_queues": 1,
"ad_idx_queues": 1,
"dyn_idx_queues": 1,
"max_autoscale": 4,
"batch_size": 5000,
"resume_trigger": 5000,
"auto_scale_trigger": 20000,
"block_queue_limit": 10000,
"max_queue_limit": 100000,
"routing_mode": "heatmap",
"polling_interval": 10000
},
"indexer": {
"start_on": 0,
"stop_on": 0,
"rewrite": false,
"purge_queues": false,
"live_reader": false,
"live_only_mode": false,
"abi_scan_mode": true,
"fetch_block": true,
"fetch_traces": true,
"disable_reading": false,
"disable_indexing": false,
"process_deltas": true
"block_queue_limit": 10000,
"max_queue_limit": 100000,
"routing_mode": "round_robin",
"polling_interval": 10000
},
"features": {
"streaming": {
@@ -107,5 +130,6 @@
"read": 50,
"block": 100,
"index": 500
}
},
"plugins": {}
}
-10
View File
File diff suppressed because one or more lines are too long
+55
View File
@@ -0,0 +1,55 @@
{
"amqp": {
"host": "127.0.0.1:5672",
"api": "127.0.0.1:15672",
"user": "username",
"pass": "password",
"vhost": "hyperion"
},
"elasticsearch": {
"host": "127.0.0.1:9200",
"ingest_nodes": [
"127.0.0.1:9200"
],
"user": "elastic",
"pass": "password"
},
"redis": {
"host": "127.0.0.1",
"port": "6379"
},
"chains": {
"eos": {
"name": "EOS Mainnet",
"chain_id": "aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906",
"http": "http://127.0.0.1:8888",
"ship": "ws://127.0.0.1:8080",
"WS_ROUTER_PORT": 7001,
"WS_ROUTER_HOST": "127.0.0.1"
},
"eos2": {
"name": "EOS Mainnet",
"chain_id": "aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906",
"http": "http://127.0.0.1:8888",
"ship": "ws://127.0.0.1:8080",
"WS_ROUTER_PORT": 7001,
"WS_ROUTER_HOST": "127.0.0.1"
},
"demo": {
"name": "Demo Chain",
"chain_id": "aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906",
"http": "http://api.eosrio.io",
"ship": "ws://200.201.191.228:58013",
"WS_ROUTER_PORT": 57201,
"WS_ROUTER_HOST": "192.168.0.1"
},
"demo2": {
"name": "Demo Chain",
"chain_id": "aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906",
"http": "http://api.eosrio.io",
"ship": "ws://200.201.191.228:58013",
"WS_ROUTER_PORT": 57201,
"WS_ROUTER_HOST": "192.168.0.1"
}
}
}
+17 -11
View File
@@ -9,18 +9,22 @@ export async function createConnection(config): Promise<Connection> {
debugLog("[AMQP] connection established");
return conn;
} catch (e) {
console.log("[AMQP] failed to connect!");
console.log(e.message);
hLog("[AMQP] failed to connect!");
hLog(e.message);
await new Promise(resolve => setTimeout(resolve, 5000));
return await createConnection(config);
}
}
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}`;
return `amqp://${u}:${p}@${config.host}/${v}?frameMax=${frameMaxValue}`;
}
@@ -30,8 +34,8 @@ async function createChannels(connection) {
const confirmChannel = await connection.createConfirmChannel();
return [channel, confirmChannel];
} catch (e) {
console.log("[AMQP] failed to create channels");
console.error(e);
hLog("[AMQP] failed to create channels");
hLog(e);
return null;
}
}
@@ -66,7 +70,7 @@ export async function amqpConnect(onReconnect, config, onClose) {
export async function checkQueueSize(q_name, config) {
try {
const v = encodeURIComponent(config.vhost);
const apiUrl = `http://${config.api}/api/queues/${v}/${encodeURIComponent(q_name)}`;
const apiUrl = `${config.protocol}://${config.api}/api/queues/${v}/${encodeURIComponent(q_name)}`;
const opts = {
username: config.user,
password: config.pass
@@ -74,11 +78,13 @@ export async function checkQueueSize(q_name, config) {
const data = await got.get(apiUrl, opts).json() as any;
return data.messages;
} catch (e) {
hLog('[WARNING] Checking queue size failed, HTTP API is not ready!');
if (e instanceof HTTPError) {
hLog(e.response);
} else {
hLog(JSON.stringify(e, null, 2));
hLog(`[WARNING] Checking queue size failed! - ${e.message}`);
if (e.response && e.response.body) {
if (e instanceof HTTPError) {
hLog(e.response.body);
} else {
hLog(JSON.stringify(e.response.body, null, 2));
}
}
return 0;
}
+149 -135
View File
@@ -1,164 +1,178 @@
import {ConfigurationModule} from "../modules/config";
import {JsonRpc} from "eosjs/dist";
import {JsonRpc} from "eosjs";
import got from "got";
import {Client} from '@elastic/elasticsearch'
import {HyperionConnections} from "../interfaces/hyperionConnections";
import {HyperionConfig} from "../interfaces/hyperionConfig";
import {amqpConnect, checkQueueSize, getAmpqUrl} from "./amqp";
import {StateHistorySocket} from "./state-history";
import fetch from 'node-fetch';
import fetch from 'cross-fetch';
import {exec} from "child_process";
import {hLog} from "../helpers/common_functions";
export class ConnectionManager {
config: HyperionConfig;
conn: HyperionConnections;
config: HyperionConfig;
conn: HyperionConnections;
chain: string;
last_commit_hash: string;
current_version: string;
chain: string;
last_commit_hash: string;
current_version: string;
private esIngestClients: Client[];
private esIngestClient: Client;
private readonly esIngestClients: Client[];
private esIngestClient: Client;
constructor(private cm: ConfigurationModule) {
this.config = cm.config;
this.conn = cm.connections;
this.chain = this.config.settings.chain;
this.esIngestClients = [];
this.prepareESClient();
this.prepareIngestClients();
}
constructor(private cm: ConfigurationModule) {
this.config = cm.config;
this.conn = cm.connections;
if (!this.conn.amqp.protocol) {
this.conn.amqp.protocol = 'http';
}
this.chain = this.config.settings.chain;
this.esIngestClients = [];
this.prepareESClient();
this.prepareIngestClients();
}
get nodeosJsonRPC() {
// @ts-ignore
return new JsonRpc(this.conn.chains[this.chain].http, {fetch});
}
get nodeosJsonRPC() {
// @ts-ignore
return new JsonRpc(this.conn.chains[this.chain].http, {fetch});
}
async purgeQueues() {
hLog(`Purging all ${this.chain} queues!`);
const apiUrl = `http://${this.conn.amqp.api}`;
const getAllQueuesFromVHost = apiUrl + `/api/queues/%2F${this.conn.amqp.vhost}`;
const opts = {
username: this.conn.amqp.user,
password: this.conn.amqp.pass
};
let result;
try {
const data = await got(getAllQueuesFromVHost, opts);
if (data) {
result = JSON.parse(data.body);
}
} catch (e) {
console.log(e.message);
console.error('failed to connect to rabbitmq http api');
process.exit(1);
}
if (result) {
for (const queue of result) {
if (queue.name.startsWith(this.chain + ":")) {
const msg_count = parseInt(queue.messages);
if (msg_count > 0) {
try {
await got.delete(apiUrl + `/api/queues/%2F${this.conn.amqp.vhost}/${queue.name}/contents`, opts);
hLog(`${queue.messages} messages deleted on queue ${queue.name}`);
} catch (e) {
console.log(e.message);
console.error('failed to connect to rabbitmq http api');
process.exit(1);
}
}
}
}
}
}
async purgeQueues() {
hLog(`Purging all ${this.chain} queues!`);
const apiUrl = `http://${this.conn.amqp.api}`;
const vHost = encodeURIComponent(this.conn.amqp.vhost);
const getAllQueuesFromVHost = apiUrl + `/api/queues/${vHost}`;
const opts = {
username: this.conn.amqp.user,
password: this.conn.amqp.pass
};
let result;
try {
const data = await got(getAllQueuesFromVHost, opts);
if (data) {
result = JSON.parse(data.body);
}
} catch (e) {
console.log(e.message);
console.error('failed to connect to rabbitmq http api');
process.exit(1);
}
if (result) {
for (const queue of result) {
if (queue.name.startsWith(this.chain + ":")) {
const msg_count = parseInt(queue.messages);
if (msg_count > 0) {
try {
await got.delete(apiUrl + `/api/queues/${vHost}/${queue.name}/contents`, opts);
hLog(`${queue.messages} messages deleted on queue ${queue.name}`);
} catch (e) {
console.log(e.message);
console.error('failed to connect to rabbitmq http api');
process.exit(1);
}
}
}
}
}
}
prepareESClient() {
let es_url;
const _es = this.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}`
}
this.esIngestClient = new Client({
node: es_url,
ssl: {
rejectUnauthorized: false
}
});
}
prepareESClient() {
let es_url;
const _es = this.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}`
}
this.esIngestClient = new Client({
node: es_url,
ssl: {
rejectUnauthorized: false
}
});
}
get elasticsearchClient() {
return this.esIngestClient;
}
get elasticsearchClient() {
return this.esIngestClient;
}
prepareIngestClients() {
const _es = this.conn.elasticsearch;
if (!_es.protocol) {
_es.protocol = 'http';
}
if (_es.ingest_nodes) {
if (_es.ingest_nodes.length > 0) {
for (const node of _es.ingest_nodes) {
let es_url;
if (_es.user !== '') {
es_url = `${_es.protocol}://${_es.user}:${_es.pass}@${node}`;
} else {
es_url = `${_es.protocol}://${node}`
}
this.esIngestClients.push(new Client({
node: es_url,
pingTimeout: 100,
ssl: {
rejectUnauthorized: false
}
}));
}
}
}
}
prepareIngestClients() {
const _es = this.conn.elasticsearch;
if (!_es.protocol) {
_es.protocol = 'http';
}
if (_es.ingest_nodes) {
if (_es.ingest_nodes.length > 0) {
for (const node of _es.ingest_nodes) {
let es_url;
if (_es.user !== '') {
es_url = `${_es.protocol}://${_es.user}:${_es.pass}@${node}`;
} else {
es_url = `${_es.protocol}://${node}`
}
this.esIngestClients.push(new Client({
node: es_url,
pingTimeout: 100,
ssl: {
rejectUnauthorized: false
}
}));
}
}
}
}
get ingestClients() {
if (this.esIngestClients.length > 0) {
return this.esIngestClients;
} else {
return [this.esIngestClient];
}
}
get ingestClients() {
if (this.esIngestClients.length > 0) {
return this.esIngestClients;
} else {
return [this.esIngestClient];
}
}
async createAMQPChannels(onReconnect, onClose) {
return await amqpConnect(onReconnect, this.conn.amqp, onClose);
}
async createAMQPChannels(onReconnect, onClose) {
return await amqpConnect(onReconnect, this.conn.amqp, onClose);
}
async checkQueueSize(queue) {
return await checkQueueSize(queue, this.conn.amqp);
}
async checkQueueSize(queue) {
return await checkQueueSize(queue, this.conn.amqp);
}
get shipClient(): StateHistorySocket {
return new StateHistorySocket(this.conn.chains[this.config.settings.chain]['ship'], this.config.settings.max_ws_payload_kb);
}
get shipClient(): StateHistorySocket {
return new StateHistorySocket(this.conn.chains[this.config.settings.chain]['ship'], this.config.settings.max_ws_payload_mb);
}
get ampqUrl() {
return getAmpqUrl(this.conn.amqp);
}
get ampqUrl() {
return getAmpqUrl(this.conn.amqp);
}
calculateServerHash() {
exec('git rev-parse HEAD', (err, stdout) => {
console.log('Last commit hash on this branch is:', stdout);
this.last_commit_hash = stdout.trim();
});
}
calculateServerHash() {
exec('git rev-parse HEAD', (err, stdout) => {
if (err) {
// hLog(`\n ${err.message}\n >>> Failed to check last commit hash. Version hash will be "custom"`);
hLog(`Failed to check last commit hash. Version hash will be "custom"`);
this.last_commit_hash = 'custom';
} else {
hLog('Last commit hash on this branch is:', stdout);
this.last_commit_hash = stdout.trim();
}
this.getHyperionVersion();
});
}
getServerHash() {
return this.last_commit_hash;
}
getServerHash() {
return this.last_commit_hash;
}
getHyperionVersion() {
this.current_version = require('../package.json').version
}
getHyperionVersion() {
this.current_version = require('../package.json').version;
if (this.last_commit_hash === 'custom') {
this.current_version = this.current_version + '-dirty';
}
}
}
+40 -39
View File
@@ -1,48 +1,49 @@
import {debugLog, hLog} from "../helpers/common_functions";
const WebSocket = require('ws');
import WebSocket from 'ws';
export class StateHistorySocket {
private ws;
private readonly shipUrl;
private readonly max_payload_kb;
private ws;
private readonly shipUrl;
private readonly max_payload_mb;
constructor(ship_url, max_payload_kb) {
this.shipUrl = ship_url;
if (max_payload_kb) {
this.max_payload_kb = max_payload_kb;
} else {
this.max_payload_kb = 256;
}
}
constructor(ship_url, max_payload_mb) {
this.shipUrl = ship_url;
if (max_payload_mb) {
this.max_payload_mb = max_payload_mb;
} else {
this.max_payload_mb = 256;
}
}
connect(onMessage, onDisconnect, onError, onConnected) {
debugLog(`Connecting to ${this.shipUrl}...`);
this.ws = new WebSocket(this.shipUrl, null, {
perMessageDeflate: false,
maxPayload: this.max_payload_kb * 1024 * 1024,
});
this.ws.on('open', () => {
hLog('Websocket connected!');
if (onConnected) {
onConnected();
}
});
this.ws.on('message', onMessage);
this.ws.on('close', () => {
hLog('Websocket disconnected!');
onDisconnect();
});
this.ws.on('error', (err) => {
hLog(`${this.shipUrl} :: ${err.message}`);
});
}
connect(onMessage, onDisconnect, onError, onConnected) {
close() {
this.ws.close();
}
debugLog(`Connecting to ${this.shipUrl}...`);
this.ws = new WebSocket(this.shipUrl, {
perMessageDeflate: false,
maxPayload: this.max_payload_mb * 1024 * 1024,
});
this.ws.on('open', () => {
hLog('Websocket connected!');
if (onConnected) {
onConnected();
}
});
this.ws.on('message', (data) => onMessage(data));
this.ws.on('close', () => {
hLog('Websocket disconnected!');
onDisconnect();
});
this.ws.on('error', (err) => {
hLog(`${this.shipUrl} :: ${err.message}`);
});
}
send(payload) {
this.ws.send(payload);
}
close() {
this.ws.close();
}
send(payload) {
this.ws.send(payload);
}
}
+232 -228
View File
@@ -1,260 +1,264 @@
const AbiDefinitions = {
version: "eosio::abi/1.1",
structs: [
version: 'eosio::abi/1.1',
structs: [
{
name: 'extensions_entry',
base: '',
fields: [
{
name: "extensions_entry",
base: "",
fields: [
{
name: "tag",
type: "uint16"
},
{
name: "value",
type: "bytes"
}
]
name: 'tag',
type: 'uint16',
},
{
name: "type_def",
base: "",
fields: [
{
name: "new_type_name",
type: "string"
},
{
name: "type",
type: "string"
}
]
name: 'value',
type: 'bytes',
},
],
},
{
name: 'type_def',
base: '',
fields: [
{
name: 'new_type_name',
type: 'string',
},
{
name: "field_def",
base: "",
fields: [
{
name: "name",
type: "string"
},
{
name: "type",
type: "string"
}
]
name: 'type',
type: 'string',
},
],
},
{
name: 'field_def',
base: '',
fields: [
{
name: 'name',
type: 'string',
},
{
name: "struct_def",
base: "",
fields: [
{
name: "name",
type: "string"
},
{
name: "base",
type: "string"
},
{
name: "fields",
type: "field_def[]"
}
]
name: 'type',
type: 'string',
},
],
},
{
name: 'struct_def',
base: '',
fields: [
{
name: 'name',
type: 'string',
},
{
name: "action_def",
base: "",
fields: [
{
name: "name",
type: "name"
},
{
name: "type",
type: "string"
},
{
name: "ricardian_contract",
type: "string"
}
]
name: 'base',
type: 'string',
},
{
name: "table_def",
base: "",
fields: [
{
name: "name",
type: "name"
},
{
name: "index_type",
type: "string"
},
{
name: "key_names",
type: "string[]"
},
{
name: "key_types",
type: "string[]"
},
{
name: "type",
type: "string"
}
]
name: 'fields',
type: 'field_def[]',
},
],
},
{
name: 'action_def',
base: '',
fields: [
{
name: 'name',
type: 'name',
},
{
name: "clause_pair",
base: "",
fields: [
{
name: "id",
type: "string"
},
{
name: "body",
type: "string"
}
]
name: 'type',
type: 'string',
},
{
name: "error_message",
base: "",
fields: [
{
name: "error_code",
type: "uint64"
},
{
name: "error_msg",
type: "string"
}
]
name: 'ricardian_contract',
type: 'string',
},
],
},
{
name: 'table_def',
base: '',
fields: [
{
name: 'name',
type: 'name',
},
{
name: "variant_def",
base: "",
fields: [
{
name: "name",
type: "string"
},
{
name: "types",
type: "string[]"
}
]
name: 'index_type',
type: 'string',
},
{
name: "abi_def",
base: "",
fields: [
{
name: "version",
type: "string"
},
{
name: "types",
type: "type_def[]"
},
{
name: "structs",
type: "struct_def[]"
},
{
name: "actions",
type: "action_def[]"
},
{
name: "tables",
type: "table_def[]"
},
{
name: "ricardian_clauses",
type: "clause_pair[]"
},
{
name: "error_messages",
type: "error_message[]"
},
{
name: "abi_extensions",
type: "extensions_entry[]"
},
{
name: "variants",
type: "variant_def[]$"
}
]
}
]
name: 'key_names',
type: 'string[]',
},
{
name: 'key_types',
type: 'string[]',
},
{
name: 'type',
type: 'string',
},
],
},
{
name: 'clause_pair',
base: '',
fields: [
{
name: 'id',
type: 'string',
},
{
name: 'body',
type: 'string',
},
],
},
{
name: 'error_message',
base: '',
fields: [
{
name: 'error_code',
type: 'uint64',
},
{
name: 'error_msg',
type: 'string',
},
],
},
{
name: 'variant_def',
base: '',
fields: [
{
name: 'name',
type: 'string',
},
{
name: 'types',
type: 'string[]',
},
],
},
{
name: 'abi_def',
base: '',
fields: [
{
name: 'version',
type: 'string',
},
{
name: 'types',
type: 'type_def[]',
},
{
name: 'structs',
type: 'struct_def[]',
},
{
name: 'actions',
type: 'action_def[]',
},
{
name: 'tables',
type: 'table_def[]',
},
{
name: 'ricardian_clauses',
type: 'clause_pair[]',
},
{
name: 'error_messages',
type: 'error_message[]',
},
{
name: 'abi_extensions',
type: 'extensions_entry[]',
},
{
name: 'variants',
type: 'variant_def[]$',
},
],
},
],
};
const RexAbi = {
version: "eosio::abi/1.1",
types: [],
structs: [
version: 'eosio::abi/1.1',
types: [],
structs: [
{
name: 'buyresult',
base: '',
fields: [
{
name: "buyresult",
base: "",
fields: [{
name: "rex_received",
type: "asset"
}
]
name: 'rex_received',
type: 'asset',
},
],
}, {
name: 'orderresult',
base: '',
fields: [
{
name: 'owner',
type: 'name',
}, {
name: "orderresult",
base: "",
fields: [{
name: "owner",
type: "name"
}, {
name: "proceeds",
type: "asset"
}
]
}, {
name: "rentresult",
base: "",
fields: [{
name: "rented_tokens",
type: "asset"
}
]
name: 'proceeds',
type: 'asset',
},
],
}, {
name: 'rentresult',
base: '',
fields: [
{
name: "sellresult",
base: "",
fields: [{
name: "proceeds",
type: "asset"
}]
}
],
actions: [
{
name: "buyresult",
type: "buyresult",
ricardian_contract: ""
name: 'rented_tokens',
type: 'asset',
},
],
},
{
name: 'sellresult',
base: '',
fields: [
{
name: "orderresult",
type: "orderresult",
ricardian_contract: ""
},
{
name: "rentresult",
type: "rentresult",
ricardian_contract: ""
},
{
name: "sellresult",
type: "sellresult",
ricardian_contract: ""
}
]
name: 'proceeds',
type: 'asset',
}],
},
],
actions: [
{
name: 'buyresult',
type: 'buyresult',
ricardian_contract: '',
},
{
name: 'orderresult',
type: 'orderresult',
ricardian_contract: '',
},
{
name: 'rentresult',
type: 'rentresult',
ricardian_contract: '',
},
{
name: 'sellresult',
type: 'sellresult',
ricardian_contract: '',
},
],
};
module.exports = {AbiDefinitions, RexAbi};
+46 -32
View File
@@ -1,37 +1,51 @@
function addIndexer(chainName) {
return {
script: './launcher.js',
name: chainName + '-indexer',
namespace: chainName,
interpreter: 'node',
interpreter_args: ['--max-old-space-size=4096', '--trace-deprecation'],
autorestart: false,
kill_timeout: 3600,
watch: false,
time: true, // include timestamps in pm2 logs
env: {
CONFIG_JSON: 'chains/' + chainName + '.config.json',
TRACE_LOGS: 'false',
},
};
function interpreterArgs(heap) {
const arr = ['--trace-deprecation', '--trace-warnings'];
if (heap) {
arr.push('--max-old-space-size=' + heap);
} else {
arr.push('--max-old-space-size=2048');
}
if (process.env.INSPECT) {
arr.push('--inspect');
}
return arr;
}
function addApiServer(chainName, threads) {
return {
script: './api/server.js',
name: chainName + '-api',
namespace: chainName,
node_args: ['--trace-deprecation'],
exec_mode: 'cluster',
merge_logs: true,
instances: threads,
autorestart: true,
exp_backoff_restart_delay: 100,
watch: false,
env: {
CONFIG_JSON: 'chains/' + chainName + '.config.json',
},
};
function addIndexer(chainName, heap) {
return {
script: './launcher.js',
name: chainName + '-indexer',
namespace: chainName,
interpreter: 'node',
interpreter_args: interpreterArgs(heap),
autorestart: false,
kill_timeout: 3600,
watch: false,
time: true,
env: {
CONFIG_JSON: 'chains/' + chainName + '.config.json',
TRACE_LOGS: 'false',
},
};
}
function addApiServer(chainName, threads, heap) {
return {
script: './api/server.js',
name: chainName + '-api',
namespace: chainName,
node_args: interpreterArgs(heap),
exec_mode: 'cluster',
merge_logs: true,
instances: threads,
autorestart: true,
exp_backoff_restart_delay: 100,
watch: false,
time: true,
env: {
CONFIG_JSON: 'chains/' + chainName + '.config.json',
},
};
}
module.exports = {addIndexer, addApiServer};
+1
View File
@@ -1,5 +1,6 @@
import {IlmPutLifecycle} from "@elastic/elasticsearch/api/requestParams";
// noinspection JSUnusedGlobalSymbols
export const ILPs: IlmPutLifecycle[] = [
{
policy: "hyperion-rollover",
+431 -428
View File
@@ -1,9 +1,7 @@
import {ConfigurationModule} from "../modules/config";
const shards = 2;
const replicas = 0;
const refresh = "1s";
let defaultLifecyclePolicy = "200G";
export * from './index-lifecycle-policies';
@@ -15,507 +13,512 @@ const compression = "best_compression";
const cm = new ConfigurationModule();
const chain = cm.config.settings.chain;
if (cm.config.settings.hot_warm_policy) {
defaultLifecyclePolicy = "hyperion-rollover";
}
if(cm.config.settings.custom_policy) {
defaultLifecyclePolicy = cm.config.settings.custom_policy;
// update number of replicas if set to larger than 0
let replicas = 0;
if (cm.config.settings.es_replicas) {
replicas = cm.config.settings.es_replicas;
}
const defaultIndexSettings = {
"index": {
"number_of_shards": shards,
"refresh_interval": refresh,
"number_of_replicas": replicas,
"codec": compression
}
"index": {
"number_of_shards": shards,
"refresh_interval": refresh,
"number_of_replicas": replicas,
"codec": compression
}
};
const actionSettings = {
index: {
lifecycle: {
"name": defaultLifecyclePolicy,
"rollover_alias": chain + "-action"
},
codec: compression,
refresh_interval: refresh,
number_of_shards: shards * 2,
number_of_replicas: replicas,
sort: {
field: "global_sequence",
order: "desc"
}
}
index: {
codec: compression,
refresh_interval: refresh,
number_of_shards: shards * 2,
number_of_replicas: replicas,
sort: {
field: "global_sequence",
order: "desc"
}
}
};
// actionSettings.index["lifecycle"] = {
// "name": defaultLifecyclePolicy,
// "rollover_alias": chain + "-action"
// };
if (cm.config.settings.hot_warm_policy) {
actionSettings["routing"] = {"allocation": {"exclude": {"data": "warm"}}};
actionSettings["routing"] = {"allocation": {"exclude": {"data": "warm"}}};
}
export const action = {
order: 0,
index_patterns: [
chain + "-action-*"
],
settings: actionSettings,
mappings: {
properties: {
"@timestamp": {"type": "date"},
"ds_error": {"type": "boolean"},
"global_sequence": {"type": "long"},
"account_ram_deltas.delta": {"type": "integer"},
"account_ram_deltas.account": {"type": "keyword"},
"act.authorization.permission": {"enabled": false},
"act.authorization.actor": {"type": "keyword"},
"act.account": {"type": "keyword"},
"act.name": {"type": "keyword"},
"act.data": {"enabled": false},
"block_num": {"type": "long"},
"action_ordinal": {"type": "long"},
"creator_action_ordinal": {"type": "long"},
"cpu_usage_us": {"type": "integer"},
"net_usage_words": {"type": "integer"},
"code_sequence": {"type": "integer"},
"abi_sequence": {"type": "integer"},
"trx_id": {"type": "keyword"},
"producer": {"type": "keyword"},
"notified": {"type": "keyword"},
"signatures": {"enabled": false},
"inline_count": {"type": "short"},
"max_inline": {"type": "short"},
"inline_filtered": {"type": "boolean"},
"receipts": {
"properties": {
"global_sequence": {"type": "long"},
"recv_sequence": {"type": "long"},
"receiver": {"type": "keyword"},
"auth_sequence": {
"properties": {
"account": {"type": "keyword"},
"sequence": {"type": "long"}
}
}
}
},
order: 0,
index_patterns: [
chain + "-action-*"
],
settings: actionSettings,
mappings: {
properties: {
"@timestamp": {"type": "date"},
"ds_error": {"type": "boolean"},
"global_sequence": {"type": "long"},
"account_ram_deltas.delta": {"type": "integer"},
"account_ram_deltas.account": {"type": "keyword"},
"act.authorization.permission": {"enabled": false},
"act.authorization.actor": {"type": "keyword"},
"act.account": {"type": "keyword"},
"act.name": {"type": "keyword"},
"act.data": {"enabled": false},
"block_num": {"type": "long"},
"block_id": {"type": "keyword"},
"action_ordinal": {"type": "long"},
"creator_action_ordinal": {"type": "long"},
"cpu_usage_us": {"type": "integer"},
"net_usage_words": {"type": "integer"},
"code_sequence": {"type": "integer"},
"abi_sequence": {"type": "integer"},
"trx_id": {"type": "keyword"},
"producer": {"type": "keyword"},
"notified": {"type": "keyword"},
"signatures": {"enabled": false},
"inline_count": {"type": "short"},
"max_inline": {"type": "short"},
"inline_filtered": {"type": "boolean"},
"receipts": {
"properties": {
"global_sequence": {"type": "long"},
"recv_sequence": {"type": "long"},
"receiver": {"type": "keyword"},
"auth_sequence": {
"properties": {
"account": {"type": "keyword"},
"sequence": {"type": "long"}
}
}
}
},
// eosio::newaccount
"@newaccount": {
"properties": {
"active": {"type": "object"},
"owner": {"type": "object"},
"newact": {"type": "keyword"}
}
},
// eosio::newaccount
"@newaccount": {
"properties": {
"active": {"type": "object"},
"owner": {"type": "object"},
"newact": {"type": "keyword"}
}
},
// eosio::updateauth
"@updateauth": {
"properties": {
"permission": {"type": "keyword"},
"parent": {"type": "keyword"},
"auth": {"type": "object"}
}
},
// eosio::updateauth
"@updateauth": {
"properties": {
"permission": {"type": "keyword"},
"parent": {"type": "keyword"},
"auth": {"type": "object"}
}
},
// *::transfer
"@transfer": {
"properties": {
"from": {"type": "keyword"},
"to": {"type": "keyword"},
"amount": {"type": "float"},
"symbol": {"type": "keyword"},
"memo": {"type": "text"}
}
},
// *::transfer
"@transfer": {
"properties": {
"from": {"type": "keyword"},
"to": {"type": "keyword"},
"amount": {"type": "float"},
"symbol": {"type": "keyword"},
"memo": {"type": "text"}
}
},
// eosio::unstaketorex
"@unstaketorex": {
"properties": {
"owner": {"type": "keyword"},
"receiver": {"type": "keyword"},
"amount": {"type": "float"}
}
},
// eosio::unstaketorex
"@unstaketorex": {
"properties": {
"owner": {"type": "keyword"},
"receiver": {"type": "keyword"},
"amount": {"type": "float"}
}
},
// eosio::buyrex
"@buyrex": {
"properties": {
"from": {"type": "keyword"},
"amount": {"type": "float"}
}
},
// eosio::buyrex
"@buyrex": {
"properties": {
"from": {"type": "keyword"},
"amount": {"type": "float"}
}
},
// eosio::buyram
"@buyram": {
"properties": {
"payer": {"type": "keyword"},
"receiver": {"type": "keyword"},
"quant": {"type": "float"}
}
},
// eosio::buyram
"@buyram": {
"properties": {
"payer": {"type": "keyword"},
"receiver": {"type": "keyword"},
"quant": {"type": "float"}
}
},
// eosio::buyrambytes
"@buyrambytes": {
"properties": {
"payer": {"type": "keyword"},
"receiver": {"type": "keyword"},
"bytes": {"type": "long"}
}
},
// eosio::buyrambytes
"@buyrambytes": {
"properties": {
"payer": {"type": "keyword"},
"receiver": {"type": "keyword"},
"bytes": {"type": "long"}
}
},
// eosio::delegatebw
"@delegatebw": {
"properties": {
"from": {"type": "keyword"},
"receiver": {"type": "keyword"},
"stake_cpu_quantity": {"type": "float"},
"stake_net_quantity": {"type": "float"},
"transfer": {"type": "boolean"},
"amount": {"type": "float"}
}
},
// eosio::delegatebw
"@delegatebw": {
"properties": {
"from": {"type": "keyword"},
"receiver": {"type": "keyword"},
"stake_cpu_quantity": {"type": "float"},
"stake_net_quantity": {"type": "float"},
"transfer": {"type": "boolean"},
"amount": {"type": "float"}
}
},
// eosio::undelegatebw
"@undelegatebw": {
"properties": {
"from": {"type": "keyword"},
"receiver": {"type": "keyword"},
"unstake_cpu_quantity": {"type": "float"},
"unstake_net_quantity": {"type": "float"},
"amount": {"type": "float"}
}
}
}
}
// eosio::undelegatebw
"@undelegatebw": {
"properties": {
"from": {"type": "keyword"},
"receiver": {"type": "keyword"},
"unstake_cpu_quantity": {"type": "float"},
"unstake_net_quantity": {"type": "float"},
"amount": {"type": "float"}
}
}
}
}
};
const deltaSettings = {
"index": {
"lifecycle": {
"name": defaultLifecyclePolicy,
"rollover_alias": chain + "-delta"
},
"codec": compression,
"number_of_shards": shards * 2,
"refresh_interval": refresh,
"number_of_replicas": replicas,
"sort.field": ["block_num", "scope", "primary_key"],
"sort.order": ["desc", "asc", "asc"]
}
index: {
codec: compression,
number_of_shards: shards * 2,
refresh_interval: refresh,
number_of_replicas: replicas,
sort: {
field: ["block_num", "scope", "primary_key"],
order: ["desc", "asc", "asc"]
}
}
};
// deltaSettings.index["lifecycle"] = {
// "name": defaultLifecyclePolicy,
// "rollover_alias": chain + "-delta"
// };
if (cm.config.settings.hot_warm_policy) {
deltaSettings["routing"] = {"allocation": {"exclude": {"data": "warm"}}};
deltaSettings["routing"] = {"allocation": {"exclude": {"data": "warm"}}};
}
export const delta = {
"index_patterns": [chain + "-delta-*"],
"settings": deltaSettings,
"mappings": {
"properties": {
"index_patterns": [chain + "-delta-*"],
"settings": deltaSettings,
"mappings": {
"properties": {
// base fields
"@timestamp": {"type": "date"},
"ds_error": {"type": "boolean"},
"block_id": {"type": "keyword"},
"block_num": {"type": "long"},
"deleted_at": {"type": "long"},
"code": {"type": "keyword"},
"scope": {"type": "keyword"},
"table": {"type": "keyword"},
"payer": {"type": "keyword"},
"primary_key": {"type": "keyword"},
"data": {"enabled": false},
"value": {"enabled": false},
// base fields
"@timestamp": {"type": "date"},
"present": {"type": "byte"},
"ds_error": {"type": "boolean"},
"block_id": {"type": "keyword"},
"block_num": {"type": "long"},
"deleted_at": {"type": "long"},
"code": {"type": "keyword"},
"scope": {"type": "keyword"},
"table": {"type": "keyword"},
"payer": {"type": "keyword"},
"primary_key": {"type": "keyword"},
"data": {"enabled": false},
"value": {"enabled": false},
// eosio.msig::approvals
"@approvals.proposal_name": {"type": "keyword"},
"@approvals.provided_approvals": {"type": "object"},
"@approvals.requested_approvals": {"type": "object"},
// eosio.msig::approvals
"@approvals.proposal_name": {"type": "keyword"},
"@approvals.provided_approvals": {"type": "object"},
"@approvals.requested_approvals": {"type": "object"},
// eosio.msig::proposal
"@proposal.proposal_name": {"type": "keyword"},
"@proposal.transaction": {"enabled": false},
// eosio.msig::proposal
"@proposal.proposal_name": {"type": "keyword"},
"@proposal.transaction": {"enabled": false},
// *::accounts
"@accounts.amount": {"type": "float"},
"@accounts.symbol": {"type": "keyword"},
// *::accounts
"@accounts.amount": {"type": "float"},
"@accounts.symbol": {"type": "keyword"},
// eosio::voters
"@voters.is_proxy": {"type": "boolean"},
"@voters.producers": {"type": "keyword"},
"@voters.last_vote_weight": {"type": "double"},
"@voters.proxied_vote_weight": {"type": "double"},
"@voters.staked": {"type": "float"},
"@voters.proxy": {"type": "keyword"},
// eosio::voters
"@voters.is_proxy": {"type": "boolean"},
"@voters.producers": {"type": "keyword"},
"@voters.last_vote_weight": {"type": "double"},
"@voters.proxied_vote_weight": {"type": "double"},
"@voters.staked": {"type": "float"},
"@voters.proxy": {"type": "keyword"},
// eosio::producers
"@producers.total_votes": {"type": "double"},
"@producers.is_active": {"type": "boolean"},
"@producers.unpaid_blocks": {"type": "long"},
// eosio::producers
"@producers.total_votes": {"type": "double"},
"@producers.is_active": {"type": "boolean"},
"@producers.unpaid_blocks": {"type": "long"},
// eosio::global
"@global": {
"properties": {
"last_name_close": {"type": "date"},
"last_pervote_bucket_fill": {"type": "date"},
"last_producer_schedule_update": {"type": "date"},
"perblock_bucket": {"type": "double"},
"pervote_bucket": {"type": "double"},
"total_activated_stake": {"type": "double"},
"total_voteshare_change_rate": {"type": "double"},
"total_unpaid_voteshare": {"type": "double"},
"total_producer_vote_weight": {"type": "double"},
"total_ram_bytes_reserved": {"type": "long"},
"total_ram_stake": {"type": "long"},
"total_unpaid_blocks": {"type": "long"},
}
}
}
}
// eosio::global
"@global": {
"properties": {
"last_name_close": {"type": "date"},
"last_pervote_bucket_fill": {"type": "date"},
"last_producer_schedule_update": {"type": "date"},
"perblock_bucket": {"type": "double"},
"pervote_bucket": {"type": "double"},
"total_activated_stake": {"type": "double"},
"total_voteshare_change_rate": {"type": "double"},
"total_unpaid_voteshare": {"type": "double"},
"total_producer_vote_weight": {"type": "double"},
"total_ram_bytes_reserved": {"type": "long"},
"total_ram_stake": {"type": "long"},
"total_unpaid_blocks": {"type": "long"},
}
}
}
}
};
export const abi = {
"index_patterns": [chain + "-abi-*"],
"settings": defaultIndexSettings,
"mappings": {
"properties": {
"@timestamp": {"type": "date"},
"block": {"type": "long"},
"account": {"type": "keyword"},
"abi": {"enabled": false},
"abi_hex": {"enabled": false},
"actions": {"type": "keyword"},
"tables": {"type": "keyword"}
}
}
"index_patterns": [chain + "-abi-*"],
"settings": defaultIndexSettings,
"mappings": {
"properties": {
"@timestamp": {"type": "date"},
"block": {"type": "long"},
"account": {"type": "keyword"},
"abi": {"enabled": false},
"abi_hex": {"enabled": false},
"actions": {"type": "keyword"},
"tables": {"type": "keyword"}
}
}
};
export const permissionLink = {
"index_patterns": [chain + "-link-*"],
"settings": defaultIndexSettings,
"mappings": {
"properties": {
"block_num": {"type": "long"},
"@timestamp": {"type": "date"},
"present": {"type": "boolean"},
"account": {"type": "keyword"},
"code": {"type": "keyword"},
"action": {"type": "keyword"},
"permission": {"type": "keyword"}
}
}
"index_patterns": [chain + "-link-*"],
"settings": defaultIndexSettings,
"mappings": {
"properties": {
"block_num": {"type": "long"},
"@timestamp": {"type": "date"},
"present": {"type": "byte"},
"account": {"type": "keyword"},
"code": {"type": "keyword"},
"action": {"type": "keyword"},
"permission": {"type": "keyword"}
}
}
};
export const permission = {
"index_patterns": [chain + "-perm-*"],
"settings": defaultIndexSettings,
"mappings": {
"properties": {
"block_num": {"type": "long"},
"present": {"type": "boolean"},
"owner": {"type": "keyword"},
"name": {"type": "keyword"},
"parent": {"type": "keyword"},
"last_updated": {"type": "date"},
"auth": {"type": "object"}
}
}
"index_patterns": [chain + "-perm-*"],
"settings": defaultIndexSettings,
"mappings": {
"properties": {
"block_num": {"type": "long"},
"present": {"type": "byte"},
"owner": {"type": "keyword"},
"name": {"type": "keyword"},
"parent": {"type": "keyword"},
"last_updated": {"type": "date"},
"auth": {"type": "object"}
}
}
};
export const resourceLimits = {
"index_patterns": [chain + "-reslimits-*"],
"settings": defaultIndexSettings,
"mappings": {
"properties": {
"block_num": {"type": "long"},
"@timestamp": {"type": "date"},
"owner": {"type": "keyword"},
"total_weight": {"type": "long"},
"net_weight": {"type": "long"},
"cpu_weight": {"type": "long"},
"ram_bytes": {"type": "long"}
}
}
"index_patterns": [chain + "-reslimits-*"],
"settings": defaultIndexSettings,
"mappings": {
"properties": {
"block_num": {"type": "long"},
"@timestamp": {"type": "date"},
"owner": {"type": "keyword"},
"total_weight": {"type": "long"},
"net_weight": {"type": "long"},
"cpu_weight": {"type": "long"},
"ram_bytes": {"type": "long"}
}
}
};
export const generatedTransaction = {
"index_patterns": [chain + "-gentrx-*"],
"settings": defaultIndexSettings,
"mappings": {
"properties": {
"block_num": {"type": "long"},
"@timestamp": {"type": "date"},
"sender": {"type": "keyword"},
"sender_id": {"type": "keyword"},
"payer": {"type": "keyword"},
"trx_id": {"type": "keyword"},
"actions": {"enabled": false},
"packed_trx": {"enabled": false}
}
}
"index_patterns": [chain + "-gentrx-*"],
"settings": defaultIndexSettings,
"mappings": {
"properties": {
"block_num": {"type": "long"},
"@timestamp": {"type": "date"},
"sender": {"type": "keyword"},
"sender_id": {"type": "keyword"},
"payer": {"type": "keyword"},
"trx_id": {"type": "keyword"},
"actions": {"enabled": false},
"packed_trx": {"enabled": false}
}
}
};
export const failedTransaction = {
"index_patterns": [chain + "-trxerr-*"],
"settings": defaultIndexSettings,
"mappings": {
"properties": {
"block_num": {"type": "long"},
"@timestamp": {"type": "date"},
"status": {"type": "short"}
}
}
"index_patterns": [chain + "-trxerr-*"],
"settings": defaultIndexSettings,
"mappings": {
"properties": {
"block_num": {"type": "long"},
"@timestamp": {"type": "date"},
"status": {"type": "short"}
}
}
};
export const resourceUsage = {
"index_patterns": [chain + "-userres-*"],
"settings": defaultIndexSettings,
"mappings": {
"properties": {
"block_num": {"type": "long"},
"@timestamp": {"type": "date"},
"owner": {"type": "keyword"},
"net_used": {"type": "long"},
"net_total": {"type": "long"},
"net_pct": {"type": "float"},
"cpu_used": {"type": "long"},
"cpu_total": {"type": "long"},
"cpu_pct": {"type": "float"},
"ram": {"type": "long"}
}
}
"index_patterns": [chain + "-userres-*"],
"settings": defaultIndexSettings,
"mappings": {
"properties": {
"block_num": {"type": "long"},
"@timestamp": {"type": "date"},
"owner": {"type": "keyword"},
"net_used": {"type": "long"},
"net_total": {"type": "long"},
"net_pct": {"type": "float"},
"cpu_used": {"type": "long"},
"cpu_total": {"type": "long"},
"cpu_pct": {"type": "float"},
"ram": {"type": "long"}
}
}
};
export const logs = {
"index_patterns": [chain + "-logs-*"],
"settings": defaultIndexSettings
"index_patterns": [chain + "-logs-*"],
"settings": defaultIndexSettings
};
export const block = {
"index_patterns": [chain + "-block-*"],
"settings": {
"index": {
"codec": compression,
"number_of_shards": shards,
"refresh_interval": refresh,
"number_of_replicas": replicas,
"sort.field": "block_num",
"sort.order": "desc"
}
},
"mappings": {
"properties": {
"@timestamp": {"type": "date"},
"block_num": {"type": "long"},
"block_id": {"type": "keyword"},
"prev_id": {"type": "keyword"},
"producer": {"type": "keyword"},
"new_producers.producers.block_signing_key": {"enabled": false},
"new_producers.producers.producer_name": {"type": "keyword"},
"new_producers.version": {"type": "long"},
"schedule_version": {"type": "double"},
"cpu_usage": {"type": "integer"},
"net_usage": {"type": "integer"}
}
}
"index_patterns": [chain + "-block-*"],
"settings": {
"index": {
"codec": compression,
"number_of_shards": shards,
"refresh_interval": refresh,
"number_of_replicas": replicas,
"sort.field": "block_num",
"sort.order": "desc"
}
},
"mappings": {
"properties": {
"@timestamp": {"type": "date"},
"block_num": {"type": "long"},
"block_id": {"type": "keyword"},
"prev_id": {"type": "keyword"},
"producer": {"type": "keyword"},
"new_producers.producers.block_signing_key": {"enabled": false},
"new_producers.producers.producer_name": {"type": "keyword"},
"new_producers.version": {"type": "long"},
"schedule_version": {"type": "double"},
"cpu_usage": {"type": "integer"},
"net_usage": {"type": "integer"}
}
}
};
export const tableProposals = {
"index_patterns": [chain + "-table-proposals-*"],
"settings": {
"index": {
"codec": compression,
"number_of_shards": shards,
"refresh_interval": refresh,
"number_of_replicas": replicas,
"sort.field": "block_num",
"sort.order": "desc"
}
},
"mappings": {
"properties": {
"block_num": {"type": "long"},
"present": {"type": "boolean"},
"proposal_name": {"type": "keyword"},
"requested_approvals": {"type": "object"},
"provided_approvals": {"type": "object"},
"executed": {"type": "boolean"}
}
}
"index_patterns": [chain + "-table-proposals-*"],
"settings": {
"index": {
"codec": compression,
"number_of_shards": shards,
"refresh_interval": refresh,
"number_of_replicas": replicas,
"sort.field": "block_num",
"sort.order": "desc"
}
},
"mappings": {
"properties": {
"block_num": {"type": "long"},
"present": {"type": "byte"},
"proposal_name": {"type": "keyword"},
"requested_approvals": {"type": "object"},
"provided_approvals": {"type": "object"},
"executed": {"type": "boolean"}
}
}
};
export const tableAccounts = {
"index_patterns": [chain + "-table-accounts-*"],
"settings": {
"index": {
"codec": compression,
"number_of_shards": shards,
"refresh_interval": refresh,
"number_of_replicas": replicas,
"sort.field": "amount",
"sort.order": "desc"
}
},
"mappings": {
"properties": {
"block_num": {"type": "long"},
"present": {"type": "boolean"},
"code": {"type": "keyword"},
"scope": {"type": "keyword"},
"amount": {"type": "float"},
"symbol": {"type": "keyword"}
}
}
"index_patterns": [chain + "-table-accounts-*"],
"settings": {
"index": {
"codec": compression,
"number_of_shards": shards,
"refresh_interval": refresh,
"number_of_replicas": replicas,
"sort.field": "amount",
"sort.order": "desc"
}
},
"mappings": {
"properties": {
"block_num": {"type": "long"},
"present": {"type": "byte"},
"code": {"type": "keyword"},
"scope": {"type": "keyword"},
"amount": {"type": "float"},
"symbol": {"type": "keyword"}
}
}
};
// noinspection JSUnusedGlobalSymbols
export const tableDelBand = {
"index_patterns": [chain + "-table-delband-*"],
"settings": {
"index": {
"codec": compression,
"number_of_shards": shards,
"refresh_interval": refresh,
"number_of_replicas": replicas,
"sort.field": "total_weight",
"sort.order": "desc"
}
},
"mappings": {
"properties": {
"block_num": {"type": "long"},
"from": {"type": "keyword"},
"to": {"type": "keyword"},
"total_weight": {"type": "float"},
"net_weight": {"type": "float"},
"cpu_weight": {"type": "float"}
}
}
"index_patterns": [chain + "-table-delband-*"],
"settings": {
"index": {
"codec": compression,
"number_of_shards": shards,
"refresh_interval": refresh,
"number_of_replicas": replicas,
"sort.field": "total_weight",
"sort.order": "desc"
}
},
"mappings": {
"properties": {
"block_num": {"type": "long"},
"from": {"type": "keyword"},
"to": {"type": "keyword"},
"total_weight": {"type": "float"},
"net_weight": {"type": "float"},
"cpu_weight": {"type": "float"}
}
}
};
export const tableVoters = {
"index_patterns": [chain + "-table-voters-*"],
"settings": {
"index": {
"codec": compression,
"number_of_shards": shards,
"refresh_interval": refresh,
"number_of_replicas": replicas,
"sort.field": "last_vote_weight",
"sort.order": "desc"
}
},
"mappings": {
"properties": {
"block_num": {"type": "long"},
"voter": {"type": "keyword"},
"producers": {"type": "keyword"},
"last_vote_weight": {"type": "double"},
"is_proxy": {"type": "boolean"},
"proxied_vote_weight": {"type": "double"},
"staked": {"type": "double"},
"proxy": {"type": "keyword"}
}
}
"index_patterns": [chain + "-table-voters-*"],
"settings": {
"index": {
"codec": compression,
"number_of_shards": shards,
"refresh_interval": refresh,
"number_of_replicas": replicas,
"sort.field": "last_vote_weight",
"sort.order": "desc"
}
},
"mappings": {
"properties": {
"block_num": {"type": "long"},
"voter": {"type": "keyword"},
"producers": {"type": "keyword"},
"last_vote_weight": {"type": "double"},
"is_proxy": {"type": "boolean"},
"proxied_vote_weight": {"type": "double"},
"staked": {"type": "double"},
"proxy": {"type": "keyword"}
}
}
};
@@ -0,0 +1,5 @@
cluster.name: docker-cluster
network.host: 0.0.0.0
bootstrap.memory_lock: true
discovery.type: single-node
node.name: es01
@@ -0,0 +1,2 @@
-Xms8g
-Xmx8g
+5 -11
View File
@@ -1,11 +1,5 @@
FROM ubuntu:18.04
RUN apt-get update && apt-get upgrade -y && apt-get autoremove && apt-get install -y wget netcat
RUN wget -nv https://github.com/eosio/eos/releases/download/v2.0.5/eosio_2.0.5-1-ubuntu-18.04_amd64.deb
RUN apt-get install -y ./eosio_2.0.5-1-ubuntu-18.04_amd64.deb
RUN useradd -m -s /bin/bash eosio
USER eosio
EXPOSE 8080
FROM ubuntu:20.04
LABEL version="2.1.0" description="EOSIO 2.1.0"
RUN apt-get update && apt-get install -y wget
RUN wget -nv https://github.com/EOSIO/eos/releases/download/v2.1.0/eosio_2.1.0-1-ubuntu-20.04_amd64.deb
RUN apt-get install -y ./eosio_2.1.0-1-ubuntu-20.04_amd64.deb && rm -f ./eosio_2.1.0-1-ubuntu-20.04_amd64.deb
+7 -21
View File
@@ -1,28 +1,14 @@
# Enable block production, even if the chain is stale. (eosio::producer_plugin)
### PRODUCER PLUGIN ###
enable-stale-production = true
# ID of producer controlled by this node (e.g. inita; may specify multiple times) (eosio::producer_plugin)
producer-name = eosio
# print contract's output to console (eosio::chain_plugin)
contracts-console = true
# The local IP and port to listen for incoming http connections; set blank to disable. (eosio::http_plugin)
http-server-address = 0.0.0.0:8888
# If set to false, then any incoming "Host" header is considered valid (eosio::http_plugin)
http-validate-host = false
# enable trace history (eosio::state_history_plugin)
### STATE HISTORY PLUGIN ###
plugin = eosio::state_history_plugin
state-history-endpoint = 0.0.0.0:8080
trace-history = true
# enable chain state history (eosio::state_history_plugin)
chain-state-history = true
# the endpoint upon which to listen for incoming connections. Caution: only expose this port to your internal network. (eosio::state_history_plugin)
state-history-endpoint = 0.0.0.0:8080
# Plugin(s) to enable, may be specified multiple times
plugin = eosio::producer_api_plugin
### CHAIN API PLUGIN ###
plugin = eosio::chain_api_plugin
plugin = eosio::state_history_plugin
http-server-address = 0.0.0.0:8888
http-validate-host = false
+5 -14
View File
@@ -1,15 +1,6 @@
FROM ubuntu:18.04
RUN apt-get update && apt-get upgrade -y && apt-get autoremove && apt-get install -y build-essential git curl netcat
RUN curl -sL https://deb.nodesource.com/setup_14.x | bash -
RUN apt-get install -y nodejs && npm install pm2 -g && git clone https://github.com/eosrio/hyperion-history-api.git
RUN useradd -m -s /bin/bash hyperion && chown -R hyperion:hyperion /hyperion-history-api
USER hyperion
FROM node:16.5
RUN npm install pm2 -g
RUN git clone https://github.com/eosrio/hyperion-history-api.git
WORKDIR /hyperion-history-api
RUN npm install
EXPOSE 7001
RUN git checkout dev-3.3
RUN npm install --production
-5
View File
@@ -1,5 +0,0 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore
+4
View File
@@ -0,0 +1,4 @@
default_vhost = hyperion
default_user = hyperion
default_pass = hyp123456
cluster_name = hyp-docker
-111
View File
@@ -1,111 +0,0 @@
version: '3.8'
services:
redis:
container_name: redis
image: redis:5.0.9-alpine
restart: on-failure
networks:
- hyperion
rabbitmq:
container_name: rabbitmq
image: rabbitmq:3.8.3-management
restart: on-failure
environment:
- RABBITMQ_DEFAULT_USER=username
- RABBITMQ_DEFAULT_PASS=password
- RABBITMQ_DEFAULT_VHOST=/hyperion
volumes:
- ./rabbitmq/data:/var/lib/rabbitmq
ports:
- 15672:15672
networks:
- hyperion
elasticsearch:
container_name: elasticsearch
image: docker.elastic.co/elasticsearch/elasticsearch:7.7.1
restart: on-failure
environment:
- discovery.type=single-node
- cluster.name=es-cluster
- node.name=es01
- bootstrap.memory_lock=true
- xpack.security.enabled=true
- "ES_JAVA_OPTS=-Xms2g -Xmx2g"
- ELASTIC_USERNAME=elastic
- ELASTIC_PASSWORD=password
volumes:
- ./elasticsearch/data:/usr/share/elasticsearch/data
ports:
- 9200:9200
networks:
- hyperion
kibana:
container_name: kibana
image: docker.elastic.co/kibana/kibana:7.7.1
restart: on-failure
environment:
- ELASTICSEARCH_HOSTS=http://elasticsearch:9200
- ELASTICSEARCH_USERNAME=elastic
- ELASTICSEARCH_PASSWORD=password
ports:
- 5601:5601
networks:
- hyperion
depends_on:
- elasticsearch
eosio-node:
container_name: eosio-node
image: eosrio/hyperion:eosio-2.0.5
volumes:
- ./eosio/data/:/home/eosio/data/
- ./eosio/config/:/home/eosio/config/
- ./scripts/:/home/eosio/scripts/
ports:
- 8888:8888
networks:
- hyperion
command: bash -c "/home/eosio/scripts/run-nodeos.sh ${SCRIPT:-false} ${SNAPSHOT:-""}"
hyperion-indexer:
container_name: hyperion-indexer
image: eosrio/hyperion:hyperion-3.1.0-beta.3
restart: on-failure
depends_on:
- elasticsearch
- redis
- rabbitmq
- eosio-node
volumes:
- ./hyperion/config/connections.json:/hyperion-history-api/connections.json
- ./hyperion/config/ecosystem.config.js:/hyperion-history-api/ecosystem.config.js
- ./hyperion/config/chains/:/hyperion-history-api/chains/
- ./scripts/:/home/hyperion/scripts/
networks:
- hyperion
command: bash -c "/home/hyperion/scripts/run-hyperion.sh ${SCRIPT:-false} eos-indexer"
hyperion-api:
container_name: hyperion-api
image: eosrio/hyperion:hyperion-3.0
restart: on-failure
ports:
- 7000:7000
depends_on:
- hyperion-indexer
volumes:
- ./hyperion/config/connections.json:/hyperion-history-api/connections.json
- ./hyperion/config/ecosystem.config.js:/hyperion-history-api/ecosystem.config.js
- ./hyperion/config/chains/:/hyperion-history-api/chains/
- ./scripts/:/home/hyperion/scripts/
networks:
- hyperion
command: bash -c "/home/hyperion/scripts/run-hyperion.sh ${SCRIPT:-false} eos-api"
networks:
hyperion:
driver: bridge
+23
View File
@@ -0,0 +1,23 @@
const {addApiServer, addIndexer} = require('./definitions/ecosystem_settings');
const {readdirSync, readFileSync} = require("fs");
const path = require('path');
const apps = [];
const chainsRoot = path.join(path.resolve(), 'chains');
readdirSync(chainsRoot)
.filter(f => f.endsWith('.config.json'))
.forEach(value => {
const configFile = readFileSync(path.join(chainsRoot, value))
const config = JSON.parse(configFile.toString());
const chainName = config.settings.chain;
if (config.api.enabled) {
const apiHeap = config.api.node_max_old_space_size;
apps.push(addApiServer(chainName, config.api.pm2_scaling, apiHeap));
}
if (config.indexer.enabled) {
const indexerHeap = config.indexer.node_max_old_space_size;
apps.push(addIndexer(chainName, indexerHeap));
}
});
module.exports = {apps};
-1596
View File
File diff suppressed because one or more lines are too long
+6 -2
View File
@@ -2,14 +2,18 @@
"amqp": {
"host": "127.0.0.1:5672",
"api": "127.0.0.1:15672",
"protocol": "http",
"user": "username",
"pass": "password",
"vhost": "hyperion"
"vhost": "hyperion",
"frameMax": "0x10000"
},
"elasticsearch": {
"protocol": "http",
"host": "127.0.0.1:9200",
"ingest_nodes": ["127.0.0.1:9200"],
"ingest_nodes": [
"127.0.0.1:9200"
],
"user": "elastic",
"pass": "password"
},
-8
View File
@@ -1,8 +0,0 @@
const {addApiServer, addIndexer} = require("./definitions/ecosystem_settings");
module.exports = {
apps: [
addIndexer('eos'),
addApiServer('eos', 1)
]
};

Some files were not shown because too many files have changed in this diff Show More