merge 3.3.5 to master

This commit is contained in:
Igor Lins e Silva
2022-09-11 03:42:18 -03:00
parent 7cf9f0613c
commit 279f5d9474
273 changed files with 11140 additions and 34351 deletions
+12 -33
View File
@@ -11,33 +11,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 +26,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,9 +74,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/eosio/data
docker/rabbitmq/data
docker/eosio/data
configuration_backups
-6
View File
@@ -1,6 +0,0 @@
#!/usr/bin/env bash
curl -sL https://deb.nodesource.com/setup_13.x | sudo -E bash -
sudo apt-get install -y nodejs
sudo npm install pm2 -g
npm install
-303
View File
@@ -1,303 +0,0 @@
#!/usr/bin/env bash
set -eo pipefail
#terminal colors
COLOR_NC=$(tput sgr0)
export COLOR_NC
COLOR_RED=$(tput setaf 1)
export COLOR_RED
COLOR_GREEN=$(tput setaf 2)
export COLOR_GREEN
COLOR_YELLOW=$(tput setaf 3)
export COLOR_YELLOW
COLOR_BLUE=$(tput setaf 4)
export COLOR_BLUE
#variables
INITIAL_PARAMS="$*"
GLOBAL="y"
RABBIT_USER="hyperion"
RABBIT_PASSWORD="123456"
NODE=false
ELASTIC=false
REDIS=false
RABBIT=false
RAM=0
help_function() {
echo -e "\nAutomated shell script that installs all dependencies and then configure Hyperion."
echo -e "\nUsage: ./install.env <option>\n"
echo -e "Options:"
echo -e " --help ==> Show this menu"
echo -e " --version ==> Show Hyperion current version\n"
exit 0
}
version (){
echo -e "\nv3.0.0\n"
exit 0
}
arg_checker() {
if [ "${INITIAL_PARAMS}" == "--help" ]; then
help_function
elif [ "${INITIAL_PARAMS}" == "--version" ]; then
version
fi
}
check_ram() {
RAM=$(free --giga | awk '/Mem/ {print $2}')
}
# first check if is installed, after check version
check_dependencies() {
echo -e "\n\n${COLOR_BLUE}Checking dependencies...${COLOR_NC}\n\n"
CMD=$(if (dpkg --compare-versions $(dpkg -s nodejs 2>/dev/null | awk '/Version/ {print $2}') ge "5" 2>/dev/null); then echo true; else echo false; fi)
if ("$CMD" = true); then
NODE=true
echo -e "\n\n${COLOR_BLUE}Nodejs compatible version already installed ${COLOR_NC}"
elif ("$CMD" = false); then
echo -e "\n\n${COLOR_RED}Nodejs installed version is < 13. Please, update and try again. ${COLOR_NC}\n\n"
exit 1
else
echo -e "\n\n${COLOR_BLUE}Nodejs not installed ${COLOR_NC}"
fi
CMD=$(if (dpkg --compare-versions $(dpkg -s elasticsearch 2>/dev/null | awk '/Version/ {print $2}') ge "7.6" 2>/dev/null); then echo true; else echo false; fi)
if ("$CMD" = true); then
ELASTIC=true
echo -e "\n${COLOR_BLUE}Elasticsearch compatible version already installed ${COLOR_NC}"
elif ("$CMD" = false); then
echo -e "\n\n${COLOR_RED}Elasticsearch installed version is < 7.6. Please, update and try again. ${COLOR_NC}\n\n"
exit 1
else
echo -e "\n${COLOR_BLUE}Elasticsearch not installed ${COLOR_NC}"
fi
CMD=$(if (dpkg --compare-versions $(dpkg -s redis-server 2>/dev/null | awk '/Version/ {print $2}') ge "5" 2>/dev/null); then echo true; else echo false; fi)
if ("$CMD" = true); then
REDIS=true
echo -e "\n${COLOR_BLUE}Redis compatible version already installed ${COLOR_NC}"
elif ("$CMD" = false); then
echo -e "\n\n${COLOR_RED}Redis installed version is < 5. Please, update and try again. ${COLOR_NC}\n\n"
exit 1
else
echo -e "\n${COLOR_BLUE}Redis not installed ${COLOR_NC}"
fi
CMD=$(if (dpkg --compare-versions $(dpkg -s rabbitmq-server 2>/dev/null | awk '/Version/ {print $2}') ge "3.8" 2>/dev/null); then echo true; else echo false; fi)
if ("$CMD" = true); then
RABBIT=true
echo -e "\n${COLOR_BLUE}RabbitMQ compatible version already installed ${COLOR_NC}"
elif ("$CMD" = false); then
echo -e "\n\n${COLOR_RED}RabbitMQ installed version is < 3.8. Please, update and try again. ${COLOR_NC}\n\n"
exit 1
else
echo -e "\n${COLOR_BLUE}RabbitMQ not installed ${COLOR_NC}"
fi
CMD=$(if (dpkg --compare-versions $(dpkg -s kibana 2>/dev/null | awk '/Version/ {print $2}') ge "7.6" 2>/dev/null); then echo true; else echo false; fi)
if ("$CMD" = true); then
RABBIT=true
echo -e "\n${COLOR_BLUE}Kibana compatible version already installed ${COLOR_NC}"
elif ("$CMD" = false); then
echo -e "\n\n${COLOR_RED}Kibana installed version is < 7.6. Please, update and try again. ${COLOR_NC}\n\n"
exit 1
else
echo -e "\n${COLOR_BLUE}Kibana not installed ${COLOR_NC}\n\n"
fi
}
# Make a directory for global installations
configure_npm() {
mkdir ~/.npm-global
npm config set prefix "$HOME/.npm-global"
# export path for the current session
export PATH=$HOME/.npm-global/bin:$PATH
# make PATH persistent
echo "export PATH=~/.npm-global/bin:$PATH" >>"$HOME/.profile"
# shellcheck source=/dev/null
source ~/.profile
}
install_keys_sources() {
echo -e "\n\n${COLOR_BLUE}Configuring keys and sources...${COLOR_NC}\n\n"
PPA="https://artifacts.elastic.co/packages/7.x/apt stable main"
if ! grep -q "^deb .*$PPA" /etc/apt/sources.list /etc/apt/sources.list.d/*; then
wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo apt-key add -
echo "deb https://artifacts.elastic.co/packages/7.x/apt stable main" | sudo tee -a /etc/apt/sources.list.d/elastic-7.x.list
fi
PPA="http://dl.bintray.com/rabbitmq-erlang/debian bionic erlang"
if ! grep -q "^deb .*$PPA" /etc/apt/sources.list /etc/apt/sources.list.d/*; then
curl -fsSL https://github.com/rabbitmq/signing-keys/releases/download/2.0/rabbitmq-release-signing-key.asc | sudo apt-key add -
echo "deb http://dl.bintray.com/rabbitmq-erlang/debian bionic erlang" | sudo tee /etc/apt/sources.list.d/bintray.rabbitmq.list
curl -s https://packagecloud.io/install/repositories/rabbitmq/rabbitmq-server/script.deb.sh | sudo bash
fi
PPA="https://deb.nodesource.com/node_13.x bionic main"
if ! grep -q "^deb .*$PPA" /etc/apt/sources.list /etc/apt/sources.list.d/*; then
curl -sL "https://deb.nodesource.com/setup_13.x" | sudo -E bash -
fi
sudo apt update -y
}
install_dep() {
echo -e "\n\n${COLOR_BLUE}Installing dependencies...${COLOR_NC}\n\n"
# sudo apt install -y build-essential curl
sudo apt install -y curl
}
install_node() {
echo -e "\n\n${COLOR_BLUE}Installing nodejs...${COLOR_NC}\n\n"
sudo apt install -y nodejs
read -p "Do you want to create a directory for npm global installations [Y/n] : " GLOBAL
GLOBAL=${GLOBAL:-y}
if [ "$GLOBAL" = "y" ]; then
configure_npm
fi
}
install_build_hyperion() {
echo -e "\n\n${COLOR_BLUE}Installing packages and building hyperion...${COLOR_NC}\n\n"
npm install
}
install_pm2() {
echo -e "\n\n${COLOR_BLUE}Installing pm2...${COLOR_NC}\n\n"
if [ "$GLOBAL" = "y" ]; then
npm install pm2@latest -g
else
sudo npm install pm2@latest -g
fi
}
install_redis() {
echo -e "\n\n${COLOR_BLUE}Installing redis...${COLOR_NC}\n\n"
sudo apt install -y redis-server
sudo sed -ie 's/supervised no/supervised systemd/' /etc/redis/redis.conf
sudo systemctl restart redis.service
}
install_erlang() {
echo -e "\n\n${COLOR_BLUE}Installing erlang...${COLOR_NC}\n\n"
sudo apt -y install erlang
}
#ask user for rabbit credentials
rabbit_credentials() {
read -p "Enter rabbitmq user [hyperion]: " RABBIT_USER
RABBIT_USER=${RABBIT_USER:-hyperion}
read -p "Enter rabbitmq password [123456]: " RABBIT_PASSWORD
RABBIT_PASSWORD=${RABBIT_PASSWORD:-123456}
}
install_rabittmq() {
echo -e "\n\n${COLOR_BLUE}Installing rabbit-mq...${COLOR_NC}\n\n"
sudo apt install -y rabbitmq-server
#enable web gui
sudo rabbitmq-plugins enable rabbitmq_management
sudo rabbitmqctl add_vhost /hyperion
sudo rabbitmqctl add_user ${RABBIT_USER} ${RABBIT_PASSWORD}
sudo rabbitmqctl set_user_tags ${RABBIT_USER} administrator
sudo rabbitmqctl set_permissions -p /hyperion ${RABBIT_USER} ".*" ".*" ".*"
}
install_elastic() {
echo -e "\n\n${COLOR_BLUE}Installing elastic...${COLOR_NC}\n\n"
sudo apt install -y elasticsearch
echo -e "\n\n${COLOR_BLUE}Configuring elastic...${COLOR_NC}\n\n"
# edit configs
sudo sed -ie 's/#cluster.name: my-application/cluster.name: myCluster/; s/#bootstrap.memory_lock: true/bootstrap.memory_lock: true/' /etc/elasticsearch/elasticsearch.yml
# set jvm options based on system RAM
check_ram
if [ "$RAM" -lt 32 ]; then
(( RAM=RAM/2 ))
sudo sed -ie 's/-Xms1g/-Xms'"$RAM"'g/; s/-Xmx1g/-Xmx'"$RAM"'g/' /etc/elasticsearch/jvm.options
else
sudo sed -ie 's/-Xms1g/-Xms16g/; s/-Xmx1g/-Xmx16g/' /etc/elasticsearch/jvm.options
fi
sudo bash -c 'echo "xpack.security.enabled: true" >> /etc/elasticsearch/elasticsearch.yml'
sudo mkdir -p /etc/systemd/system/elasticsearch.service.d/
echo -e "[Service]\nLimitMEMLOCK=infinity" | sudo tee /etc/systemd/system/elasticsearch.service.d/override.conf
sudo systemctl daemon-reload
sudo service elasticsearch start
sudo systemctl enable elasticsearch
echo -e "\n\n${COLOR_BLUE}Generating Elasticsearch cluster passwords...${COLOR_NC}\n\n"
echo "y" | sudo /usr/share/elasticsearch/bin/elasticsearch-setup-passwords auto >elastic_pass.txt
}
install_kibana() {
echo -e "\n\n${COLOR_BLUE}Installing and configuring Kibana...${COLOR_NC}\n\n"
sudo apt install -y kibana
KIBANA_PASSWORD=$(awk <elastic_pass.txt '/PASSWORD kibana =/ {print $4}')
sudo sed -ie 's/#elasticsearch.password: "pass"/elasticsearch.password: '"$KIBANA_PASSWORD"'/; s/#elasticsearch.username: "kibana"/elasticsearch.username: "kibana"/' /etc/kibana/kibana.yml
sudo systemctl enable kibana
sudo systemctl start kibana
}
#******************
# End of functions
#******************
arg_checker
echo -e "\n\n${COLOR_BLUE}*** STARTING HYPERION HISTORY API CONFIGURATION ***${COLOR_NC}\n\n"
check_dependencies
if [ "$RABBIT" = false ]; then
rabbit_credentials
fi
install_dep
install_keys_sources
if [ "$NODE" = false ]; then
install_node
fi
install_pm2
install_build_hyperion
if [ "$RABBIT" = false ]; then
install_rabittmq
fi
if [ "$REDIS" = false ]; then
install_redis
fi
if [ "$ELASTIC" = false ]; then
install_elastic
install_kibana
fi
printf "
_ ___ ______ _____ ____ ___ ___ _ _
| | | \ \ / / _ \| ____| _ \|_ _/ _ \| \ | |
| |_| |\ V /| |_) | _| | |_) || | | | | \| |
| _ | | | | __/| |___| _ < | | |_| | |\ |
|_| |_| |_| |_| |_____|_| \_\___\___/|_| \_|
Made with ♥ by EOS Rio
"
echo -e "\n\n${COLOR_GREEN}Hyperion History API successfully installed!!${COLOR_NC}"
echo -e "\n${COLOR_YELLOW}Please, check your installation: ${COLOR_NC}"
echo -e "${COLOR_YELLOW}- Elastic: http://localhost:9200 ${COLOR_NC}"
echo -e "${COLOR_YELLOW}- Kibana: http://localhost:5601 ${COLOR_NC}"
echo -e "${COLOR_YELLOW}- RabbitMQ: http://localhost:15672 ${COLOR_NC}"
echo -e "${COLOR_YELLOW}- Refer to the elastic_pas.txt file for the elasticsearch passwords. ${COLOR_NC}"
echo -e "${COLOR_YELLOW}- Make pm2 auto-boot at server restart: \$ pm2 startup ${COLOR_NC}\n"
+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
+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'
});
+27 -89
View File
@@ -1,27 +1,28 @@
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}
});
}
@@ -36,11 +37,11 @@ export function registerRoutes(server: FastifyInstance) {
'/v2/history',
'/v2/state',
'/v1/chain/*',
'/v1/chain',
'/v1/chain'
];
server.addHook('onRoute', opts => {
if (!ignoreList.includes(opts.url)) {
if (opts.url.startsWith('/v') && !opts.url.startsWith('/v2/explore') && !opts.url.startsWith('/v2/docs')) {
if (opts.url.startsWith('/v')) {
routeSet.add(opts.url);
}
}
@@ -60,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"],
@@ -67,7 +70,10 @@ 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);
}
});
@@ -80,7 +86,7 @@ export function registerRoutes(server: FastifyInstance) {
summary: "Get list of supported APIs",
tags: ["node"]
},
handler: async (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => {
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);
@@ -92,88 +98,20 @@ export function registerRoutes(server: FastifyInstance) {
}
});
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}`);
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,28 +1,49 @@
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 redis = fastify.redis;
const trxId = request.query.id.toLowerCase();
const cachedData = await redis.hgetall(trxId);
let hits;
let hits2;
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,
"cached": undefined,
"cache_expires_in": undefined,
"executed": false,
"hot_only": false,
"trx_id": trxId,
"lib": undefined,
"actions": [],
"generated": undefined
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;
// 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);
});
}
});
});
// reconstruct hits from cached data
if (cachedData && Object.keys(cachedData).length > 0) {
const gsArr = [];
for (let cachedDataKey in cachedData) {
@@ -31,83 +52,77 @@ async function getTransaction(fastify: FastifyInstance, request: FastifyRequest)
gsArr.sort((a, b) => {
return a.global_sequence - b.global_sequence;
});
response.cache_expires_in = await redis.ttl(trxId);
hits = gsArr.map(value => {
return {_source: JSON.parse(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;
}
// search on ES if cache is not present
if (!hits) {
const _size = fastify.manager.config.api.limits.get_trx_actions || 100;
let indexPattern = fastify.manager.chain + '-action-*';
if (request.query.hot_only) {
indexPattern = fastify.manager.chain + '-action';
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 pResults = await Promise.all([
fastify.eosjs.rpc.get_info(),
fastify.elastic.search({
// build search request
const $search = fastify.elastic.search({
index: indexPattern,
size: _size,
body: {
query: {bool: {must: [{term: {trx_id: request.query.id.toLowerCase()}}]}},
query: {bool: {must: [{term: {trx_id: trxId}}]}},
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()}}]}}
}
})
]);
const results = pResults[1];
const genTrxRes = pResults[2];
});
// 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;
if (request.query.hot_only) {
response.hot_only = true;
}
hits = results['body']['hits']['hits'];
hits2 = genTrxRes['body']['hits']['hits'];
}
if (hits) {
if (hits.length > 0) {
let highestBlockNum = 0;
for (let action of hits) {
if (action._source.block_num > highestBlockNum) {
highestBlockNum = action._source.block_num;
}
if (hits.length > 0) {
let highestBlockNum = 0;
for (let action of hits) {
if (action._source.block_num > highestBlockNum) {
highestBlockNum = action._source.block_num;
}
for (let action of hits) {
if (action._source.block_num === highestBlockNum) {
action = action._source;
mergeActionMeta(action);
response.actions.push(action);
}
}
response.actions = [];
for (let action of hits) {
if (action._source.block_num === highestBlockNum) {
mergeActionMeta(action._source);
response.actions.push(action._source);
}
response.executed = true;
}
response.executed = true;
}
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;
}
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));
}
}
@@ -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');
}
}
}
-4
View File
@@ -1,4 +0,0 @@
#!/usr/bin/env bash
git clone https://github.com/eosrio/hyperion-history-api.git || exit
cd hyperion-history-api || exit
bash ./install_env.sh
+44 -22
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,19 +22,48 @@
"get_trx_actions": 200
},
"access_log": false,
"enable_explorer": false,
"chain_api_error_log": false,
"custom_core_token": "",
"enable_export_action": false,
"disable_rate_limit": false,
"rate_limit_rpm": 1000,
"rate_limit_allow": [],
"disable_tx_cache": false,
"tx_cache_expiration_sec": 3600
"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,
@@ -39,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": [],
@@ -63,29 +97,16 @@
"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",
"routing_mode": "round_robin",
"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
},
"features": {
"streaming": {
"enable": false,
@@ -109,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"
}
}
}
+10 -10
View File
@@ -9,8 +9,8 @@ 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);
}
@@ -34,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;
}
}
@@ -70,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
@@ -78,13 +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) {
if(e.response.body) {
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));
}
} else {
hLog(JSON.stringify(e, null, 2));
}
return 0;
}
+148 -134
View File
@@ -1,5 +1,5 @@
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";
@@ -12,153 +12,167 @@ 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 -33
View File
@@ -1,38 +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,
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,
time: true,
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

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