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 *.seed
*.pid.lock *.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 # Dependency directories
node_modules/ node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# Optional npm cache directory # Optional npm cache directory
.npm .npm
@@ -51,18 +26,11 @@ typings/
# Output of 'npm pack' # Output of 'npm pack'
*.tgz *.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file # dotenv environment variables file
.env .env
# next.js build output
.next
.idea .idea
*.pem *.pem
ecosystem.config.js
package-lock.json package-lock.json
connections.json connections.json
connections.json2 connections.json2
@@ -106,9 +74,20 @@ modules/**/*.js.map
addons/**/*.js addons/**/*.js
addons/**/*.js.map 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/repos
plugins/*.js
.gitmodules
docker/redis/data docker/redis/data
docker/elasticsearch/data docker/elasticsearch/data
docker/eosio/data
docker/rabbitmq/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 # 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/> <br/>
Scalable Full History API Solution for EOSIO based blockchains 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 ### 2. Architecture
The following components are required in order to have a fully functional Hyperion API deployment, 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. 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. 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 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. 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 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, 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, 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. 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. 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 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 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 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 Nodeos plugin used to collect action traces and state deltas. Provides data via websocket to the indexer
### 3. How to use ### 3. How to use
+9 -2
View File
@@ -4,6 +4,7 @@ export function generateOpenApiConfig(config: HyperionConfig) {
const packageData = require('../../package'); const packageData = require('../../package');
const health_link = `https://${config.api.server_name}/v2/health`; const health_link = `https://${config.api.server_name}/v2/health`;
const explorer_link = `https://${config.api.server_name}/v2/explore`; const explorer_link = `https://${config.api.server_name}/v2/explore`;
let description = ` let description = `
<img height="64" src="https://eosrio.io/hyperion.png"> <img height="64" src="https://eosrio.io/hyperion.png">
### Scalable Full History API Solution for EOSIO based blockchains ### 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}) #### Provided by [${config.api.provider_name}](${config.api.provider_url})
#### Health API: <a target="_blank" href="${health_link}">${health_link}</a> #### 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 { return {
routePrefix: '/v2/docs', routePrefix: '/v2/docs',
exposeRoute: true, 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 {createHash} from "crypto";
import * as _ from "lodash"; import * as _ from "lodash";
import {FastifyInstance, FastifyReply, FastifyRequest, HTTPMethod, RouteSchema} from "fastify"; import {FastifyInstance, FastifyReply, FastifyRequest, FastifySchema, HTTPMethods} from "fastify";
import {ServerResponse} from "http";
import got from "got"; import got from "got";
export function extendResponseSchema(responseProps: any) { export function extendResponseSchema(responseProps: any) {
const props = { const props = {
query_time_ms: {type: "number"}, query_time_ms: {type: "number"},
cached: {type: "boolean"}, cached: {type: "boolean"},
hot_only: {type: "boolean"}, hot_only: {type: "boolean"},
lib: {type: "number"}, lib: {type: "number"},
total: { total: {
type: "object", type: "object",
properties: { properties: {
value: {type: "number"}, value: {type: "number"},
relation: {type: "string"} relation: {type: "string"}
} }
} }
}; };
for (const p in responseProps) { for (const p in responseProps) {
if (responseProps.hasOwnProperty(p)) { if (responseProps.hasOwnProperty(p)) {
props[p] = responseProps[p]; props[p] = responseProps[p];
} }
} }
return { return {
200: { 200: {
type: 'object', type: 'object',
properties: props properties: props
} }
}; };
} }
export function extendQueryStringSchema(queryParams: any, required?: string[]) { export function extendQueryStringSchema(queryParams: any, required?: string[]) {
const params = { const params = {
limit: { limit: {
description: 'limit of [n] results per page', description: 'limit of [n] results per page',
type: 'integer', type: 'integer',
minimum: 1 minimum: 1
}, },
skip: { skip: {
description: 'skip [n] results', description: 'skip [n] results',
type: 'integer', type: 'integer',
minimum: 0 minimum: 0
} }
}; };
for (const p in queryParams) { for (const p in queryParams) {
if (queryParams.hasOwnProperty(p)) { if (queryParams.hasOwnProperty(p)) {
params[p] = queryParams[p]; params[p] = queryParams[p];
} }
} }
const schema = { const schema = {
type: 'object', type: 'object',
properties: params properties: params
} }
if (required && required.length > 0) { if (required && required.length > 0) {
schema["required"] = required; schema["required"] = required;
} }
return schema; return schema;
} }
export async function getCacheByHash(redis, key, chain) { export async function getCacheByHash(redis, key, chain) {
const hash = createHash('sha256'); const hash = createHash('sha256');
const query_hash = hash.update(chain + "-" + key).digest('hex'); const query_hash = hash.update(chain + "-" + key).digest('hex');
return [await redis.get(query_hash), query_hash]; return [await redis.get(query_hash), query_hash];
} }
export function mergeActionMeta(action) { export function mergeActionMeta(action) {
const name = action.act.name; const name = action.act.name;
if (action['@' + name]) { if (action['@' + name]) {
action['act']['data'] = _.merge(action['@' + name], action['act']['data']); action['act']['data'] = _.merge(action['@' + name], action['act']['data']);
delete action['@' + name]; delete action['@' + name];
} }
action['timestamp'] = action['@timestamp']; action['timestamp'] = action['@timestamp'];
// delete action['@timestamp']; // delete action['@timestamp'];
} }
export function mergeDeltaMeta(delta: any) { export function mergeDeltaMeta(delta: any) {
const name = delta.table; const name = delta.table;
if (delta["@" + name]) { if (delta["@" + name]) {
delta['data'] = _.merge(delta['@' + name], delta['data']); delta['data'] = _.merge(delta['@' + name], delta['data']);
delete delta['@' + name]; delete delta['@' + name];
} }
delta['timestamp'] = delta['@timestamp']; delta['timestamp'] = delta['@timestamp'];
delete delta['@timestamp']; delete delta['@timestamp'];
return delta; return delta;
} }
export function setCacheByHash(fastify, hash, response, expiration?: number) { export function setCacheByHash(fastify, hash, response, expiration?: number) {
if (fastify.manager.config.api.enable_caching) { if (fastify.manager.config.api.enable_caching) {
let exp; let exp;
if (expiration) { if (expiration) {
exp = expiration; exp = expiration;
} else { } else {
exp = fastify.manager.config.api.cache_life; exp = fastify.manager.config.api.cache_life;
} }
fastify.redis.set(hash, JSON.stringify(response), 'EX', exp).catch(console.log); fastify.redis.set(hash, JSON.stringify(response), 'EX', exp).catch(console.log);
} }
} }
export function getRouteName(filename: string) { export function getRouteName(filename: string) {
const arr = filename.split("/"); const arr = filename.split("/");
return arr[arr.length - 2]; return arr[arr.length - 2];
} }
export function addApiRoute( export function addApiRoute(
fastifyInstance: FastifyInstance, fastifyInstance: FastifyInstance,
method: HTTPMethod | HTTPMethod[], method: HTTPMethods | HTTPMethods[],
routeName: string, routeName: string,
routeBuilder: (fastify: FastifyInstance, route: string) => (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => Promise<void>, schema: RouteSchema) { routeBuilder: (fastify: FastifyInstance, route: string) => (request: FastifyRequest, reply: FastifyReply) => Promise<void>,
fastifyInstance.route({ schema: FastifySchema
url: '/' + routeName, ) {
method, fastifyInstance.route({
handler: routeBuilder(fastifyInstance, routeName), url: '/' + routeName,
schema method,
}); handler: routeBuilder(fastifyInstance, routeName),
schema
});
} }
export async function getCachedResponse(server: FastifyInstance, route: string, key: any) { export async function getCachedResponse(server: FastifyInstance, route: string, key: any) {
const chain = server.manager.chain; const chain = server.manager.chain;
let resp, hash; let resp, hash;
if (server.manager.config.api.enable_caching) { if (server.manager.config.api.enable_caching) {
[resp, hash] = await getCacheByHash(server.redis, route + JSON.stringify(key), chain); [resp, hash] = await getCacheByHash(server.redis, route + JSON.stringify(key), chain);
if (resp) { if (resp) {
resp = JSON.parse(resp); resp = JSON.parse(resp);
resp['cached'] = true; resp['cached'] = true;
return [resp, hash]; return [resp, hash];
} else { } else {
return [null, hash]; return [null, hash];
} }
} else { } else {
return [null, null]; return [null, null];
} }
} }
export function getTrackTotalHits(query) { export function getTrackTotalHits(query) {
let trackTotalHits: number | boolean = 10000; let trackTotalHits: number | boolean = 10000;
if (query?.track) { if (query?.track) {
if (query.track === 'true') { if (query.track === 'true') {
trackTotalHits = true; trackTotalHits = true;
} else if (query.track === 'false') { } else if (query.track === 'false') {
trackTotalHits = false; trackTotalHits = false;
} else { } else {
const parsed = parseInt(query.track, 10); const parsed = parseInt(query.track, 10);
if (parsed > 0) { if (parsed > 0) {
trackTotalHits = parsed; trackTotalHits = parsed;
} else { } else {
throw new Error('failed to parse track param'); throw new Error('failed to parse track param');
} }
} }
} }
return trackTotalHits; return trackTotalHits;
} }
function bigint2Milliseconds(input: bigint) { function bigint2Milliseconds(input: bigint) {
return parseFloat((parseInt(input.toString()) / 1000000).toFixed(3)); return parseFloat((parseInt(input.toString()) / 1000000).toFixed(3));
} }
const defaultRouteCacheMap = { const defaultRouteCacheMap = {
get_resource_usage: 3600, get_resource_usage: 3600,
get_creator: 3600 * 24 get_creator: 3600 * 24
} }
export async function timedQuery( export async function timedQuery(
queryFunction: (fastify: FastifyInstance, request: FastifyRequest) => Promise<any>, queryFunction: (fastify: FastifyInstance, request: FastifyRequest) => Promise<any>,
fastify: FastifyInstance, request: FastifyRequest, route: string): Promise<any> { fastify: FastifyInstance, request: FastifyRequest, route: string): Promise<any> {
// get reference time in nanoseconds // get reference time in nanoseconds
const t0 = process.hrtime.bigint(); const t0 = process.hrtime.bigint();
// check for cached data, return the response hash if caching is enabled // check for cached data, return the response hash if caching is enabled
const [cachedResponse, hash] = await getCachedResponse( const [cachedResponse, hash] = await getCachedResponse(
fastify, fastify,
route, route,
request.req.method === 'POST' ? request.body : request.query request.method === 'POST' ? request.body : request.query
); );
if (cachedResponse && !request.query.ignoreCache) { if (cachedResponse && !request.query["ignoreCache"]) {
// add cached query time // add cached query time
cachedResponse['query_time_ms'] = bigint2Milliseconds(process.hrtime.bigint() - t0); cachedResponse['query_time_ms'] = bigint2Milliseconds(process.hrtime.bigint() - t0);
return cachedResponse; return cachedResponse;
} }
// call query function // call query function
const response = await queryFunction(fastify, request); const response = await queryFunction(fastify, request);
// save response to cash // save response to cash
if (hash) { if (hash) {
let EX = null; let EX = null;
if (defaultRouteCacheMap[route]) { if (defaultRouteCacheMap[route]) {
EX = defaultRouteCacheMap[route]; EX = defaultRouteCacheMap[route];
} }
setCacheByHash(fastify, hash, response, EX); setCacheByHash(fastify, hash, response, EX);
} }
// add normal query time // add normal query time
if (response) { if (response) {
response['query_time_ms'] = bigint2Milliseconds(process.hrtime.bigint() - t0); response['query_time_ms'] = bigint2Milliseconds(process.hrtime.bigint() - t0);
return response; return response;
} else { } else {
return {}; return {};
} }
} }
export function chainApiHandler(fastify: FastifyInstance) { export function chainApiHandler(fastify: FastifyInstance) {
return async (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => { return async (request: FastifyRequest, reply: FastifyReply) => {
await handleChainApiRedirect(request, reply, fastify); // 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( export async function handleChainApiRedirect(
request: FastifyRequest, request: FastifyRequest,
reply: FastifyReply<ServerResponse>, reply: FastifyReply,
fastify: FastifyInstance fastify: FastifyInstance
) { ): Promise<string> {
const urlParts = request.req.url.split("?"); const urlParts = request.url.split("?");
let reqUrl = fastify.chain_api + urlParts[0]; let reqUrl = fastify.chain_api + urlParts[0];
if (urlParts[0] === '/v1/chain/push_transaction' && fastify.push_api && fastify.push_api !== "") { // const pathComponents = urlParts[0].split('/');
reqUrl = fastify.push_api + urlParts[0]; // const path = pathComponents.at(-1);
}
const opts = {};
if (request.req.method === 'POST') { if (urlParts[0] === '/v1/chain/push_transaction' && fastify.push_api && fastify.push_api !== "") {
if (request.body) { reqUrl = fastify.push_api + urlParts[0];
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;
}
try { const opts = {};
const apiResponse = await got.post(reqUrl, opts);
reply.headers({"Content-Type": "application/json"}); if (request.method === 'POST') {
if (request.req.method === 'HEAD') { if (request.body) {
reply.headers({"Content-Length": apiResponse.body.length}); if (typeof request.body === 'string') {
reply.send(""); opts['body'] = request.body;
} else { } else if (typeof request.body === 'object') {
reply.send(apiResponse.body); opts['body'] = JSON.stringify(request.body);
} }
} catch (error) { } else {
if (error.response) { opts['body'] = "";
reply.status(error.response.statusCode).send(error.response.body); }
} else { } else if (request.method === 'GET') {
console.log(error); opts['json'] = request.query;
reply.status(500).send(); }
}
if (fastify.manager.config.api.chain_api_error_log) { try {
try { const apiResponse = await got.post(reqUrl, opts);
if (error.response) { reply.headers({"Content-Type": "application/json"});
const error_msg = JSON.parse(error.response.body).error.details[0].message; if (request.method === 'HEAD') {
console.log(`endpoint: ${request.req.url} | status: ${error.response.statusCode} | error: ${error_msg}`); reply.headers({"Content-Length": apiResponse.body.length});
} else { reply.send("");
console.log(error); return '';
} } else {
// if (request.req.url === '/v1/chain/push_transaction') { reply.send(apiResponse.body);
// const packedTrx = JSON.parse(opts['body']).packed_trx; return apiResponse.body;
// const trxBuffer = Buffer.from(packedTrx, 'hex'); }
// const trxData = await fastify.eosjs.api.deserializeTransactionWithActions(trxBuffer); } catch (error) {
// console.log(trxData);
// } if (error.response) {
} catch (e) { reply.status(error.response.statusCode).send(error.response.body);
console.log(e); } 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?) { export function addChainApiRoute(fastify: FastifyInstance, routeName, description, props?, required?) {
const baseSchema = { const baseSchema = {
description: description, description: description,
summary: description, summary: description,
tags: ['chain'] tags: ['chain']
}; };
addApiRoute( addApiRoute(
fastify, fastify,
['GET', 'HEAD'], ['GET', 'HEAD'],
routeName, routeName,
chainApiHandler, chainApiHandler,
{ {
...baseSchema, ...baseSchema,
querystring: props ? { querystring: props ? {
type: 'object', type: 'object',
properties: props, properties: props,
required: required required: required
} : undefined } : undefined
} }
); );
addApiRoute( addApiRoute(
fastify, fastify,
'POST', 'POST',
routeName, routeName,
chainApiHandler, chainApiHandler,
{ {
...baseSchema, ...baseSchema,
body: props ? { body: props ? {
type: ['object', 'string'], type: ['object', 'string'],
properties: props, properties: props,
required: required required: required
} : undefined } : undefined
} }
); );
} }
export function addSharedSchemas(fastify: FastifyInstance) { export function addSharedSchemas(fastify: FastifyInstance) {
fastify.addSchema({ fastify.addSchema({
$id: "WholeNumber", $id: "WholeNumber",
description: "A whole number", title: "Integer",
anyOf: [ description: "Integer or String",
{ anyOf: [
type: "string", {
pattern: "^\\d+$" type: "string",
}, pattern: "^\\d+$"
{ },
type: "integer" {
} type: "integer"
], }
}); ],
});
fastify.addSchema({ fastify.addSchema({
$id: "Symbol", $id: "Symbol",
type: "string", type: "string",
description: "A symbol composed of capital letters between 1-7.", description: "A symbol composed of capital letters between 1-7.",
pattern: "^([A-Z]{1,7})$", pattern: "^([A-Z]{1,7})$",
title: "Symbol" title: "Symbol"
}); });
fastify.addSchema({ fastify.addSchema({
$id: "Signature", $id: "Signature",
type: "string", type: "string",
description: "String representation of an EOSIO compatible cryptographic signature", description: "String representation of an EOSIO compatible cryptographic signature",
pattern: "^SIG_([RK]1|WA)_[1-9A-HJ-NP-Za-km-z]+$", pattern: "^SIG_([RK]1|WA)_[1-9A-HJ-NP-Za-km-z]+$",
title: "Signature" title: "Signature"
}) })
fastify.addSchema({ fastify.addSchema({
$id: "AccountName", $id: "AccountName",
"anyOf": [ description: "String representation of an EOSIO compatible account name",
{ "anyOf": [
"type": "string", {
"description": "String representation of privileged EOSIO name type", "type": "string",
"pattern": "^(eosio[\\.][a-z1-5]{1,6})([a-j]{1})?$", "description": "String representation of privileged EOSIO name type",
"title": "NamePrivileged" "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", "type": "string",
"pattern": "^([a-z]{1}[a-z1-5]{11})([a-j]{1})?$", "description": "String representation of basic EOSIO name type, must be 12 characters and contain only a-z and 0-5",
"title": "NameBasic" "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", "type": "string",
"pattern": "^([a-z1-5]{1,12})([a-j]{1})?$", "description": "String representation of EOSIO bid name type, 1-12 characters and only a-z and 0-5 are allowed",
"title": "NameBid" "pattern": "^([a-z1-5]{1,12})([a-j]{1})?$",
}, "title": "NameBid"
{ },
"type": "string", {
"description": "String representation of EOSIO name type", "type": "string",
"pattern": "^([a-z1-5]{1}[a-z1-5\\.]{0,10}[a-z1-5]{1})([a-j]{1})?$", "description": "String representation of EOSIO name type",
"title": "NameCatchAll" "pattern": "^([a-z1-5]{1}[a-z1-5\\.]{0,10}[a-z1-5]{1})([a-j]{1})?$",
} "title": "NameCatchAll"
], }
"title": "Name" ],
}); "title": "Name"
});
fastify.addSchema({ fastify.addSchema({
$id: "Expiration", $id: "Expiration",
"description": "Time that transaction must be confirmed by.", "description": "Time that transaction must be confirmed by.",
"type": "string", "type": "string",
"pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}$", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}$",
"title": "DateTime" "title": "DateTime"
}); });
fastify.addSchema({ fastify.addSchema({
$id: "BlockExtensions", $id: "BlockExtensions",
"type": "array", "type": "array",
"items": { "items": {
"anyOf": [{"type": "integer"}, {"type": "string"}] "anyOf": [
}, {"type": "integer"},
"title": "Extension" {"type": "string"}
}) ]
},
"title": "Extension"
})
fastify.addSchema({ fastify.addSchema({
$id: "ActionItems", $id: "ActionItems",
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"minProperties": 5, "minProperties": 5,
"required": [ "required": [
"account", "account",
"name", "name",
"authorization", "authorization",
"data", "data",
"hex_data" "hex_data"
], ],
"properties": { "properties": {
"account": 'AccountName#', "account": {$ref: 'AccountName#'},
"name": 'AccountName#', "name": {$ref: 'AccountName#'},
"authorization": { "authorization": {
"type": "array", "type": "array",
"items": { "items": {
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"minProperties": 2, "minProperties": 2,
"required": [ "required": [
"actor", "actor",
"permission" "permission"
], ],
"properties": { "properties": {
"actor": 'AccountName#', "actor": {$ref: 'AccountName#'},
"permission": 'AccountName#' "permission": {$ref: 'AccountName#'}
}, },
"title": "Authority" "title": "Authority"
} }
}, },
"data": { "data": {
"type": "object", "type": "object",
"additionalProperties": true "additionalProperties": true
}, },
"hex_data": { "hex_data": {
"type": "string" "type": "string"
} }
}, },
"title": "Action" "title": "Action"
}); });
} }
+22 -12
View File
@@ -2,22 +2,32 @@ import * as Fastify from "fastify";
import {IncomingMessage, Server, ServerResponse} from "http"; import {IncomingMessage, Server, ServerResponse} from "http";
// fastify plugins // fastify plugins
import * as fastify_elasticsearch from 'fastify-elasticsearch'; import * as fastifyElasticsearch from 'fastify-elasticsearch';
import * as fastify_oas from 'fastify-oas'; import fastifySwagger from 'fastify-swagger';
import * as fastify_cors from 'fastify-cors'; import fastifyCors from 'fastify-cors';
import * as fastify_formbody from 'fastify-formbody'; import fastifyFormbody from 'fastify-formbody';
import * as fastify_redis from 'fastify-redis'; import fastifyRedis from 'fastify-redis';
import * as fastify_rate_limit from 'fastify-rate-limit'; import fastifyRateLimit from 'fastify-rate-limit';
// custom plugins // custom plugins
import fastify_eosjs from "./plugins/fastify-eosjs"; import fastify_eosjs from "./plugins/fastify-eosjs";
export function registerPlugins(server: Fastify.FastifyInstance<Server, IncomingMessage, ServerResponse>, params: any) { export function registerPlugins(server: Fastify.FastifyInstance<Server, IncomingMessage, ServerResponse>, params: any) {
server.register(fastify_elasticsearch, params.fastify_elasticsearch); server.register(fastifyElasticsearch, params.fastify_elasticsearch);
server.register(fastify_oas, params.fastify_oas);
server.register(fastify_cors); if (params.fastify_swagger) {
server.register(fastify_formbody); server.register(fastifySwagger, params.fastify_swagger);
server.register(fastify_redis, params.fastify_redis); }
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_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, FastifyPluginOptions} from "fastify";
import {FastifyInstance} from "fastify";
import {Api} from "eosjs/dist"; import {Api} from "eosjs/dist";
import fp from "fastify-plugin";
export default fp(async (fastify: FastifyInstance, options, next) => { export default fp(async (fastify: FastifyInstance, options: FastifyPluginOptions): Promise<void> => {
const rpc = fastify.manager.nodeosJsonRPC; const rpc = fastify.manager.nodeosJsonRPC;
const chain_data = await rpc.get_info(); const chain_data = await rpc.get_info();
const api = new Api({ const api = new Api({
rpc, rpc,
signatureProvider: null, signatureProvider: null,
chainId: chain_data.chain_id, chainId: chain_data.chain_id,
textDecoder: new TextDecoder(), textDecoder: new TextDecoder(),
textEncoder: new TextEncoder(), textEncoder: new TextEncoder(),
}); });
fastify.decorate('eosjs', {api, rpc}); fastify.decorate('eosjs', {api, rpc});
next();
}, { }, {
fastify: '>=2.0.0', fastify: '>=2.0.0',
name: 'fastify-eosjs' 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 {join} from "path";
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify"; import {FastifyError, FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http"; import {createReadStream} from "fs";
import {createReadStream, existsSync, readFileSync, unlinkSync} from "fs";
import * as AutoLoad from "fastify-autoload";
import {addSharedSchemas, handleChainApiRedirect} from "./helpers/functions"; import {addSharedSchemas, handleChainApiRedirect} from "./helpers/functions";
import autoLoad from 'fastify-autoload';
import got from "got"; import got from "got";
function addRedirect(server: FastifyInstance, url: string, redirectTo: string) { function addRedirect(server: FastifyInstance, url: string, redirectTo: string) {
server.route({ server.route({
url, url,
method: 'GET', method: 'GET',
schema: {hide: true}, schema: {
handler: async (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => { hide: true
},
handler: async (request: FastifyRequest, reply: FastifyReply) => {
reply.redirect(redirectTo); reply.redirect(redirectTo);
} }
}); });
} }
function addRoute(server: FastifyInstance, handlersPath: string, prefix: string) { function addRoute(server: FastifyInstance, handlersPath: string, prefix: string) {
server.register(AutoLoad, { server.register(autoLoad, {
dir: join(__dirname, 'routes', handlersPath), dir: join(__dirname, 'routes', handlersPath),
ignorePattern: /.*(handler|schema).js/, ignorePattern: /.*(handler|schema).js/,
dirNameRoutePrefix: false,
options: {prefix} options: {prefix}
}); });
} }
@@ -36,11 +37,11 @@ export function registerRoutes(server: FastifyInstance) {
'/v2/history', '/v2/history',
'/v2/state', '/v2/state',
'/v1/chain/*', '/v1/chain/*',
'/v1/chain', '/v1/chain'
]; ];
server.addHook('onRoute', opts => { server.addHook('onRoute', opts => {
if (!ignoreList.includes(opts.url)) { 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); routeSet.add(opts.url);
} }
} }
@@ -60,6 +61,8 @@ export function registerRoutes(server: FastifyInstance) {
// chain api redirects // chain api redirects
addRoute(server, 'v1-chain', '/v1/chain'); addRoute(server, 'v1-chain', '/v1/chain');
// other v1 requests
server.route({ server.route({
url: '/v1/chain/*', url: '/v1/chain/*',
method: ["GET", "POST"], method: ["GET", "POST"],
@@ -67,7 +70,10 @@ export function registerRoutes(server: FastifyInstance) {
summary: "Wildcard chain api handler", summary: "Wildcard chain api handler",
tags: ["chain"] tags: ["chain"]
}, },
handler: async (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => { handler: async (request: FastifyRequest, reply: FastifyReply) => {
console.log(request.url);
await handleChainApiRedirect(request, reply, server); await handleChainApiRedirect(request, reply, server);
} }
}); });
@@ -80,7 +86,7 @@ export function registerRoutes(server: FastifyInstance) {
summary: "Get list of supported APIs", summary: "Get list of supported APIs",
tags: ["node"] 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; const data = await got.get(`${server.chain_api}/v1/node/get_supported_apis`).json() as any;
if (data.apis && data.apis.length > 0) { if (data.apis && data.apis.length > 0) {
const apiSet = new Set(server.routeSet); const apiSet = new Set(server.routeSet);
@@ -92,88 +98,20 @@ export function registerRoutes(server: FastifyInstance) {
} }
}); });
server.addHook('onError', (request, reply, error, done) => { server.addHook('onError', (request: FastifyRequest, reply: FastifyReply, error: FastifyError, done) => {
console.log(`[${request.req.headers['x-real-ip']}] ${request.req.method} ${request.req.url} failed with error: ${error.message}`); console.log(`[${request.headers['x-real-ip'] || request.ip}] ${request.method} ${request.url} failed >> ${error.message}`);
done(); 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) { if (server.manager.config.features.streaming) {
// steam client lib // steam client lib
server.get('/stream-client.js', {schema: {tags: ['internal']}}, (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => { server.get(
const stream = createReadStream('./client_bundle.js'); '/stream-client.js',
reply.type('application/javascript').send(stream); {schema: {tags: ['internal']}},
}); (request: FastifyRequest, reply: FastifyReply) => {
const stream = createReadStream('./hyperion-stream-client.js');
reply.type('application/javascript').send(stream);
});
} }
// Redirect routes to documentation // 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), getRouteName(__filename),
'Returns an object containing rows from the specified table.', 'Returns an object containing rows from the specified table.',
{ {
"code": 'AccountName#', "code": {$ref: 'AccountName#'},
"action": 'AccountName#', "action": {$ref: 'AccountName#'},
"binargs": { "binargs": {
"type": "string", "type": "string",
"pattern": "^(0x)(([0-9a-f][0-9a-f])+)?$", "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, fastify,
getRouteName(__filename), getRouteName(__filename),
'Retrieves the ABI for a contract based on its account name', 'Retrieves the ABI for a contract based on its account name',
{"account_name": 'AccountName#'}, {"account_name": {$ref: 'AccountName#'}},
["account_name"] ["account_name"]
); );
next(); next();
+3 -1
View File
@@ -6,7 +6,9 @@ export default function (fastify: FastifyInstance, opts: any, next) {
fastify, fastify,
getRouteName(__filename), getRouteName(__filename),
'Returns an object containing various details about a specific account on the blockchain.', 'Returns an object containing various details about a specific account on the blockchain.',
{"account_name": 'AccountName#'}, {
"account_name": {$ref: 'AccountName#'}
},
["account_name"] ["account_name"]
); );
next(); next();
+1 -1
View File
@@ -7,7 +7,7 @@ export default function (fastify: FastifyInstance, opts: any, next) {
getRouteName(__filename), getRouteName(__filename),
'Retrieves contract code', 'Retrieves contract code',
{ {
"account_name": 'AccountName#', "account_name": {$ref: 'AccountName#'},
"code_as_wasm": { "code_as_wasm": {
"type": "integer", "type": "integer",
"default": 1, "default": 1,
@@ -7,9 +7,9 @@ export default function (fastify: FastifyInstance, opts: any, next) {
getRouteName(__filename), getRouteName(__filename),
'Retrieves the current balance', 'Retrieves the current balance',
{ {
"code": 'AccountName#', "code": {$ref: 'AccountName#'},
"account": 'AccountName#', "account": {$ref: 'AccountName#'},
"symbol": 'Symbol#', "symbol": {$ref: 'Symbol#'}
}, },
["code", "account", "symbol"] ["code", "account", "symbol"]
); );
@@ -7,12 +7,7 @@ export default function (fastify: FastifyInstance, opts: any, next) {
getRouteName(__filename), getRouteName(__filename),
'Retrieves currency stats', 'Retrieves currency stats',
{ {
"code": { "code": {$ref: 'AccountName#'},
description: 'contract name',
type: 'string',
minLength: 1,
maxLength: 12
},
"symbol": { "symbol": {
description: 'token symbol', description: 'token symbol',
type: 'string', type: 'string',
+1 -1
View File
@@ -7,7 +7,7 @@ export default function (fastify: FastifyInstance, opts: any, next) {
getRouteName(__filename), getRouteName(__filename),
'Retrieves raw ABI for a contract based on account name', 'Retrieves raw ABI for a contract based on account name',
{ {
"account_name": 'AccountName#' "account_name": {$ref: 'AccountName#'}
}, },
["account_name"] ["account_name"]
); );
@@ -7,7 +7,7 @@ export default function (fastify: FastifyInstance, opts: any, next) {
getRouteName(__filename), getRouteName(__filename),
'Retrieves raw code and ABI for a contract based on account name', 'Retrieves raw code and ABI for a contract based on account name',
{ {
"account_name": 'AccountName#' "account_name": {$ref: 'AccountName#'}
}, },
["account_name"] ["account_name"]
); );
+2 -1
View File
@@ -36,7 +36,8 @@ export default function (fastify: FastifyInstance, opts: any, next) {
"lower_bound": { "lower_bound": {
"type": "string" "type": "string"
} }
} },
["code", "table", "scope"]
); );
next(); next();
} }
@@ -17,7 +17,7 @@ export default function (fastify: FastifyInstance, opts: any, next) {
signatures: { signatures: {
"type": "array", "type": "array",
"description": "array of signatures required to authorize transaction", "description": "array of signatures required to authorize transaction",
"items": 'Signature#' "items": {$ref: 'Signature#'}
}, },
compression: { compression: {
"type": "boolean", "type": "boolean",
+50 -41
View File
@@ -2,45 +2,54 @@ import {FastifyInstance} from "fastify";
import {addApiRoute, chainApiHandler, getRouteName} from "../../../helpers/functions"; import {addApiRoute, chainApiHandler, getRouteName} from "../../../helpers/functions";
export default function (fastify: FastifyInstance, opts: any, next) { export default function (fastify: FastifyInstance, opts: any, next) {
addApiRoute( addApiRoute(
fastify, fastify,
'POST', 'POST',
getRouteName(__filename), getRouteName(__filename),
chainApiHandler, chainApiHandler,
{ {
description: "This method expects a transaction in JSON format and will attempt to apply it to the blockchain.", 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.", summary: "This method expects a transaction in JSON format and will attempt to apply it to the blockchain.",
tags: ['chain'], tags: ['chain'],
body: { body: {
type: ['array', 'object', 'string'], type: ['array', 'object', 'string'],
items: { items: {
type: ['object', 'string'], type: ['object', 'string'],
additionalProperties: false, additionalProperties: false,
minProperties: 8, minProperties: 8,
required: [ required: [
"expiration", "expiration",
"ref_block_num", "ref_block_num",
"ref_block_prefix", "ref_block_prefix",
"max_net_usage_words", "max_net_usage_words",
"max_cpu_usage_ms", "max_cpu_usage_ms",
"delay_sec", "delay_sec",
"context_free_actions", "context_free_actions",
"actions" "actions"
], ],
properties: { properties: {
"expiration": 'Expiration#', "expiration": {$ref: 'Expiration#'},
"ref_block_num": {"type": "integer"}, "ref_block_num": {"type": "integer"},
"ref_block_prefix": {"type": "integer"}, "ref_block_prefix": {"type": "integer"},
"max_net_usage_words": 'WholeNumber#', "max_net_usage_words": {$ref: 'WholeNumber#'},
"max_cpu_usage_ms": 'WholeNumber#', "max_cpu_usage_ms": {$ref: 'WholeNumber#'},
"delay_sec": {"type": "integer"}, "delay_sec": {"type": "integer"},
"context_free_actions": {"type": "array", "items": 'ActionItems#'}, "context_free_actions": {
"actions": {"type": "array", "items": 'ActionItems#'}, "type": "array",
"transaction_extensions": {"type": "array", "items": 'BlockExtensions#'} "items": {$ref: 'ActionItems#'}
}, },
} "actions": {
} "type": "array",
} "items": {$ref: 'ActionItems#'}
); },
next(); "transaction_extensions": {
"type": "array",
"items": {$ref: 'BlockExtensions#'}
}
},
}
}
}
);
next();
} }
@@ -17,7 +17,7 @@ export default function (fastify: FastifyInstance, opts: any, next) {
signatures: { signatures: {
"type": "array", "type": "array",
"description": "array of signatures required to authorize transaction", "description": "array of signatures required to authorize transaction",
"items": 'Signature#' "items": {$ref: 'Signature#'}
}, },
compression: { compression: {
"type": "boolean", "type": "boolean",
@@ -1,7 +1,6 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify"; import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import {mergeActionMeta, timedQuery} from "../../../helpers/functions"; import {mergeActionMeta, timedQuery} from "../../../helpers/functions";
import * as flatstr from 'flatstr'; import flatstr from 'flatstr';
const terms = ["notified", "act.authorization.actor"]; const terms = ["notified", "act.authorization.actor"];
const extendedActions = new Set(["transfer", "newaccount", "updateauth"]); const extendedActions = new Set(["transfer", "newaccount", "updateauth"]);
@@ -12,7 +11,7 @@ async function getActions(fastify: FastifyInstance, request: FastifyRequest) {
request.body = JSON.parse(request.body); request.body = JSON.parse(request.body);
} }
const reqBody = request.body; const reqBody = request.body as any;
const should_array = []; const should_array = [];
for (const entry of terms) { for (const entry of terms) {
const tObj = {term: {}}; const tObj = {term: {}};
@@ -191,7 +190,7 @@ async function getActions(fastify: FastifyInstance, request: FastifyRequest) {
} }
export function getActionsHandler(fastify: FastifyInstance, route: string) { 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)); reply.send(await timedQuery(getActions, fastify, request, route));
} }
} }
@@ -1,72 +1,72 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify"; import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import {timedQuery} from "../../../helpers/functions"; import {timedQuery} from "../../../helpers/functions";
import {ApiResponse} from "@elastic/elasticsearch"; import {ApiResponse} from "@elastic/elasticsearch";
async function getControlledAccounts(fastify: FastifyInstance, request: FastifyRequest) { async function getControlledAccounts(fastify: FastifyInstance, request: FastifyRequest) {
if (typeof request.body === 'string') { if (typeof request.body === 'string') {
request.body = JSON.parse(request.body) request.body = JSON.parse(request.body)
} }
let controlling_account = request.body.controlling_account; let controlling_account = request.body["controlling_account"];
const results: ApiResponse = await fastify.elastic.search({ const results: ApiResponse = await fastify.elastic.search({
index: fastify.manager.chain + '-action-*', index: fastify.manager.chain + '-action-*',
size: 100, size: 100,
body: { body: {
query: { query: {
bool: { bool: {
should: [ should: [
{ {
term: {"@updateauth.auth.accounts.permission.actor": controlling_account}}, term: {"@updateauth.auth.accounts.permission.actor": controlling_account}
{ },
bool: { {
must: [ bool: {
{term: {"act.account": "eosio"}}, must: [
{term: {"act.name": "newaccount"}}, {term: {"act.account": "eosio"}},
{term: {"act.authorization.actor": controlling_account}} {term: {"act.name": "newaccount"}},
] {term: {"act.authorization.actor": controlling_account}}
} ]
} }
], }
minimum_should_match: 1 ],
} minimum_should_match: 1
}, }
sort: [ },
{"global_sequence": {"order": "desc"}} sort: [
] {"global_sequence": {"order": "desc"}}
} ]
}); }
});
const response = { const response = {
controlled_accounts: [] controlled_accounts: []
}; };
const hits = results.body.hits.hits; const hits = results.body.hits.hits;
if (hits.length > 0) { if (hits.length > 0) {
response.controlled_accounts = hits.map((v) => { response.controlled_accounts = hits.map((v) => {
if (v._source.act.name === 'newaccount') { if (v._source.act.name === 'newaccount') {
if (v._source['@newaccount'].newact) { if (v._source['@newaccount'].newact) {
return v._source['@newaccount'].newact; return v._source['@newaccount'].newact;
} else if (v._source.act.data.newact) { } else if (v._source.act.data.newact) {
return v._source.act.data.newact; return v._source.act.data.newact;
} else { } else {
return null; return null;
} }
} else if (v._source.act.name === 'updateauth') { } else if (v._source.act.name === 'updateauth') {
return v._source.act.data.account; return v._source.act.data.account;
} else { } else {
return null; return null;
} }
}); });
} }
if (response.controlled_accounts.length > 0) { if (response.controlled_accounts.length > 0) {
response.controlled_accounts = [...(new Set(response.controlled_accounts))]; response.controlled_accounts = [...(new Set(response.controlled_accounts))];
} }
return response; return response;
} }
export function getControlledAccountsHandler(fastify: FastifyInstance, route: string) { export function getControlledAccountsHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => { return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getControlledAccounts, fastify, request, route)); reply.send(await timedQuery(getControlledAccounts, fastify, request, route));
} }
} }
@@ -1,31 +1,21 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify"; import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import {mergeActionMeta, timedQuery} from "../../../helpers/functions"; import {mergeActionMeta, timedQuery} from "../../../helpers/functions";
import {createHash} from "crypto"; 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) { async function getTransaction(fastify: FastifyInstance, request: FastifyRequest) {
if (typeof request.body === 'string') { if (typeof request.body === 'string') {
request.body = JSON.parse(request.body) request.body = JSON.parse(request.body)
} }
const pResults = await Promise.all([fastify.eosjs.rpc.get_info(), fastify.elastic['search']({ const body: any = request.body;
"index": fastify.manager.chain + '-action-*', const redis = fastify.redis;
"body": { const trxId = body.id.toLowerCase();
"query": { const conf = fastify.manager.config;
"bool": { const cachedData = await redis.hgetall('trx_' + trxId);
must: [
{term: {"trx_id": request.body.id.toLowerCase()}}
]
}
},
"sort": {
"global_sequence": "asc"
}
}
})]);
const results = pResults[1];
const response: any = { const response: any = {
"id": request.body.id, "id": body.id,
"trx": { "trx": {
"receipt": { "receipt": {
"status": "executed", "status": "executed",
@@ -42,11 +32,89 @@ async function getTransaction(fastify: FastifyInstance, request: FastifyRequest)
}, },
"block_num": 0, "block_num": 0,
"block_time": "", "block_time": "",
"last_irreversible_block": pResults[0].last_irreversible_block_num, "last_irreversible_block": undefined,
"traces": [] "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) { if (hits.length > 0) {
const actions = hits; const actions = hits;
@@ -98,7 +166,7 @@ async function getTransaction(fastify: FastifyInstance, request: FastifyRequest)
except: null, except: null,
inline_traces: [], inline_traces: [],
producer_block_id: "", producer_block_id: "",
trx_id: request.body.id, trx_id: body.id,
notified: action.notified notified: action.notified
}; };
let hash = createHash('sha256'); let hash = createHash('sha256');
@@ -157,7 +225,7 @@ async function getTransaction(fastify: FastifyInstance, request: FastifyRequest)
except: null, except: null,
inline_traces: [], inline_traces: [],
producer_block_id: "", producer_block_id: "",
trx_id: request.body.id, trx_id: body.id,
}; };
traces[action.global_sequence].inline_traces.unshift(trace); traces[action.global_sequence].inline_traces.unshift(trace);
response.traces.push(trace); response.traces.push(trace);
@@ -166,7 +234,7 @@ async function getTransaction(fastify: FastifyInstance, request: FastifyRequest)
} }
}); });
} else { } 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 { return {
code: 500, code: 500,
message: "Internal Service Error", message: "Internal Service Error",
@@ -189,7 +257,7 @@ async function getTransaction(fastify: FastifyInstance, request: FastifyRequest)
} }
export function getTransactionHandler(fastify: FastifyInstance, route: string) { 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)); reply.send(await timedQuery(getTransaction, fastify, request, route));
} }
} }
@@ -9,7 +9,10 @@ export default function (fastify: FastifyInstance, opts: any, next) {
tags: ['history'], tags: ['history'],
body: { body: {
type: ['object', 'string'], 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"] required: ["id"]
} }
}); });
+2 -3
View File
@@ -1,5 +1,4 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify"; import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import {timedQuery} from "../../../helpers/functions"; import {timedQuery} from "../../../helpers/functions";
import * as _ from "lodash"; import * as _ from "lodash";
@@ -19,7 +18,7 @@ async function getBlockTrace(fastify: FastifyInstance, request: FastifyRequest)
request.body = JSON.parse(request.body); request.body = JSON.parse(request.body);
} }
const reqBody = request.body; const reqBody: any = request.body;
const targetBlock = parseInt(reqBody.block_num); const targetBlock = parseInt(reqBody.block_num);
let searchBody; let searchBody;
@@ -121,7 +120,7 @@ async function getBlockTrace(fastify: FastifyInstance, request: FastifyRequest)
} }
export function getBlockTraceHandler(fastify: FastifyInstance, route: string) { 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)); 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 {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import {timedQuery} from "../../../helpers/functions"; import {timedQuery} from "../../../helpers/functions";
async function getAbiSnapshot(fastify: FastifyInstance, request: FastifyRequest) { async function getAbiSnapshot(fastify: FastifyInstance, request: FastifyRequest) {
@@ -8,9 +7,11 @@ async function getAbiSnapshot(fastify: FastifyInstance, request: FastifyRequest)
block_num: null block_num: null
}; };
const code = request.query.contract; const query: any = request.query;
const block = request.query.block;
const should_fetch = request.query.fetch; const code = query.contract;
const block = query.block;
const should_fetch = query.fetch;
const mustArray = []; const mustArray = [];
@@ -21,7 +22,7 @@ async function getAbiSnapshot(fastify: FastifyInstance, request: FastifyRequest)
} }
const results = await fastify.elastic.search({ const results = await fastify.elastic.search({
index: fastify.manager.chain + '-abi', index: fastify.manager.chain + '-abi-*',
size: 1, size: 1,
body: { body: {
query: {bool: {must: mustArray}}, query: {bool: {must: mustArray}},
@@ -44,7 +45,7 @@ async function getAbiSnapshot(fastify: FastifyInstance, request: FastifyRequest)
} }
export function getAbiSnapshotHandler(fastify: FastifyInstance, route: string) { 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)); 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) { export function addSortedBy(query, queryBody, sort_direction) {
if (query['sortedBy']) { if (query['sortedBy']) {
const opts = query['sortedBy'].split(":"); const opts = query['sortedBy'].split(":");
const sortedByObj = {}; const sortedByObj = {};
sortedByObj[opts[0]] = opts[1]; sortedByObj[opts[0]] = opts[1];
queryBody['sort'] = sortedByObj; queryBody['sort'] = sortedByObj;
} else { } else {
queryBody['sort'] = { queryBody['sort'] = {
"global_sequence": sort_direction "global_sequence": sort_direction
}; };
} }
} }
export function processMultiVars(queryStruct, parts, field) { export function processMultiVars(queryStruct, parts, field) {
const must = []; const must = [];
const mustNot = []; const mustNot = [];
parts.forEach(part => { parts.forEach(part => {
if (part.startsWith("!")) { if (part.startsWith("!")) {
mustNot.push(part.replace("!", "")); mustNot.push(part.replace("!", ""));
} else { } else {
must.push(part); must.push(part);
} }
}); });
if (must.length > 1) { if (must.length > 1) {
queryStruct.bool.must.push({ queryStruct.bool.must.push({
bool: { bool: {
should: must.map(elem => { should: must.map(elem => {
const _q = {}; const _q = {};
_q[field] = elem; _q[field] = elem;
return {term: _q} return {term: _q}
}) })
} }
}); });
} else if (must.length === 1) { } else if (must.length === 1) {
const mustQuery = {}; const mustQuery = {};
mustQuery[field] = must[0]; mustQuery[field] = must[0];
queryStruct.bool.must.push({term: mustQuery}); queryStruct.bool.must.push({term: mustQuery});
} }
if (mustNot.length > 1) { if (mustNot.length > 1) {
queryStruct.bool.must_not.push({ queryStruct.bool.must_not.push({
bool: { bool: {
should: mustNot.map(elem => { should: mustNot.map(elem => {
const _q = {}; const _q = {};
_q[field] = elem; _q[field] = elem;
return {term: _q} return {term: _q}
}) })
} }
}); });
} else if (mustNot.length === 1) { } else if (mustNot.length === 1) {
const mustNotQuery = {}; const mustNotQuery = {};
mustNotQuery[field] = mustNot[0].replace("!", ""); mustNotQuery[field] = mustNot[0].replace("!", "");
queryStruct.bool.must_not.push({term: mustNotQuery}); queryStruct.bool.must_not.push({term: mustNotQuery});
} }
} }
function addRangeQuery(queryStruct, prop, pkey, query) { function addRangeQuery(queryStruct, prop, pkey, query) {
const _termQuery = {}; const _termQuery = {};
const parts = query[prop].split("-"); const parts = query[prop].split("-");
_termQuery[pkey] = { _termQuery[pkey] = {
"gte": parts[0], "gte": parts[0],
"lte": parts[1] "lte": parts[1]
}; };
queryStruct.bool.must.push({range: _termQuery}); queryStruct.bool.must.push({range: _termQuery});
} }
export function applyTimeFilter(query, queryStruct) { export function applyTimeFilter(query, queryStruct) {
if (query['after'] || query['before']) { if (query['after'] || query['before']) {
let _lte = "now"; let _lte = "now";
let _gte = "0"; let _gte = "0";
if (query['before']) { if (query['before']) {
_lte = query['before']; try {
if (!_lte.endsWith("Z")) { _lte = new Date(query['before']).toISOString();
_lte += "Z"; } catch (e) {
} throw new Error(e.message + ' [before]');
} }
if (query['after']) { }
_gte = query['after']; if (query['after']) {
if (!_gte.endsWith("Z")) { try {
_gte += "Z"; _gte = new Date(query['after']).toISOString();
} } catch (e) {
} throw new Error(e.message + ' [after]');
if (!queryStruct.bool['filter']) { }
queryStruct.bool['filter'] = []; }
} if (!queryStruct.bool['filter']) {
queryStruct.bool['filter'].push({ queryStruct.bool['filter'] = [];
range: { }
"@timestamp": { queryStruct.bool['filter'].push({
"gte": _gte, range: {
"lte": _lte "@timestamp": {
} "gte": _gte,
} "lte": _lte
}); }
} }
});
}
} }
export function applyGenericFilters(query, queryStruct) { export function applyGenericFilters(query, queryStruct, allowedExtraParams: Set<string>) {
for (const prop in query) { for (const prop in query) {
if (Object.prototype.hasOwnProperty.call(query, prop)) { if (Object.prototype.hasOwnProperty.call(query, prop)) {
const pair = prop.split("."); const pair = prop.split(".");
if (pair.length > 1 || primaryTerms.includes(pair[0])) { if (pair.length > 1 || primaryTerms.includes(pair[0])) {
let pkey; let pkey;
if (pair.length > 1) { if (pair.length > 1 && allowedExtraParams) {
pkey = extendedActions.has(pair[0]) ? "@" + prop : prop; pkey = allowedExtraParams.has(pair[0]) ? "@" + prop : prop;
} else { } else {
pkey = prop; pkey = prop;
} }
if (query[prop].indexOf("-") !== -1) { if (query[prop].indexOf("-") !== -1) {
addRangeQuery(queryStruct, prop, pkey, query); addRangeQuery(queryStruct, prop, pkey, query);
} else { } else {
const _termQuery = {}; const _qObj = {};
const parts = query[prop].split(","); const parts = query[prop].split(",");
if (parts.length > 1) { if (parts.length > 1) {
processMultiVars(queryStruct, parts, prop); processMultiVars(queryStruct, parts, prop);
} else if (parts.length === 1) { } else if (parts.length === 1) {
const andParts = parts[0].split(" ");
if (andParts.length > 1) { // @transfer.memo special case
andParts.forEach(value => { if (pkey === '@transfer.memo') {
const _q = {}; _qObj[pkey] = {
console.log(value); query: parts[0]
_q[pkey] = value; };
queryStruct.bool.must.push({term: _q});
}); if (query.match_fuzziness) {
} else { _qObj[pkey].fuzziness = query.match_fuzziness;
if (parts[0].startsWith("!")) { }
_termQuery[pkey] = parts[0].replace("!", "");
queryStruct.bool.must_not.push({term: _termQuery}); if (query.match_operator) {
} else { _qObj[pkey].operator = query.match_operator;
_termQuery[pkey] = parts[0]; }
queryStruct.bool.must.push({term: _termQuery});
} 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) { export function makeShouldArray(query) {
const should_array = []; const should_array = [];
for (const entry of terms) { for (const entry of terms) {
const tObj = {term: {}}; const tObj = {term: {}};
tObj.term[entry] = query.account; tObj.term[entry] = query.account;
should_array.push(tObj); should_array.push(tObj);
} }
return should_array; return should_array;
} }
export function applyCodeActionFilters(query, queryStruct) { export function applyCodeActionFilters(query, queryStruct) {
let filterObj = []; let filterObj = [];
if (query.filter) { if (query.filter) {
for (const filter of query.filter.split(',')) { for (const filter of query.filter.split(',')) {
if (filter !== '*:*') { if (filter !== '*:*') {
const _arr = []; const _arr = [];
const parts = filter.split(':'); const parts = filter.split(':');
if (parts.length === 2) { if (parts.length === 2) {
const [code, method] = parts; const [code, method] = parts;
if (code && code !== "*") { if (code && code !== "*") {
_arr.push({'term': {'act.account': code}}); _arr.push({'term': {'act.account': code}});
} }
if (method && method !== "*") { if (method && method !== "*") {
_arr.push({'term': {'act.name': method}}); _arr.push({'term': {'act.name': method}});
} }
} }
if (_arr.length > 0) { if (_arr.length > 0) {
filterObj.push({bool: {must: _arr}}); filterObj.push({bool: {must: _arr}});
} }
} }
} }
if (filterObj.length > 0) { if (filterObj.length > 0) {
queryStruct.bool['should'] = filterObj; queryStruct.bool['should'] = filterObj;
queryStruct.bool['minimum_should_match'] = 1; queryStruct.bool['minimum_should_match'] = 1;
} }
} }
} }
export function getSkipLimit(query, max?: number) { export function getSkipLimit(query, max?: number) {
let skip, limit; let skip, limit;
skip = parseInt(query.skip, 10); skip = parseInt(query.skip, 10);
if (skip < 0) { if (skip < 0) {
throw new Error('invalid skip parameter'); throw new Error('invalid skip parameter');
} }
limit = parseInt(query.limit, 10); limit = parseInt(query.limit, 10);
if (limit < 1) { if (limit < 1) {
throw new Error('invalid limit parameter'); throw new Error('invalid limit parameter');
} else if (limit > max) { } else if (limit > max) {
throw new Error(`limit too big, maximum: ${max}`); throw new Error(`limit too big, maximum: ${max}`);
} }
return {skip, limit}; return {skip, limit};
} }
export function getSortDir(query) { export function getSortDir(query) {
let sort_direction = 'desc'; let sort_direction = 'desc';
if (query.sort) { if (query.sort) {
if (query.sort === 'asc' || query.sort === '1') { if (query.sort === 'asc' || query.sort === '1') {
sort_direction = 'asc'; sort_direction = 'asc';
} else if (query.sort === 'desc' || query.sort === '-1') { } else if (query.sort === 'desc' || query.sort === '-1') {
sort_direction = 'desc' sort_direction = 'desc'
} else { } else {
throw new Error('invalid sort direction'); throw new Error('invalid sort direction');
} }
} }
return sort_direction; return sort_direction;
} }
export function applyAccountFilters(query, queryStruct) { export function applyAccountFilters(query, queryStruct) {
if (query.account) { if (query.account) {
queryStruct.bool.must.push({"bool": {should: makeShouldArray(query)}}); queryStruct.bool.must.push({"bool": {should: makeShouldArray(query)}});
} }
} }
@@ -1,119 +1,118 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify"; import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import {getTrackTotalHits, mergeActionMeta, timedQuery} from "../../../helpers/functions"; import {getTrackTotalHits, mergeActionMeta, timedQuery} from "../../../helpers/functions";
import { import {
addSortedBy, addSortedBy,
applyAccountFilters, applyAccountFilters,
applyCodeActionFilters, applyCodeActionFilters,
applyGenericFilters, applyGenericFilters,
applyTimeFilter, applyTimeFilter,
getSkipLimit, getSkipLimit,
getSortDir getSortDir
} from "./functions"; } from "./functions";
async function getActions(fastify: FastifyInstance, request: FastifyRequest) { async function getActions(fastify: FastifyInstance, request: FastifyRequest) {
const query = request.query; const query: any = request.query;
const maxActions = fastify.manager.config.api.limits.get_actions; const maxActions = fastify.manager.config.api.limits.get_actions;
const queryStruct = { const queryStruct = {
"bool": { "bool": {
must: [], must: [],
must_not: [], must_not: [],
boost: 1.0 boost: 1.0
} }
}; };
const {skip, limit} = getSkipLimit(query, maxActions); const {skip, limit} = getSkipLimit(query, maxActions);
const sort_direction = getSortDir(query); const sort_direction = getSortDir(query);
applyAccountFilters(query, queryStruct); applyAccountFilters(query, queryStruct);
applyGenericFilters(query, queryStruct); applyGenericFilters(query, queryStruct, fastify.allowedActionQueryParamSet);
applyTimeFilter(query, queryStruct); applyTimeFilter(query, queryStruct);
applyCodeActionFilters(query, queryStruct); applyCodeActionFilters(query, queryStruct);
// allow precise counting of total hits // allow precise counting of total hits
const trackTotalHits = getTrackTotalHits(query); const trackTotalHits = getTrackTotalHits(query);
// Prepare query body // Prepare query body
const query_body = { const query_body = {
"track_total_hits": trackTotalHits, "track_total_hits": trackTotalHits,
"query": queryStruct "query": queryStruct
}; };
// Include sorting // Include sorting
addSortedBy(query, query_body, sort_direction); addSortedBy(query, query_body, sort_direction);
// Perform search // Perform search
let indexPattern = fastify.manager.chain + '-action-*'; let indexPattern = fastify.manager.chain + '-action-*';
if (query.hot_only) { if (query.hot_only) {
indexPattern = fastify.manager.chain + '-action'; indexPattern = fastify.manager.chain + '-action';
} }
const esResults = await fastify.elastic.search({ const esResults = await fastify.elastic.search({
"index": indexPattern, "index": indexPattern,
"from": skip || 0, "from": skip || 0,
"size": (limit > maxActions ? maxActions : limit) || 10, "size": (limit > maxActions ? maxActions : limit) || 10,
"body": query_body "body": query_body
}); });
const results = esResults['body']['hits']; const results = esResults['body']['hits'];
const response: any = { const response: any = {
cached: false, cached: false,
lib: 0, lib: 0,
total: results['total'] total: results['total']
}; };
if (query.hot_only) { if (query.hot_only) {
response.hot_only = true; response.hot_only = true;
} }
if (query.checkLib) { if (query.checkLib) {
response.lib = (await fastify.eosjs.rpc.get_info()).last_irreversible_block_num; response.lib = (await fastify.eosjs.rpc.get_info()).last_irreversible_block_num;
} }
if (query.simple) { if (query.simple) {
response['simple_actions'] = []; response['simple_actions'] = [];
} else { } else {
response['actions'] = []; response['actions'] = [];
} }
if (results['hits'].length > 0) { if (results['hits'].length > 0) {
const actions = results['hits']; const actions = results['hits'];
for (let action of actions) { for (let action of actions) {
action = action._source; action = action._source;
mergeActionMeta(action); mergeActionMeta(action);
if (query.noBinary === true) { if (query.noBinary === true) {
for (const key in action['act']['data']) { for (const key in action['act']['data']) {
if (action['act']['data'].hasOwnProperty(key)) { if (action['act']['data'].hasOwnProperty(key)) {
if (typeof action['act']['data'][key] === 'string' && action['act']['data'][key].length > 256) { if (typeof action['act']['data'][key] === 'string' && action['act']['data'][key].length > 256) {
action['act']['data'][key] = action['act']['data'][key].slice(0, 32) + "..."; action['act']['data'][key] = action['act']['data'][key].slice(0, 32) + "...";
} }
} }
} }
} }
if (query.simple) { if (query.simple) {
response.simple_actions.push({ response.simple_actions.push({
block: action['block_num'], block: action['block_num'],
irreversible: response.lib !== 0 ? action['block_num'] < response.lib : undefined, irreversible: response.lib !== 0 ? action['block_num'] < response.lib : undefined,
timestamp: action['@timestamp'], timestamp: action['@timestamp'],
transaction_id: action['trx_id'], transaction_id: action['trx_id'],
actors: action['act']['authorization'].map(a => `${a.actor}@${a.permission}`).join(","), actors: action['act']['authorization'].map(a => `${a.actor}@${a.permission}`).join(","),
notified: action['notified'].join(','), notified: action['notified'].join(','),
contract: action['act']['account'], contract: action['act']['account'],
action: action['act']['name'], action: action['act']['name'],
data: action['act']['data'] data: action['act']['data']
}); });
} else { } else {
response.actions.push(action); response.actions.push(action);
} }
} }
} }
return response; return response;
} }
export function getActionsHandler(fastify: FastifyInstance, route: string) { 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)); 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 {getActionsHandler} from "./get_actions";
import {addApiRoute, extendQueryStringSchema, extendResponseSchema, getRouteName} from "../../../helpers/functions"; import {addApiRoute, extendQueryStringSchema, extendResponseSchema, getRouteName} from "../../../helpers/functions";
export const getActionResponseSchema = { export const getActionResponseSchema = {
"@timestamp": {type: "string"}, "@timestamp": {type: "string"},
"timestamp": {type: "string"}, "timestamp": {type: "string"},
"block_num": {type: "number"}, "block_num": {type: "number"},
"trx_id": {type: "string"}, "trx_id": {type: "string"},
"act": { "act": {
type: 'object', type: 'object',
properties: { properties: {
"account": {type: "string"}, "account": {type: "string"},
"name": {type: "string"} "name": {type: "string"}
}, },
additionalProperties: true additionalProperties: true
}, },
"notified": { "notified": {
type: "array", items: {type: "string"} type: "array", items: {type: "string"}
}, },
"cpu_usage_us": {type: "number"}, "cpu_usage_us": {type: "number"},
"net_usage_words": {type: "number"}, "net_usage_words": {type: "number"},
"account_ram_deltas": { "account_ram_deltas": {
type: "array", type: "array",
items: { items: {
type: "object", type: "object",
properties: { properties: {
"account": {type: "string"}, "account": {type: "string"},
"delta": {type: "number"} "delta": {type: "number"}
}, },
additionalProperties: true additionalProperties: true
} }
}, },
"global_sequence": {type: "number"}, "global_sequence": {type: "number"},
"receiver": {type: 'string'}, "receiver": {type: 'string'},
"producer": {type: "string"}, "producer": {type: "string"},
"parent": {type: "number"}, "parent": {type: "number"},
"action_ordinal": {type: 'number'}, "action_ordinal": {type: 'number'},
"creator_action_ordinal": {type: 'number'} "creator_action_ordinal": {type: 'number'},
"signatures": {type: "array", items: {type: 'string'}}
}; };
export default function (fastify: FastifyInstance, opts: any, next) { export default function (fastify: FastifyInstance, opts: any, next) {
const schema: RouteSchema = { const schema: FastifySchema = {
description: 'get actions based on notified account. this endpoint also accepts generic filters based on indexed fields' + 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', ' (e.g. act.authorization.actor=eosio or act.name=delegatebw), if included they will be combined with a AND operator',
summary: 'get root actions', summary: 'get root actions',
tags: ['history'], tags: ['history'],
querystring: extendQueryStringSchema({ querystring: extendQueryStringSchema({
"account": { "account": {
description: 'notified account', description: 'notified account',
type: 'string', type: 'string',
minLength: 1, minLength: 1,
maxLength: 12 maxLength: 12
}, },
"track": { "track": {
description: 'total results to track (count) [number or true]', description: 'total results to track (count) [number or true]',
type: 'string' type: 'string'
}, },
"filter": { "filter": {
description: 'code:name filter', description: 'code:name filter',
type: 'string', type: 'string',
minLength: 3 minLength: 3
}, },
"sort": { "sort": {
description: 'sort direction', description: 'sort direction',
enum: ['desc', 'asc', '1', '-1'], enum: ['desc', 'asc', '1', '-1'],
type: 'string' type: 'string'
}, },
"after": { "after": {
description: 'filter after specified date (ISO8601)', description: 'filter after specified date (ISO8601)',
type: 'string' type: 'string'
}, },
"before": { "before": {
description: 'filter before specified date (ISO8601)', description: 'filter before specified date (ISO8601)',
type: 'string' type: 'string'
}, },
"simple": { "simple": {
description: 'simplified output mode', description: 'simplified output mode',
type: 'boolean' type: 'boolean'
}, },
"hot_only": { "hot_only": {
description: 'search only the latest hot index', description: 'search only the latest hot index',
type: 'boolean' type: 'boolean'
}, },
"noBinary": { "noBinary": {
description: "exclude large binary data", description: "exclude large binary data",
type: 'boolean' type: 'boolean'
}, },
"checkLib": { "checkLib": {
description: "perform reversibility check", description: "perform reversibility check",
type: 'boolean' type: 'boolean'
}, },
}), }),
response: extendResponseSchema({ response: extendResponseSchema({
"simple_actions": { "simple_actions": {
type: "array", type: "array",
items: { items: {
type: "object", type: "object",
properties: { properties: {
"block": {type: "number"}, "block": {type: "number"},
"timestamp": {type: "string"}, "timestamp": {type: "string"},
"irreversible": {type: "boolean"}, "irreversible": {type: "boolean"},
"contract": {type: "string"}, "contract": {type: "string"},
"action": {type: "string"}, "action": {type: "string"},
"actors": {type: "string"}, "actors": {type: "string"},
"notified": {type: "string"}, "notified": {type: "string"},
"transaction_id": {type: "string"}, "transaction_id": {type: "string"},
"data": { "data": {
additionalProperties: true additionalProperties: true
} }
} }
} }
}, },
"actions": { "actions": {
type: "array", type: "array",
items: { items: {
type: 'object', type: 'object',
properties: getActionResponseSchema properties: getActionResponseSchema
} }
} }
}) })
}; };
addApiRoute( addApiRoute(
fastify, fastify,
'GET', 'GET',
getRouteName(__filename), getRouteName(__filename),
getActionsHandler, getActionsHandler,
schema schema
); );
next(); next();
} }
@@ -1,58 +1,58 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify"; import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import {timedQuery} from "../../../helpers/functions"; import {timedQuery} from "../../../helpers/functions";
import {getSkipLimit} from "../get_actions/functions"; import {getSkipLimit} from "../get_actions/functions";
async function getCreatedAccounts(fastify: FastifyInstance, request: FastifyRequest) { async function getCreatedAccounts(fastify: FastifyInstance, request: FastifyRequest) {
const {skip, limit} = getSkipLimit(request.query); const query: any = request.query;
const maxActions = fastify.manager.config.api.limits.get_created_accounts; const {skip, limit} = getSkipLimit(query);
const results = await fastify.elastic.search({ const maxActions = fastify.manager.config.api.limits.get_created_accounts;
"index": fastify.manager.chain + '-action-*', const results = await fastify.elastic.search({
"from": skip || 0, "index": fastify.manager.chain + '-action-*',
"size": (limit > maxActions ? maxActions : limit) || 100, "from": skip || 0,
"body": { "size": (limit > maxActions ? maxActions : limit) || 100,
"query": { "body": {
"bool": { "query": {
must: [ "bool": {
{term: {"act.authorization.actor": request.query.account.toLowerCase()}}, must: [
{term: {"act.name": "newaccount"}}, {term: {"act.authorization.actor": query.account.toLowerCase()}},
{term: {"act.account": "eosio"}} {term: {"act.name": "newaccount"}},
] {term: {"act.account": "eosio"}}
} ]
}, }
sort: { },
"global_sequence": "desc" sort: {
} "global_sequence": "desc"
} }
}); }
});
const response = {accounts: []}; const response = {accounts: []};
if (results['body']['hits']['hits'].length > 0) { if (results['body']['hits']['hits'].length > 0) {
const actions = results['body']['hits']['hits']; const actions = results['body']['hits']['hits'];
for (let action of actions) { for (let action of actions) {
action = action._source; action = action._source;
const _tmp = {}; const _tmp = {};
if (action['act']['data']['newact']) { if (action['act']['data']['newact']) {
_tmp['name'] = action['act']['data']['newact']; _tmp['name'] = action['act']['data']['newact'];
} else if (action['@newaccount']['newact']) { } else if (action['@newaccount']['newact']) {
_tmp['name'] = action['@newaccount']['newact']; _tmp['name'] = action['@newaccount']['newact'];
} else { } else {
console.log(action); console.log(action);
} }
_tmp['trx_id'] = action['trx_id']; _tmp['trx_id'] = action['trx_id'];
_tmp['timestamp'] = action['@timestamp']; _tmp['timestamp'] = action['@timestamp'];
response.accounts.push(_tmp); response.accounts.push(_tmp);
} }
} }
return response; return response;
} }
export function getCreatedAccountsHandler(fastify: FastifyInstance, route: string) { export function getCreatedAccountsHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => { return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getCreatedAccounts, fastify, request, route)); 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 {getCreatedAccountsHandler} from "./get_created_accounts";
import {addApiRoute, extendQueryStringSchema, extendResponseSchema, getRouteName} from "../../../helpers/functions"; import {addApiRoute, extendQueryStringSchema, extendResponseSchema, getRouteName} from "../../../helpers/functions";
export default function (fastify: FastifyInstance, opts: any, next) { export default function (fastify: FastifyInstance, opts: any, next) {
const schema: RouteSchema = { const schema: FastifySchema = {
description: 'get all accounts created by one creator', description: 'get all accounts created by one creator',
summary: 'get created accounts', summary: 'get created accounts',
tags: ['accounts'], tags: ['accounts'],
querystring: extendQueryStringSchema({ querystring: extendQueryStringSchema({
"account": { "account": {
description: 'creator account', description: 'creator account',
type: 'string', type: 'string',
minLength: 1, minLength: 1,
maxLength: 12 maxLength: 12
} }
}, ["account"]), }, ["account"]),
response: extendResponseSchema({ response: extendResponseSchema({
"query_time": { "query_time": {
type: "number" type: "number"
}, },
"total": { "total": {
type: "object", type: "object",
properties: { properties: {
"value": {type: "number"}, "value": {type: "number"},
"relation": {type: "string"} "relation": {type: "string"}
} }
}, },
"accounts": { "accounts": {
type: "array", type: "array",
items: { items: {
type: 'object', type: 'object',
properties: { properties: {
'name': {type: 'string'}, 'name': {type: 'string'},
'timestamp': {type: 'string'}, 'timestamp': {type: 'string'},
'trx_id': {type: 'string'} 'trx_id': {type: 'string'}
} }
} }
} }
}) })
}; };
addApiRoute(fastify, 'GET', getRouteName(__filename), getCreatedAccountsHandler, schema); addApiRoute(fastify, 'GET', getRouteName(__filename), getCreatedAccountsHandler, schema);
next(); next();
} }
@@ -1,18 +1,19 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify"; import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import {timedQuery} from "../../../helpers/functions"; import {timedQuery} from "../../../helpers/functions";
async function getCreator(fastify: FastifyInstance, request: FastifyRequest) { async function getCreator(fastify: FastifyInstance, request: FastifyRequest) {
const query: any = request.query;
const response = { const response = {
account: request.query.account, account: query.account,
creator: '', creator: '',
timestamp: '', timestamp: '',
block_num: 0, block_num: 0,
trx_id: '', 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); const genesisBlock = await fastify.eosjs.rpc.get_block(1);
if (genesisBlock) { if (genesisBlock) {
response.creator = '__self__'; response.creator = '__self__';
@@ -31,7 +32,7 @@ async function getCreator(fastify: FastifyInstance, request: FastifyRequest) {
size: 1, size: 1,
query: { query: {
bool: { 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 { } else {
let accountInfo; let accountInfo;
try { try {
accountInfo = await fastify.eosjs.rpc.get_account(request.query.account); accountInfo = await fastify.eosjs.rpc.get_account(query.account);
} catch (e) { } catch (e) {
throw new Error("account not found"); throw new Error("account not found");
} }
@@ -73,7 +74,7 @@ async function getCreator(fastify: FastifyInstance, request: FastifyRequest) {
const actions = transaction.trx.transaction.actions; const actions = transaction.trx.transaction.actions;
for (const act of actions) { for (const act of actions) {
if (act.name === 'newaccount') { if (act.name === 'newaccount') {
if (act.data.name === request.query.account) { if (act.data.name === query.account) {
response.creator = act.data.creator; response.creator = act.data.creator;
response.trx_id = transaction.id; response.trx_id = transaction.id;
return response; return response;
@@ -93,7 +94,7 @@ async function getCreator(fastify: FastifyInstance, request: FastifyRequest) {
} }
export function getCreatorHandler(fastify: FastifyInstance, route: string) { 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)); 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 {getCreatorHandler} from "./get_creator";
import {addApiRoute, extendResponseSchema, getRouteName} from "../../../helpers/functions"; import {addApiRoute, extendResponseSchema, getRouteName} from "../../../helpers/functions";
export default function (fastify: FastifyInstance, opts: any, next) { export default function (fastify: FastifyInstance, opts: any, next) {
const schema: RouteSchema = { const schema: FastifySchema = {
description: 'get account creator', description: 'get account creator',
summary: 'get account creator', summary: 'get account creator',
tags: ['accounts'], tags: ['accounts'],
querystring: { querystring: {
type: 'object', type: 'object',
properties: { properties: {
"account": { "account": {
description: 'created account', description: 'created account',
type: 'string', type: 'string',
minLength: 1, minLength: 1,
maxLength: 12 maxLength: 12
} }
}, },
required: ["account"] required: ["account"]
}, },
response: extendResponseSchema({ response: extendResponseSchema({
"account": { "account": {
type: "string" type: "string"
}, },
"creator": { "creator": {
type: "string" type: "string"
}, },
"timestamp": { "timestamp": {
type: "string" type: "string"
}, },
"block_num": { "block_num": {
type: "integer" type: "integer"
}, },
"trx_id": { "trx_id": {
type: "string" type: "string"
}, },
"indirect_creator": { "indirect_creator": {
type: "string" type: "string"
} }
}) })
}; };
addApiRoute(fastify, 'GET', getRouteName(__filename), getCreatorHandler, schema); addApiRoute(fastify, 'GET', getRouteName(__filename), getCreatorHandler, schema);
next(); next();
} }
@@ -1,5 +1,4 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify"; import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import {mergeDeltaMeta, timedQuery} from "../../../helpers/functions"; import {mergeDeltaMeta, timedQuery} from "../../../helpers/functions";
import {applyTimeFilter} from "../get_actions/functions"; import {applyTimeFilter} from "../get_actions/functions";
@@ -7,9 +6,10 @@ async function getDeltas(fastify: FastifyInstance, request: FastifyRequest) {
let skip, limit; let skip, limit;
let sort_direction = 'desc'; let sort_direction = 'desc';
const mustArray = []; const mustArray = [];
for (const param in request.query) { const query: any = request.query;
if (Object.prototype.hasOwnProperty.call(request.query, param)) { for (const param in query) {
const value = request.query[param]; if (Object.prototype.hasOwnProperty.call(query, param)) {
const value = query[param];
switch (param) { switch (param) {
case 'limit': { case 'limit': {
limit = parseInt(value, 10); limit = parseInt(value, 10);
@@ -42,7 +42,7 @@ async function getDeltas(fastify: FastifyInstance, request: FastifyRequest) {
break; break;
} }
default: { default: {
const values = request.query[param].split(","); const values = query[param].split(",");
if (values.length > 1) { if (values.length > 1) {
const terms = {}; const terms = {};
terms[param] = values; 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 maxDeltas = fastify.manager.config.api.limits.get_deltas ?? 1000;
const queryStruct = {bool: {must: mustArray}}; const queryStruct = {bool: {must: mustArray}};
applyTimeFilter(request.query, queryStruct); applyTimeFilter(query, queryStruct);
const results = await fastify.elastic.search({ const results = await fastify.elastic.search({
"index": fastify.manager.chain + '-delta-*', "index": fastify.manager.chain + '-delta-*',
@@ -85,7 +85,7 @@ async function getDeltas(fastify: FastifyInstance, request: FastifyRequest) {
} }
export function getDeltasHandler(fastify: FastifyInstance, route: string) { 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)); 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 {getDeltasHandler} from "./get_deltas";
import {addApiRoute, extendQueryStringSchema, extendResponseSchema, getRouteName} from "../../../helpers/functions"; import {addApiRoute, extendQueryStringSchema, extendResponseSchema, getRouteName} from "../../../helpers/functions";
export default function (fastify: FastifyInstance, opts: any, next) { export default function (fastify: FastifyInstance, opts: any, next) {
const schema: RouteSchema = { const schema: FastifySchema = {
description: 'get state deltas', description: 'get state deltas',
summary: 'get state deltas', summary: 'get state deltas',
tags: ['history'], tags: ['history'],
@@ -31,7 +31,11 @@ export default function (fastify: FastifyInstance, opts: any, next) {
"before": { "before": {
description: 'filter before specified date (ISO8601)', description: 'filter before specified date (ISO8601)',
type: 'string' type: 'string'
} },
"present": {
description: 'delta present flag',
type: 'number'
},
}), }),
response: extendResponseSchema({ response: extendResponseSchema({
"deltas": { "deltas": {
@@ -40,12 +44,12 @@ export default function (fastify: FastifyInstance, opts: any, next) {
type: 'object', type: 'object',
properties: { properties: {
"timestamp": {type: 'string'}, "timestamp": {type: 'string'},
"present": {type: 'number'},
"code": {type: 'string'}, "code": {type: 'string'},
"scope": {type: 'string'}, "scope": {type: 'string'},
"table": {type: 'string'}, "table": {type: 'string'},
"primary_key": {type: 'string'}, "primary_key": {type: 'string'},
"payer": {type: 'string'}, "payer": {type: 'string'},
"present": {type: 'boolean'},
"block_num": {type: 'number'}, "block_num": {type: 'number'},
"block_id": {type: 'string'}, "block_id": {type: 'string'},
"data": { "data": {
@@ -1,71 +1,73 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify"; import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import {timedQuery} from "../../../helpers/functions"; import {timedQuery} from "../../../helpers/functions";
import {createHash} from "crypto"; import {createHash} from "crypto";
import {base58ToBinary, binaryToBase58} from "eosjs/dist/eosjs-numeric"; import {base58ToBinary, binaryToBase58} from "eosjs/dist/eosjs-numeric";
import {Search} from "@elastic/elasticsearch/api/requestParams"; import {Search} from "@elastic/elasticsearch/api/requestParams";
function convertToLegacyKey(block_signing_key: string) { function convertToLegacyKey(block_signing_key: string) {
if (block_signing_key.startsWith("PUB_K1_")) { if (block_signing_key.startsWith("PUB_K1_")) {
const buf = base58ToBinary(37, block_signing_key.substr(7)); const buf = base58ToBinary(37, block_signing_key.substr(7));
const data = buf.slice(0, buf.length - 4); const data = buf.slice(0, buf.length - 4);
const merged = Buffer.concat([ const merged = Buffer.concat([
data, data,
createHash('ripemd160') createHash('ripemd160')
.update(data) .update(data)
.digest() .digest()
.slice(0, 4) .slice(0, 4)
]); ]);
return "EOS" + binaryToBase58(merged); return "EOS" + binaryToBase58(merged);
} else { } else {
return block_signing_key; return block_signing_key;
} }
} }
async function getSchedule(fastify: FastifyInstance, request: FastifyRequest) { async function getSchedule(fastify: FastifyInstance, request: FastifyRequest) {
const response: any = {
producers: [] const query: any = request.query;
};
const searchParams: Search<any> = { const response: any = {
track_total_hits: true, producers: []
index: fastify.manager.chain + "-block-*", };
size: 1, const searchParams: Search<any> = {
body: { track_total_hits: true,
query: { index: fastify.manager.chain + "-block-*",
bool: { size: 1,
must: [] body: {
} query: {
}, bool: {
sort: {block_num: "desc"} must: []
} }
}; },
if (request.query.version) { sort: {block_num: "desc"}
searchParams.body.query.bool.must.push({"term": {"new_producers.version": {"value": request.query.version}}}); }
} else { };
searchParams.body.query.bool.must.push({ if (query.version) {
"exists": { searchParams.body.query.bool.must.push({"term": {"new_producers.version": {"value": query.version}}});
"field": "new_producers.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"]; const apiResponse = await fastify.elastic.search(searchParams);
response.block_num = results[0]._source.block_num; const results = apiResponse.body.hits.hits;
response.version = results[0]._source.new_producers.version; if (results) {
response.producers = results[0]._source.new_producers.producers.map(prod => { response.timestamp = results[0]._source["@timestamp"];
return { response.block_num = results[0]._source.block_num;
...prod, response.version = results[0]._source.new_producers.version;
legacy_key: convertToLegacyKey(prod.block_signing_key) response.producers = results[0]._source.new_producers.producers.map(prod => {
} return {
}); ...prod,
} legacy_key: convertToLegacyKey(prod.block_signing_key)
return response; }
});
}
return response;
} }
export function getScheduleHandler(fastify: FastifyInstance, route: string) { export function getScheduleHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => { return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getSchedule, fastify, request, route)); 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 {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import {mergeActionMeta, timedQuery} from "../../../helpers/functions"; import {mergeActionMeta, timedQuery} from "../../../helpers/functions";
import {GetInfoResult} from "eosjs/dist/eosjs-rpc-interfaces";
async function getTransaction(fastify: FastifyInstance, request: FastifyRequest) { async function getTransaction(fastify: FastifyInstance, request: FastifyRequest) {
const redis = fastify.redis; const redis = fastify.redis;
const trxId = request.query.id.toLowerCase(); const query: any = request.query;
const cachedData = await redis.hgetall(trxId); const trxId = query.id.toLowerCase();
const conf = fastify.manager.config;
let hits; const cachedData = await redis.hgetall('trx_' + trxId);
let hits2;
const response = { const response = {
"query_time_ms": undefined, query_time_ms: undefined,
"cached": undefined, executed: false,
"cache_expires_in": undefined, cached: undefined,
"executed": false, cache_expires_in: undefined,
"hot_only": false, trx_id: query.id,
"trx_id": trxId, lib: undefined,
"lib": undefined, cached_lib: false,
"actions": [], actions: undefined,
"generated": 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) { if (cachedData && Object.keys(cachedData).length > 0) {
const gsArr = []; const gsArr = [];
for (let cachedDataKey in cachedData) { for (let cachedDataKey in cachedData) {
@@ -31,83 +52,77 @@ async function getTransaction(fastify: FastifyInstance, request: FastifyRequest)
gsArr.sort((a, b) => { gsArr.sort((a, b) => {
return a.global_sequence - b.global_sequence; return a.global_sequence - b.global_sequence;
}); });
response.cache_expires_in = await redis.ttl(trxId);
hits = gsArr.map(value => { 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) { if (!hits) {
const _size = fastify.manager.config.api.limits.get_trx_actions || 100; const _size = conf.api.limits.get_trx_actions || 100;
let indexPattern = fastify.manager.chain + '-action-*'; const blockHint = parseInt(query.block_hint, 10);
if (request.query.hot_only) { let indexPattern = '';
indexPattern = fastify.manager.chain + '-action'; 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([ // build search request
fastify.eosjs.rpc.get_info(), const $search = fastify.elastic.search({
fastify.elastic.search({
index: indexPattern, index: indexPattern,
size: _size, size: _size,
body: { body: {
query: {bool: {must: [{term: {trx_id: request.query.id.toLowerCase()}}]}}, query: {bool: {must: [{term: {trx_id: trxId}}]}},
sort: {global_sequence: "asc"} 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; 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) {
if (hits.length > 0) { let highestBlockNum = 0;
let highestBlockNum = 0; for (let action of hits) {
for (let action of hits) { if (action._source.block_num > highestBlockNum) {
if (action._source.block_num > highestBlockNum) { highestBlockNum = action._source.block_num;
highestBlockNum = action._source.block_num;
}
} }
for (let action of hits) { }
if (action._source.block_num === highestBlockNum) { response.actions = [];
action = action._source; for (let action of hits) {
mergeActionMeta(action); if (action._source.block_num === highestBlockNum) {
response.actions.push(action); 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; return response;
} }
export function getTransactionHandler(fastify: FastifyInstance, route: string) { 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)); reply.send(await timedQuery(getTransaction, fastify, request, route));
} }
} }
@@ -10,9 +10,13 @@ export default function (fastify: FastifyInstance, opts: any, next) {
querystring: { querystring: {
type: 'object', type: 'object',
properties: { properties: {
"id": { id: {
description: 'transaction id', description: 'transaction id',
type: 'string' type: 'string'
},
block_hint: {
description: 'block hint to speed up tx recovery',
type: 'integer'
} }
}, },
required: ["id"] required: ["id"]
@@ -1,10 +1,12 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify"; import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import got from "got"; import got from "got";
import {timedQuery} from "../../../helpers/functions"; import {timedQuery} from "../../../helpers/functions";
async function getAccount(fastify: FastifyInstance, request: FastifyRequest) { async function getAccount(fastify: FastifyInstance, request: FastifyRequest) {
const query: any = request.query;
const response = { const response = {
account: null, account: null,
actions: null, actions: null,
@@ -13,7 +15,7 @@ async function getAccount(fastify: FastifyInstance, request: FastifyRequest) {
links: null links: null
}; };
const account = request.query.account; const account = query.account;
const reqQueue = []; const reqQueue = [];
try { try {
@@ -45,7 +47,7 @@ async function getAccount(fastify: FastifyInstance, request: FastifyRequest) {
} }
export function getAccountHandler(fastify: FastifyInstance, route: string) { 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)); reply.send(await timedQuery(getAccount, fastify, request, route));
} }
} }
@@ -1,137 +1,139 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify"; import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import {timedQuery} from "../../../helpers/functions"; import {timedQuery} from "../../../helpers/functions";
import {Numeric} from "eosjs/dist"; import {Numeric} from "eosjs/dist";
import {getSkipLimit} from "../../v2-history/get_actions/functions"; import {getSkipLimit} from "../../v2-history/get_actions/functions";
function invalidKey() { function invalidKey() {
const err: any = new Error(); const err: any = new Error();
err.statusCode = 400; err.statusCode = 400;
err.message = 'invalid public key'; err.message = 'invalid public key';
throw err; throw err;
} }
async function getKeyAccounts(fastify: FastifyInstance, request: FastifyRequest) { async function getKeyAccounts(fastify: FastifyInstance, request: FastifyRequest) {
let publicKey; let publicKey;
if (typeof request.body === 'string' && request.req.method === 'POST') { if (typeof request.body === 'string' && request.method === 'POST') {
request.body = JSON.parse(request.body); request.body = JSON.parse(request.body);
} }
const {skip, limit} = getSkipLimit(request.query); const body: any = request.body;
const maxDocs = fastify.manager.config.api.limits.get_key_accounts ?? 1000; 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_")) { const public_Key = request.method === 'POST' ? body.public_key : query.public_key;
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 response = { if (public_Key.startsWith("PUB_")) {
account_names: [] publicKey = public_Key;
} as any; } 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) { const response = {
response.permissions = []; account_names: []
} } as any;
try { if (request.method === 'GET' && query.details) {
response.permissions = [];
}
const permTableResults = await fastify.elastic.search({ try {
index: fastify.manager.chain + '-perm-*',
size: (limit > maxDocs ? maxDocs : limit) || 100,
from: skip || 0,
body: {
query: {
bool: {
must: [
{
term: {
"auth.keys.key.keyword": publicKey
}
}
],
}
}
}
});
if (permTableResults.body.hits.hits.length > 0) { const permTableResults = await fastify.elastic.search({
for (const perm of permTableResults.body.hits.hits) { index: fastify.manager.chain + '-perm-*',
response.account_names.push(perm._source.owner); size: (limit > maxDocs ? maxDocs : limit) || 100,
if (request.req.method === 'GET' && request.query.details) { from: skip || 0,
response.permissions.push(perm._source); body: {
} query: {
} bool: {
} must: [
{
term: {
"auth.keys.key.keyword": publicKey
}
}
],
}
}
}
});
} catch (e) { if (permTableResults.body.hits.hits.length > 0) {
console.log(e); 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) { } catch (e) {
response.account_names = [...(new Set(response.account_names))]; console.log(e);
return response; }
}
// Fallback to action search if (response.account_names.length > 0) {
const _body = { response.account_names = [...(new Set(response.account_names))];
query: { return response;
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"}}]
};
const results = await fastify.elastic.search({ // Fallback to action search
index: fastify.manager.chain + '-action-*', const _body = {
size: (limit > maxDocs ? maxDocs : limit) || 100, query: {
from: skip || 0, bool: {
body: _body 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) { const results = await fastify.elastic.search({
response.account_names = results['body']['hits']['hits'].map((v) => { index: fastify.manager.chain + '-action-*',
if (v._source.act.name === 'newaccount') { size: (limit > maxDocs ? maxDocs : limit) || 100,
if (v._source['@newaccount'].newact) { from: skip || 0,
return v._source['@newaccount'].newact; body: _body
} 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.account_names.length > 0) { if (results['body']['hits']['hits'].length > 0) {
response.account_names = [...(new Set(response.account_names))]; 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) { export function getKeyAccountsHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => { return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getKeyAccounts, fastify, request, route)); reply.send(await timedQuery(getKeyAccounts, fastify, request, route));
} }
} }
@@ -38,7 +38,7 @@ export default function (fastify: FastifyInstance, opts: any, next) {
last_updated: {type: 'string'}, last_updated: {type: 'string'},
auth: {}, auth: {},
name: {type: 'string'}, name: {type: 'string'},
present: {type: 'boolean'} present: {type: 'number'}
} }
} }
} }
+52 -52
View File
@@ -1,71 +1,71 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify"; import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import {getTrackTotalHits, timedQuery} from "../../../helpers/functions"; import {getTrackTotalHits, timedQuery} from "../../../helpers/functions";
import {ApiResponse} from "@elastic/elasticsearch"; import {ApiResponse} from "@elastic/elasticsearch";
import {getSkipLimit} from "../../v2-history/get_actions/functions"; import {getSkipLimit} from "../../v2-history/get_actions/functions";
async function getLinks(fastify: FastifyInstance, request: FastifyRequest) { async function getLinks(fastify: FastifyInstance, request: FastifyRequest) {
const query = request.query; const query: any = request.query;
const {account, code, action, permissions} = query; const {account, code, action, permissions} = query;
const {skip, limit} = getSkipLimit(query); const {skip, limit} = getSkipLimit(query);
const queryStruct = { const queryStruct = {
"bool": { "bool": {
must: [] must: [],
} must_not: []
}; }
};
if (account) { if (account) {
queryStruct.bool.must.push({'term': {'account': account}}); queryStruct.bool.must.push({'term': {'account': account}});
} }
if (code) { if (code) {
queryStruct.bool.must.push({'term': {'code': code}}); queryStruct.bool.must.push({'term': {'code': code}});
} }
if (action) { if (action) {
queryStruct.bool.must.push({'term': {'action': action}}); queryStruct.bool.must.push({'term': {'action': action}});
} }
if (permissions) { if (permissions) {
queryStruct.bool.must.push({'term': {'permissions': permissions}}); queryStruct.bool.must.push({'term': {'permissions': permissions}});
} }
// only present deltas // only present deltas
queryStruct.bool.must.push({'term': {'present': true}}); queryStruct.bool.must_not.push({'term': {'present': 0}});
// Prepare query body // Prepare query body
const query_body = { const query_body = {
track_total_hits: getTrackTotalHits(query), track_total_hits: getTrackTotalHits(query),
query: queryStruct, query: queryStruct,
sort: {block_num: 'desc'} sort: {block_num: 'desc'}
}; };
const maxLinks = fastify.manager.config.api.limits.get_links; const maxLinks = fastify.manager.config.api.limits.get_links;
const results: ApiResponse = await fastify.elastic.search({ const results: ApiResponse = await fastify.elastic.search({
index: fastify.manager.chain + '-link-*', index: fastify.manager.chain + '-link-*',
from: skip || 0, from: skip || 0,
size: (limit > maxLinks ? maxLinks : limit) || 50, size: (limit > maxLinks ? maxLinks : limit) || 50,
body: query_body body: query_body
}); });
const hits = results.body.hits.hits; const hits = results.body.hits.hits;
const response: any = { const response: any = {
cached: false, cached: false,
total: results.body.hits.total, total: results.body.hits.total,
links: [] links: []
}; };
for (const hit of hits) { for (const hit of hits) {
const link = hit._source; const link = hit._source;
link.timestamp = link['@timestamp']; link.timestamp = link['@timestamp'];
delete link['@timestamp']; delete link['@timestamp'];
response.links.push(link); response.links.push(link);
} }
return response; return response;
} }
export function getLinksHandler(fastify: FastifyInstance, route: string) { export function getLinksHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => { return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getLinks, fastify, request, route)); reply.send(await timedQuery(getLinks, fastify, request, route));
} }
} }
@@ -1,16 +1,17 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify"; import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import {getTrackTotalHits, timedQuery} from "../../../helpers/functions"; import {getTrackTotalHits, timedQuery} from "../../../helpers/functions";
async function getProposals(fastify: FastifyInstance, request: FastifyRequest) { async function getProposals(fastify: FastifyInstance, request: FastifyRequest) {
const query: any = request.query;
// Pagination // Pagination
let skip, limit; let skip, limit;
skip = parseInt(request.query.skip, 10); skip = parseInt(query.skip, 10);
if (skip < 0) { if (skip < 0) {
return 'invalid skip parameter'; return 'invalid skip parameter';
} }
limit = parseInt(request.query.limit, 10); limit = parseInt(query.limit, 10);
if (limit < 1) { if (limit < 1) {
return 'invalid limit parameter'; return 'invalid limit parameter';
} }
@@ -22,8 +23,8 @@ async function getProposals(fastify: FastifyInstance, request: FastifyRequest) {
}; };
// Filter by accounts // Filter by accounts
if (request.query.account) { if (query.account) {
const accounts = request.query.account.split(','); const accounts = query.account.split(',');
for(const acc of accounts) { for(const acc of accounts) {
queryStruct.bool.must.push({ queryStruct.bool.must.push({
"bool": { "bool": {
@@ -37,28 +38,28 @@ async function getProposals(fastify: FastifyInstance, request: FastifyRequest) {
} }
// Filter by proposer account // Filter by proposer account
if (request.query.proposer) { if (query.proposer) {
queryStruct.bool.must.push({"term": {"proposer": request.query.proposer}}); queryStruct.bool.must.push({"term": {"proposer": query.proposer}});
} }
// Filter by proposal name // Filter by proposal name
if (request.query.proposal) { if (query.proposal) {
queryStruct.bool.must.push({"term": {"proposal_name": request.query.proposal}}); queryStruct.bool.must.push({"term": {"proposal_name": query.proposal}});
} }
// Filter by execution status // Filter by execution status
if (typeof request.query.executed !== 'undefined') { if (typeof query.executed !== 'undefined') {
queryStruct.bool.must.push({"term": {"executed": request.query.executed}}); queryStruct.bool.must.push({"term": {"executed": query.executed}});
} }
// Filter by requested actors // Filter by requested actors
if (request.query.requested) { if (query.requested) {
queryStruct.bool.must.push({"term": {"requested_approvals.actor": request.query.requested}}); queryStruct.bool.must.push({"term": {"requested_approvals.actor": query.requested}});
} }
// Filter by provided actors // Filter by provided actors
if (request.query.provided) { if (query.provided) {
queryStruct.bool.must.push({"term": {"provided_approvals.actor": request.query.provided}}); queryStruct.bool.must.push({"term": {"provided_approvals.actor": query.provided}});
} }
// If no filter switch to full match // 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) { 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)); 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 {timedQuery} from "../../../helpers/functions";
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify"; import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {getSkipLimit} from "../../v2-history/get_actions/functions"; 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) { 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 {skip, limit} = getSkipLimit(request.query);
const maxDocs = fastify.manager.config.api.limits.get_tokens ?? 100; const maxDocs = fastify.manager.config.api.limits.get_tokens ?? 100;
@@ -18,19 +19,26 @@ async function getTokens(fastify: FastifyInstance, request: FastifyRequest) {
"body": { "body": {
query: { query: {
bool: { bool: {
filter: [{term: {"scope": request.query.account}}] filter: [{term: {"scope": query.account}}]
} }
} }
} }
}); });
const testSet = new Set();
for (const hit of stateResult.body.hits.hits) { for (const hit of stateResult.body.hits.hits) {
const data = hit._source; const data = hit._source;
if (typeof data.present !== "undefined" && data.present === false) { if (typeof data.present !== "undefined" && data.present === 0) {
continue; continue;
} }
let precision; let precision;
const key = `${data.code}_${data.symbol}`; const key = `${data.code}_${data.symbol}`;
if (testSet.has(key)) {
continue;
}
testSet.add(key);
if (!fastify.tokenCache) { if (!fastify.tokenCache) {
fastify.tokenCache = new Map<string, any>(); fastify.tokenCache = new Map<string, any>();
} }
@@ -39,7 +47,7 @@ async function getTokens(fastify: FastifyInstance, request: FastifyRequest) {
} else { } else {
let token_data; let token_data;
try { 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) { if (token_data.length > 0) {
const [amount, symbol] = token_data[0].split(" "); const [amount, symbol] = token_data[0].split(" ");
const amount_arr = amount.split("."); const amount_arr = amount.split(".");
@@ -50,7 +58,7 @@ async function getTokens(fastify: FastifyInstance, request: FastifyRequest) {
} }
} }
} catch (e) { } 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) { 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)); reply.send(await timedQuery(getTokens, fastify, request, route));
} }
} }
+48 -48
View File
@@ -1,63 +1,63 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify"; import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import {timedQuery} from "../../../helpers/functions"; import {timedQuery} from "../../../helpers/functions";
import {getSkipLimit} from "../../v2-history/get_actions/functions"; import {getSkipLimit} from "../../v2-history/get_actions/functions";
async function getVoters(fastify: FastifyInstance, request: FastifyRequest) { 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 = { const response = {
voter_count: 0, voter_count: 0,
'voters': [] 'voters': []
}; };
let queryStruct: any = { let queryStruct: any = {
"bool": { "bool": {
"must": [] "must": []
} }
}; };
if (request.query.producer) { if (query.producer) {
for (const bp of request.query.producer.split(",")) { for (const bp of query.producer.split(",")) {
queryStruct.bool.must.push({"term": {"producers": bp}}); queryStruct.bool.must.push({"term": {"producers": bp}});
} }
} }
if (request.query.proxy === 'true') { if (query.proxy === 'true') {
queryStruct.bool.must.push({"term": {"is_proxy": true}}); queryStruct.bool.must.push({"term": {"is_proxy": true}});
} }
if (queryStruct.bool.must.length === 0) { if (queryStruct.bool.must.length === 0) {
queryStruct = { queryStruct = {
"match_all": {} "match_all": {}
}; };
} }
const maxDocs = fastify.manager.config.api.limits.get_voters ?? 100; const maxDocs = fastify.manager.config.api.limits.get_voters ?? 100;
const results = await fastify.elastic.search({ const results = await fastify.elastic.search({
"index": fastify.manager.chain + '-table-voters-*', "index": fastify.manager.chain + '-table-voters-*',
"from": skip || 0, "from": skip || 0,
"size": (limit > maxDocs ? maxDocs : limit) || 10, "size": (limit > maxDocs ? maxDocs : limit) || 10,
"body": { "body": {
"query": queryStruct, "query": queryStruct,
"sort": [{"last_vote_weight": "desc"}] "sort": [{"last_vote_weight": "desc"}]
} }
}); });
const hits = results['body']['hits']['hits']; const hits = results['body']['hits']['hits'];
for (const hit of hits) { for (const hit of hits) {
const voter = hit._source; const voter = hit._source;
response.voters.push({ response.voters.push({
account: voter.voter, account: voter.voter,
weight: voter.last_vote_weight, weight: voter.last_vote_weight,
last_vote: voter.block_num last_vote: voter.block_num
}); });
} }
response.voter_count = results['body']['hits']['total']['value']; response.voter_count = results['body']['hits']['total']['value'];
return response; return response;
} }
export function getVotersHandler(fastify: FastifyInstance, route: string) { export function getVotersHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => { return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getVoters, fastify, request, route)); reply.send(await timedQuery(getVoters, fastify, request, route));
} }
} }
@@ -1,121 +1,120 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify"; import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import {timedQuery} from "../../../helpers/functions"; import {timedQuery} from "../../../helpers/functions";
async function getLastSeq(fastify: FastifyInstance, date: string) { async function getLastSeq(fastify: FastifyInstance, date: string) {
const req = await fastify.elastic.search({ const req = await fastify.elastic.search({
index: fastify.manager.chain + '-action-*', index: fastify.manager.chain + '-action-*',
body: { body: {
"size": 1, "size": 1,
"query": { "query": {
"bool": { "bool": {
"must": [ "must": [
{"range": {"@timestamp": {"lt": date}}} {"range": {"@timestamp": {"lt": date}}}
] ]
} }
}, },
"_source": { "_source": {
"includes": ["global_sequence"] "includes": ["global_sequence"]
}, },
"sort": [{"global_sequence": "desc"}] "sort": [{"global_sequence": "desc"}]
} }
}); });
return req.body.hits.hits[0]._source.global_sequence; return req.body.hits.hits[0]._source.global_sequence;
} }
async function getTxCount(fastify: FastifyInstance, dateFrom: string, dateTo: string) { async function getTxCount(fastify: FastifyInstance, dateFrom: string, dateTo: string) {
const req = await fastify.elastic.count({ const req = await fastify.elastic.count({
index: fastify.manager.chain + '-action-*', index: fastify.manager.chain + '-action-*',
body: { body: {
"query": { "query": {
"bool": { "bool": {
"must": [ "must": [
{"term": {"creator_action_ordinal": 0}}, {"term": {"creator_action_ordinal": 0}},
{"range": {"@timestamp": {"gt": dateFrom, "lt": dateTo}}} {"range": {"@timestamp": {"gt": dateFrom, "lt": dateTo}}}
] ]
} }
} }
} }
}); });
return req.body.count; return req.body.count;
} }
async function getUniqueActors(fastify: FastifyInstance, dateFrom: string, dateTo: string) { async function getUniqueActors(fastify: FastifyInstance, dateFrom: string, dateTo: string) {
const req = await fastify.elastic.search({ const req = await fastify.elastic.search({
index: fastify.manager.chain + '-action-*', index: fastify.manager.chain + '-action-*',
size: 0, size: 0,
body: { body: {
"aggs": { "aggs": {
"unique_actors": { "unique_actors": {
"cardinality": { "cardinality": {
"field": "act.authorization.actor" "field": "act.authorization.actor"
} }
} }
}, },
"query": { "query": {
"bool": { "bool": {
"filter": [{"range": {"@timestamp": {"gt": dateFrom, "lt": dateTo}}}] "filter": [{"range": {"@timestamp": {"gt": dateFrom, "lt": dateTo}}}]
} }
} }
} }
}); });
return req.body.aggregations.unique_actors.value; return req.body.aggregations.unique_actors.value;
} }
async function getActionUsage(fastify: FastifyInstance, request: FastifyRequest) { async function getActionUsage(fastify: FastifyInstance, request: FastifyRequest) {
const query = request.query; const query: any = request.query;
let now = new Date(); let now = new Date();
let expiration = 86400; let expiration = 86400;
if (query.end_date) { if (query.end_date) {
now = new Date(query.end_date); now = new Date(query.end_date);
} }
now.setMinutes(0, 0, 0); now.setMinutes(0, 0, 0);
let period; let period;
if (query.period === '1h') { if (query.period === '1h') {
period = 3600000; period = 3600000;
} else if (query.period === '24h' || query.period === '1d') { } else if (query.period === '24h' || query.period === '1d') {
period = 86400000; period = 86400000;
} else { } else {
return {}; return {};
} }
if (!query.end_date) { if (!query.end_date) {
expiration = Math.floor((period - (Date.now() - now.getTime())) / 1000); expiration = Math.floor((period - (Date.now() - now.getTime())) / 1000);
} }
const response = {} as any; const response = {} as any;
const periodStart = new Date(now.getTime() - period); const periodStart = new Date(now.getTime() - period);
const pStart = periodStart.toISOString(); const pStart = periodStart.toISOString();
const pEnd = now.toISOString(); const pEnd = now.toISOString();
const cacheKey = `${fastify.manager.chain}_act_stats_${pStart}_${pEnd}_${query.unique_actors}`; const cacheKey = `${fastify.manager.chain}_act_stats_${pStart}_${pEnd}_${query.unique_actors}`;
const cachedValue = await fastify.redis.get(cacheKey); const cachedValue = await fastify.redis.get(cacheKey);
if (cachedValue) { if (cachedValue) {
const cachedResponse = JSON.parse(cachedValue); const cachedResponse = JSON.parse(cachedValue);
cachedResponse.cached = true; cachedResponse.cached = true;
cachedResponse.cache_exp = expiration; cachedResponse.cache_exp = expiration;
return cachedResponse; return cachedResponse;
} }
const firstReq = await getLastSeq(fastify, pStart); const firstReq = await getLastSeq(fastify, pStart);
const lastReq = await getLastSeq(fastify, pEnd); const lastReq = await getLastSeq(fastify, pEnd);
response.action_count = lastReq - firstReq; response.action_count = lastReq - firstReq;
response.tx_count = await getTxCount(fastify, pStart, pEnd); response.tx_count = await getTxCount(fastify, pStart, pEnd);
if (query.unique_actors) { if (query.unique_actors) {
response.unique_actors = await getUniqueActors(fastify, pStart, pEnd); response.unique_actors = await getUniqueActors(fastify, pStart, pEnd);
} }
response.period = query.period; response.period = query.period;
response.from = pStart; response.from = pStart;
response.to = pEnd; response.to = pEnd;
await fastify.redis.set(cacheKey, JSON.stringify(response), 'EX', expiration); await fastify.redis.set(cacheKey, JSON.stringify(response), 'EX', expiration);
return response; return response;
} }
export function getActionUsageHandler(fastify: FastifyInstance, route: string) { export function getActionUsageHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => { return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getActionUsage, fastify, request, route)); reply.send(await timedQuery(getActionUsage, fastify, request, route));
} }
} }
@@ -1,64 +1,64 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify"; import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {timedQuery} from "../../../helpers/functions"; import {timedQuery} from "../../../helpers/functions";
import {ServerResponse} from "http";
import {Search} from "@elastic/elasticsearch/api/requestParams"; import {Search} from "@elastic/elasticsearch/api/requestParams";
import {applyTimeFilter} from "../../v2-history/get_actions/functions"; import {applyTimeFilter} from "../../v2-history/get_actions/functions";
async function getMissedBlocks(fastify: FastifyInstance, request: FastifyRequest) { async function getMissedBlocks(fastify: FastifyInstance, request: FastifyRequest) {
const response = { const query: any = request.query;
stats: { const response = {
by_producer: {} stats: {
}, by_producer: {}
events: [] },
}; events: []
const searchParams: Search<any> = { };
track_total_hits: true, const searchParams: Search<any> = {
index: fastify.manager.chain + "-logs-*", track_total_hits: true,
size: 100, index: fastify.manager.chain + "-logs-*",
body: { size: 100,
query: { body: {
bool: { query: {
must: [ bool: {
{term: {"type": "missed_blocks"}} must: [
] {term: {"type": "missed_blocks"}}
} ]
}, }
sort: { },
"@timestamp": "desc" sort: {
} "@timestamp": "desc"
} }
}; }
};
let minBlocks = 0; let minBlocks = 0;
if (request.query.min_blocks) { if (query.min_blocks) {
minBlocks = parseInt(request.query.min_blocks); minBlocks = parseInt(query.min_blocks);
searchParams.body.query.bool.must.push({range: {"missed_blocks.size": {gte: minBlocks}}}) searchParams.body.query.bool.must.push({range: {"missed_blocks.size": {gte: minBlocks}}})
} }
if (request.query.producer) { if (query.producer) {
searchParams.body.query.bool.must.push({term: {"missed_blocks.producer": request.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); const apiResponse = await fastify.elastic.search(searchParams);
apiResponse.body.hits.hits.forEach(v => { apiResponse.body.hits.hits.forEach(v => {
const ev = v._source; const ev = v._source;
response.events.push({ response.events.push({
"@timestamp": ev["@timestamp"], "@timestamp": ev["@timestamp"],
...ev.missed_blocks ...ev.missed_blocks
}); });
if (!response.stats.by_producer[ev.missed_blocks.producer]) { if (!response.stats.by_producer[ev.missed_blocks.producer]) {
response.stats.by_producer[ev.missed_blocks.producer] = ev.missed_blocks.size; response.stats.by_producer[ev.missed_blocks.producer] = ev.missed_blocks.size;
} else { } else {
response.stats.by_producer[ev.missed_blocks.producer] += ev.missed_blocks.size; response.stats.by_producer[ev.missed_blocks.producer] += ev.missed_blocks.size;
} }
}); });
return response; return response;
} }
export function getMissedBlocksHandler(fastify: FastifyInstance, route: string) { export function getMissedBlocksHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => { return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getMissedBlocks, fastify, request, route)); reply.send(await timedQuery(getMissedBlocks, fastify, request, route));
} }
} }
@@ -1,16 +1,16 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify"; import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import {timedQuery} from "../../../helpers/functions"; import {timedQuery} from "../../../helpers/functions";
const percentiles = [1, 5, 25, 50, 75, 95, 99]; const percentiles = [1, 5, 25, 50, 75, 95, 99];
async function getResourceUsage(fastify: FastifyInstance, request: FastifyRequest) { async function getResourceUsage(fastify: FastifyInstance, request: FastifyRequest) {
const query: any = request.query;
const searchBody: any = { const searchBody: any = {
query: { query: {
bool: { bool: {
must: [ must: [
{term: {"act.account": request.query.code}}, {term: {"act.account": query.code}},
{term: {"act.name": request.query.action}}, {term: {"act.name": query.action}},
{term: {"action_ordinal": {"value": 1}}}, {term: {"action_ordinal": {"value": 1}}},
{ {
range: { range: {
@@ -69,7 +69,7 @@ async function getResourceUsage(fastify: FastifyInstance, request: FastifyReques
} }
export function getResourceUsageHandler(fastify: FastifyInstance, route: string) { 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)); reply.send(await timedQuery(getResourceUsage, fastify, request, route));
} }
} }
+68 -69
View File
@@ -1,91 +1,90 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify"; import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import {connect} from "amqplib"; import {connect} from "amqplib";
import {timedQuery} from "../../../helpers/functions"; import {timedQuery} from "../../../helpers/functions";
import {getLastIndexedBlockWithTotalBlocks} from "../../../../helpers/common_functions"; import {getLastIndexedBlockWithTotalBlocks} from "../../../../helpers/common_functions";
async function checkRabbit(fastify: FastifyInstance) { async function checkRabbit(fastify: FastifyInstance) {
try { try {
const connection = await connect(fastify.manager.ampqUrl); const connection = await connect(fastify.manager.ampqUrl);
await connection.close(); await connection.close();
return createHealth('RabbitMq', 'OK'); return createHealth('RabbitMq', 'OK');
} catch (e) { } catch (e) {
console.log(e); console.log(e);
return createHealth('RabbitMq', 'Error'); return createHealth('RabbitMq', 'Error');
} }
} }
async function checkNodeos(fastify: FastifyInstance) { async function checkNodeos(fastify: FastifyInstance) {
const rpc = fastify.manager.nodeosJsonRPC; const rpc = fastify.manager.nodeosJsonRPC;
try { try {
const results = await rpc.get_info(); const results = await rpc.get_info();
if (results) { if (results) {
const diff = (new Date().getTime()) - (new Date(results.head_block_time + '+00:00').getTime()); const diff = (new Date().getTime()) - (new Date(results.head_block_time + '+00:00').getTime());
return createHealth('NodeosRPC', 'OK', { return createHealth('NodeosRPC', 'OK', {
head_block_num: results.head_block_num, head_block_num: results.head_block_num,
head_block_time: results.head_block_time, head_block_time: results.head_block_time,
time_offset: diff, time_offset: diff,
last_irreversible_block: results.last_irreversible_block_num, last_irreversible_block: results.last_irreversible_block_num,
chain_id: results.chain_id chain_id: results.chain_id
}); });
} else { } else {
return createHealth('NodeosRPC', 'Error'); return createHealth('NodeosRPC', 'Error');
} }
} catch (e) { } catch (e) {
return createHealth('NodeosRPC', 'Error'); return createHealth('NodeosRPC', 'Error');
} }
} }
async function checkElastic(fastify: FastifyInstance) { async function checkElastic(fastify: FastifyInstance) {
try { try {
let esStatus = await fastify.elastic.cat.health({format: 'json', v: true}); let esStatus = await fastify.elastic.cat.health({format: 'json', v: true});
let indexedBlocks = await getLastIndexedBlockWithTotalBlocks(fastify.elastic, fastify.manager.chain); let indexedBlocks = await getLastIndexedBlockWithTotalBlocks(fastify.elastic, fastify.manager.chain);
const data = { const data = {
last_indexed_block: indexedBlocks[0], last_indexed_block: indexedBlocks[0],
total_indexed_blocks: indexedBlocks[1], total_indexed_blocks: indexedBlocks[1] + 1,
active_shards: esStatus.body[0]['active_shards_percent'] active_shards: esStatus.body[0]['active_shards_percent']
}; };
let stat = 'OK'; let stat = 'OK';
esStatus.body.forEach(status => { esStatus.body.forEach(status => {
if (status.status === 'yellow' && stat !== 'Error') { if (status.status === 'yellow' && stat !== 'Error') {
stat = 'Warning' stat = 'Warning'
} else if (status.status === 'red') { } else if (status.status === 'red') {
stat = 'Error' stat = 'Error'
} }
}); });
return createHealth('Elasticsearch', stat, data); return createHealth('Elasticsearch', stat, data);
} catch (e) { } catch (e) {
console.log(e, 'Elasticsearch Error'); console.log(e, 'Elasticsearch Error');
return createHealth('Elasticsearch', 'Error'); return createHealth('Elasticsearch', 'Error');
} }
} }
function createHealth(name: string, status, data?: any) { function createHealth(name: string, status, data?: any) {
let time = Date.now(); let time = Date.now();
return { return {
service: name, service: name,
status: status, status: status,
service_data: data, service_data: data,
time: time time: time
} }
} }
async function getHealthQuery(fastify: FastifyInstance, request: FastifyRequest) { async function getHealthQuery(fastify: FastifyInstance, request: FastifyRequest) {
let response = { let response = {
version: fastify.manager.current_version, version: fastify.manager.current_version,
version_hash: fastify.manager.getServerHash(), version_hash: fastify.manager.getServerHash(),
host: fastify.manager.config.api.server_name, host: fastify.manager.config.api.server_name,
features: fastify.manager.config.features, health: [],
health: [] features: fastify.manager.config.features
}; };
response.health.push(await checkRabbit(fastify)); response.health.push(await checkRabbit(fastify));
response.health.push(await checkNodeos(fastify)); response.health.push(await checkNodeos(fastify));
response.health.push(await checkElastic(fastify)); response.health.push(await checkElastic(fastify));
return response; return response;
} }
export function healthHandler(fastify: FastifyInstance, route: string) { export function healthHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => { return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getHealthQuery, fastify, request, route)); 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 {addApiRoute, getRouteName} from "../../../helpers/functions";
import {healthHandler} from "./health"; import {healthHandler} from "./health";
export default function (fastify: FastifyInstance, opts: any, next) { export default function (fastify: FastifyInstance, opts: any, next) {
const schema: RouteSchema = { const schema: FastifySchema = {
tags: ['status'], tags: ['status'],
summary: "API Service Health Report" summary: "API Service Health Report"
}; };
addApiRoute( addApiRoute(
fastify, fastify,
'GET', 'GET',
getRouteName(__filename), getRouteName(__filename),
healthHandler, healthHandler,
schema schema
); );
next(); 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 {ConfigurationModule} from "../modules/config";
import {ConnectionManager} from "../connections/manager.class"; import {ConnectionManager} from "../connections/manager.class";
import {HyperionConfig} from "../interfaces/hyperionConfig"; import {HyperionConfig} from "../interfaces/hyperionConfig";
import {IncomingMessage, Server, ServerResponse} from "http"; import IORedis from 'ioredis';
import * as Fastify from 'fastify'; import fastify from 'fastify'
import * as Redis from 'ioredis';
import {registerPlugins} from "./plugins"; import {registerPlugins} from "./plugins";
import {AddressInfo} from "net"; import {AddressInfo} from "net";
import {registerRoutes} from "./routes"; import {registerRoutes} from "./routes";
import {generateOpenApiConfig} from "./config/open_api"; 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 {SocketManager} from "./socketManager";
import got from "got"; import {HyperionModuleLoader} from "../modules/loader";
import {join} from "path"; import {extendedActions} from "./routes/v2-history/get_actions/definitions";
import * as io from 'socket.io-client'; import {io, Socket} from "socket.io-client";
import {CacheManager} from "./helpers/cacheManager";
import {bootstrap} from 'global-agent';
bootstrap();
class HyperionApiServer { class HyperionApiServer {
private conf: HyperionConfig; private hub: Socket;
private readonly manager: ConnectionManager; private readonly fastify;
private readonly fastify: Fastify.FastifyInstance<Server, IncomingMessage, ServerResponse>;
private readonly chain: string; 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() { constructor() {
const package_json = JSON.parse(readFileSync('./package.json').toString());
hLog(`--------- Hyperion API ${package_json.version} ---------`);
const cm = new ConfigurationModule(); const cm = new ConfigurationModule();
this.conf = cm.config; this.conf = cm.config;
this.chain = this.conf.settings.chain; this.chain = this.conf.settings.chain;
process.title = `hyp-${this.chain}-api`; process.title = `hyp-${this.chain}-api`;
this.manager = new ConnectionManager(cm); this.manager = new ConnectionManager(cm);
this.manager.calculateServerHash(); this.manager.calculateServerHash();
this.manager.getHyperionVersion(); this.mLoader = new HyperionModuleLoader(cm);
this.cacheManager = new CacheManager(this.conf);
if (!existsSync('./logs/' + this.chain)) { if (!existsSync('./logs/' + this.chain)) {
mkdirSync('./logs/' + this.chain, {recursive: true}); mkdirSync('./logs/' + this.chain, {recursive: true});
} }
const logStream = createWriteStream('./logs/' + this.chain + '/api.access.log'); 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, ignoreTrailingSlash: false,
trustProxy: true, trustProxy: true,
pluginTimeout: 5000, pluginTimeout: 5000,
logger: this.conf.api.access_log ? { logger: this.conf.api.access_log ? loggerOpts : false
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
}); });
this.fastify.decorate('cacheManager', this.cacheManager);
this.fastify.decorate('manager', this.manager); this.fastify.decorate('manager', this.manager);
if (this.conf.api.chain_api && this.conf.api.chain_api !== "") { // import get_actions query params from custom modules
this.fastify.decorate('chain_api', this.conf.api.chain_api); const extendedActionsSet: Set<string> = new Set([...extendedActions]);
} else { for (const qPrefix of this.mLoader.extendedActions) {
this.fastify.decorate('chain_api', this.manager.conn.chains[this.chain].http); 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); this.fastify.decorate('push_api', this.conf.api.push_api);
} }
console.log(`Chain API URL: ${this.fastify.chain_api}`); hLog(`Chain API URL: "${this.fastify.chain_api}" | Push API URL: "${this.fastify.push_api}"`);
console.log(`Push API URL: ${this.fastify.push_api}`);
const ioRedisClient = new Redis(this.manager.conn.redis); const ioRedisClient = new IORedis(this.manager.conn.redis);
const api_rate_limit = {
max: 1000, const pluginParams = {
whitelist: ['127.0.0.1'], fastify_elasticsearch: {
timeWindow: '1 minute', client: this.manager.elasticsearchClient
redis: ioRedisClient },
}; 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) { if (this.conf.features.streaming.enable) {
this.activateStreaming(); this.activateStreaming();
} }
registerPlugins(this.fastify, { const docsConfig = generateOpenApiConfig(this.manager.config);
fastify_elasticsearch: { if (docsConfig) {
client: this.manager.elasticsearchClient pluginParams.fastify_swagger = docsConfig;
}, }
fastify_oas: generateOpenApiConfig(this.manager.config),
fastify_rate_limit: api_rate_limit, registerPlugins(this.fastify, pluginParams);
fastify_redis: this.manager.conn.redis,
fastify_eosjs: this.manager,
});
this.addGenericTypeParsing(); this.addGenericTypeParsing();
} }
@@ -128,15 +170,15 @@ class HyperionApiServer {
} }
private addGenericTypeParsing() { private addGenericTypeParsing() {
this.fastify.addContentTypeParser('*', (req, done) => { this.fastify.addContentTypeParser('*', (request, payload, done) => {
let data = ''; let data = '';
req.on('data', chunk => { payload.on('data', chunk => {
data += chunk; data += chunk;
}); });
req.on('end', () => { payload.on('end', () => {
done(null, data); done(null, data);
}); });
req.on('error', (err) => { payload.on('error', (err) => {
console.log('---- Content Parsing Error -----'); console.log('---- Content Parsing Error -----');
console.log(err); console.log(err);
}); });
@@ -145,15 +187,24 @@ class HyperionApiServer {
async init() { 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); registerRoutes(this.fastify);
// register documentation when ready
this.fastify.ready().then(async () => { this.fastify.ready().then(async () => {
await this.fastify.oas(); await this.fastify.swagger();
console.log(this.chain + ' api ready!');
}, (err) => { }, (err) => {
console.log('an error happened', err) hLog('an error happened', err)
}); });
try { try {
@@ -161,28 +212,14 @@ class HyperionApiServer {
host: this.conf.api.server_addr, host: this.conf.api.server_addr,
port: this.conf.api.server_port 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(); this.startHyperionHub();
} catch (err) { } catch (err) {
console.log(err); hLog(err);
process.exit(1) 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() { startHyperionHub() {
if (this.conf.hub) { if (this.conf.hub) {
const url = this.conf.hub.inform_url; const url = this.conf.hub.inform_url;
@@ -190,17 +227,13 @@ class HyperionApiServer {
this.hub = io(url, { this.hub = io(url, {
query: { query: {
key: this.conf.hub.publisher_key, key: this.conf.hub.publisher_key,
client_mode: false client_mode: 'false'
} }
}); });
this.hub.on('connect', () => { this.hub.on('connect', () => {
hLog(`Hyperion Hub connected!`); hLog(`Hyperion Hub connected!`);
this.emitHubApiUpdate(); this.emitHubApiUpdate();
}); });
// this.hub.on('reconnect', () => {
// hLog(`Reconnecting...`);
// this.emitHubApiUpdate();
// });
} }
} }
@@ -211,7 +244,7 @@ class HyperionApiServer {
location: this.conf.hub.location, location: this.conf.hub.location,
chainId: this.manager.conn.chains[this.chain].chain_id, chainId: this.manager.conn.chains[this.chain].chain_id,
providerName: this.conf.api.provider_name, providerName: this.conf.api.provider_name,
explorerEnabled: this.conf.api.enable_explorer, explorerEnabled: this.conf.plugins.explorer?.enabled,
providerUrl: this.conf.api.provider_url, providerUrl: this.conf.api.provider_url,
providerLogo: this.conf.api.provider_logo, providerLogo: this.conf.api.provider_logo,
chainLogo: this.conf.api.chain_logo_url, chainLogo: this.conf.api.chain_logo_url,
+322 -315
View File
@@ -1,377 +1,384 @@
import {checkFilter, hLog} from '../helpers/common_functions'; import {checkFilter, hLog} from '../helpers/common_functions';
import * as sockets from 'socket.io'; import {Server, Socket} from 'socket.io';
import * as IOClient from 'socket.io-client'; import {createAdapter} from 'socket.io-redis';
import {io} from 'socket.io-client';
import {FastifyInstance} from "fastify"; import {FastifyInstance} from "fastify";
import IORedis from "ioredis";
export interface StreamDeltasRequest { export interface StreamDeltasRequest {
code: string; code: string;
table: string; table: string;
scope: string; scope: string;
payer: string; payer: string;
start_from: number | string; start_from: number | string;
read_until: number | string; read_until: number | string;
} }
export interface RequestFilter { export interface RequestFilter {
field: string; field: string;
value: string; value: string;
} }
export interface StreamActionsRequest { export interface StreamActionsRequest {
contract: string; contract: string;
account: string; account: string;
action: string; action: string;
filters: RequestFilter[]; filters: RequestFilter[];
start_from: number | string; start_from: number | string;
read_until: number | string; read_until: number | string;
} }
async function addBlockRangeOpts(data, search_body, fastify: FastifyInstance) { async function addBlockRangeOpts(data, search_body, fastify: FastifyInstance) {
let timeRange; let timeRange;
let blockRange; let blockRange;
let head; let head;
if (typeof data['start_from'] === 'string' && data['start_from'] !== '') { if (typeof data['start_from'] === 'string' && data['start_from'] !== '') {
if (!timeRange) { if (!timeRange) {
timeRange = {"@timestamp": {}}; timeRange = {"@timestamp": {}};
} }
timeRange["@timestamp"]['gte'] = data['start_from']; timeRange["@timestamp"]['gte'] = data['start_from'];
} }
if (typeof data['read_until'] === 'string' && data['read_until'] !== '') { if (typeof data['read_until'] === 'string' && data['read_until'] !== '') {
if (!timeRange) { if (!timeRange) {
timeRange = {"@timestamp": {}}; timeRange = {"@timestamp": {}};
} }
timeRange["@timestamp"]['lte'] = data['read_until']; timeRange["@timestamp"]['lte'] = data['read_until'];
} }
if (typeof data['start_from'] === 'number' && data['start_from'] !== 0) { if (typeof data['start_from'] === 'number' && data['start_from'] !== 0) {
if (!blockRange) { if (!blockRange) {
blockRange = {"block_num": {}}; blockRange = {"block_num": {}};
} }
if (data['start_from'] < 0) { if (data['start_from'] < 0) {
if (!head) { if (!head) {
head = (await fastify.eosjs.rpc.get_info()).head_block_num; head = (await fastify.eosjs.rpc.get_info()).head_block_num;
} }
blockRange["block_num"]['gte'] = head + data['start_from']; blockRange["block_num"]['gte'] = head + data['start_from'];
} else { } else {
blockRange["block_num"]['gte'] = data['start_from']; blockRange["block_num"]['gte'] = data['start_from'];
} }
} }
if (typeof data['read_until'] === 'number' && data['read_until'] !== 0) { if (typeof data['read_until'] === 'number' && data['read_until'] !== 0) {
if (!blockRange) { if (!blockRange) {
blockRange = {"block_num": {}}; blockRange = {"block_num": {}};
} }
if (data['read_until'] < 0) { if (data['read_until'] < 0) {
if (!head) { if (!head) {
head = (await fastify.eosjs.rpc.get_info()).head_block_num; head = (await fastify.eosjs.rpc.get_info()).head_block_num;
} }
blockRange["block_num"]['lte'] = head + data['read_until']; blockRange["block_num"]['lte'] = head + data['read_until'];
} else { } else {
blockRange["block_num"]['lte'] = data['read_until']; blockRange["block_num"]['lte'] = data['read_until'];
} }
} }
if (timeRange) { if (timeRange) {
search_body.query.bool.must.push({ search_body.query.bool.must.push({
range: timeRange, range: timeRange,
}); });
} }
if (blockRange) { if (blockRange) {
search_body.query.bool.must.push({ search_body.query.bool.must.push({
range: blockRange, range: blockRange,
}); });
} }
} }
function addTermMatch(data, search_body, field) { function addTermMatch(data, search_body, field) {
if (data[field] !== '*' && data[field] !== '') { if (data[field] !== '*' && data[field] !== '') {
const termQuery = {}; const termQuery = {};
termQuery[field] = data[field]; termQuery[field] = data[field];
search_body.query.bool.must.push({'term': termQuery}); search_body.query.bool.must.push({'term': termQuery});
} }
} }
const deltaQueryFields = ['code', 'table', 'scope', 'payer']; const deltaQueryFields = ['code', 'table', 'scope', 'payer'];
async function streamPastDeltas(fastify: FastifyInstance, socket, data) { async function streamPastDeltas(fastify: FastifyInstance, socket, data) {
const search_body = { const search_body = {
query: {bool: {must: []}}, query: {bool: {must: []}},
sort: {block_num: 'asc'}, sort: {block_num: 'asc'},
}; };
await addBlockRangeOpts(data, search_body, fastify); await addBlockRangeOpts(data, search_body, fastify);
deltaQueryFields.forEach(f => { deltaQueryFields.forEach(f => {
addTermMatch(data, search_body, f); addTermMatch(data, search_body, f);
}); });
const responseQueue = []; const responseQueue = [];
let counter = 0; let counter = 0;
const init_response = await fastify.elastic.search({ const init_response = await fastify.elastic.search({
index: fastify.manager.chain + '-delta-*', index: fastify.manager.chain + '-delta-*',
scroll: '30s', scroll: '30s',
size: 20, size: 20,
body: search_body, body: search_body,
}); });
responseQueue.push(init_response); responseQueue.push(init_response);
while (responseQueue.length) { while (responseQueue.length) {
const {body} = responseQueue.shift(); const {body} = responseQueue.shift();
counter += body['hits']['hits'].length; counter += body['hits']['hits'].length;
if (socket.connected) { if (socket.connected) {
socket.emit('message', { socket.emit('message', {
type: 'delta_trace', type: 'delta_trace',
mode: 'history', mode: 'history',
messages: body['hits']['hits'].map(doc => doc._source), messages: body['hits']['hits'].map(doc => doc._source),
}); });
} else { } else {
console.log('LOST CLIENT'); hLog('LOST CLIENT');
break; break;
} }
if (body['hits'].total.value === counter) { if (body['hits'].total.value === counter) {
console.log(`${counter} past deltas streamed to ${socket.id}`); hLog(`${counter} past deltas streamed to ${socket.id}`);
break; break;
} }
const next_response = await fastify.elastic.scroll({ const next_response = await fastify.elastic.scroll({
body: { body: {
scroll_id: body['_scroll_id'], scroll_id: body['_scroll_id'],
scroll: '30s' scroll: '30s'
} }
}); });
responseQueue.push(next_response); responseQueue.push(next_response);
} }
} }
async function streamPastActions(fastify: FastifyInstance, socket, data) { async function streamPastActions(fastify: FastifyInstance, socket, data) {
const search_body = { const search_body = {
query: {bool: {must: []}}, query: {bool: {must: []}},
sort: {global_sequence: 'asc'}, sort: {global_sequence: 'asc'},
}; };
await addBlockRangeOpts(data, search_body, fastify); await addBlockRangeOpts(data, search_body, fastify);
if (data.account !== '') { if (data.account !== '') {
search_body.query.bool.must.push({ search_body.query.bool.must.push({
bool: { bool: {
should: [ should: [
{term: {'notified': data.account}}, {term: {'notified': data.account}},
{term: {'act.authorization.actor': data.account}}, {term: {'act.authorization.actor': data.account}},
], ],
}, },
}); });
} }
if (data.contract !== '*' && data.contract !== '') { if (data.contract !== '*' && data.contract !== '') {
search_body.query.bool.must.push({'term': {'act.account': data.contract}}); search_body.query.bool.must.push({'term': {'act.account': data.contract}});
} }
if (data.action !== '*' && data.action !== '') { if (data.action !== '*' && data.action !== '') {
search_body.query.bool.must.push({'term': {'act.name': data.action}}); search_body.query.bool.must.push({'term': {'act.name': data.action}});
} }
const onDemandFilters = []; const onDemandFilters = [];
if (data.filters.length > 0) { if (data.filters.length > 0) {
data.filters.forEach(f => { data.filters.forEach(f => {
if (f.field && f.value) { if (f.field && f.value) {
if (f.field.startsWith('@') && !f.field.startsWith('act.data')) { if (f.field.startsWith('@') && !f.field.startsWith('act.data')) {
const _q = {}; const _q = {};
_q[f.field] = f.value; _q[f.field] = f.value;
search_body.query.bool.must.push({'term': _q}); search_body.query.bool.must.push({'term': _q});
} else { } else {
onDemandFilters.push(f); onDemandFilters.push(f);
} }
} }
}); });
} }
const responseQueue = []; const responseQueue = [];
let counter = 0; let counter = 0;
const init_response = await fastify.elastic.search({ const init_response = await fastify.elastic.search({
index: fastify.manager.chain + '-action-*', index: fastify.manager.chain + '-action-*',
scroll: '30s', scroll: '30s',
size: 20, size: 20,
body: search_body, body: search_body,
}); });
responseQueue.push(init_response); responseQueue.push(init_response);
while (responseQueue.length) { while (responseQueue.length) {
const {body} = responseQueue.shift(); const {body} = responseQueue.shift();
const enqueuedMessages = []; const enqueuedMessages = [];
counter += body['hits']['hits'].length; counter += body['hits']['hits'].length;
for (const doc of body['hits']['hits']) { for (const doc of body['hits']['hits']) {
let allow = false; let allow = false;
if (onDemandFilters.length > 0) { if (onDemandFilters.length > 0) {
allow = onDemandFilters.every(filter => { allow = onDemandFilters.every(filter => {
return checkFilter(filter, doc._source); return checkFilter(filter, doc._source);
}); });
} else { } else {
allow = true; allow = true;
} }
if (allow) { if (allow) {
enqueuedMessages.push(doc._source); enqueuedMessages.push(doc._source);
} }
} }
if (socket.connected) { if (socket.connected) {
socket.emit('message', {type: 'action_trace', mode: 'history', messages: enqueuedMessages}); socket.emit('message', {type: 'action_trace', mode: 'history', messages: enqueuedMessages});
} else { } else {
console.log('LOST CLIENT'); hLog('LOST CLIENT');
break; break;
} }
if (body['hits'].total.value === counter) { if (body['hits'].total.value === counter) {
console.log(`${counter} past actions streamed to ${socket.id}`); hLog(`${counter} past actions streamed to ${socket.id}`);
break; break;
} }
if (init_response.body.hits.total.value < 1000) { if (init_response.body.hits.total.value < 1000) {
const next_response = await fastify.elastic.scroll({ const next_response = await fastify.elastic.scroll({
body: {scroll_id: body['_scroll_id'], scroll: '30s'} body: {scroll_id: body['_scroll_id'], scroll: '30s'}
}); });
responseQueue.push(next_response); responseQueue.push(next_response);
} else { } else {
console.log('Request too large!'); hLog('Request too large!');
socket.emit('message', {type: 'action_trace', mode: 'history', messages: []}); socket.emit('message', {type: 'action_trace', mode: 'history', messages: []});
} }
} }
} }
export class SocketManager { export class SocketManager {
private io; private io: Server;
private relay; private relay;
relay_restored = true; relay_restored = true;
relay_down = false; relay_down = false;
private readonly url; private readonly url;
private readonly server: FastifyInstance; private readonly server: FastifyInstance;
constructor(fastify: FastifyInstance, url, redisOpts) { constructor(fastify: FastifyInstance, url, redisOptions) {
this.server = fastify; this.server = fastify;
this.url = url; this.url = url;
this.io = sockets(fastify.server, { this.io = new Server(fastify.server, {
transports: ['websocket', 'polling'], 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']) { this.io.on('connection', (socket: Socket) => {
console.log(`[socket] ${socket.id} connected via ${socket.handshake.headers['x-forwarded-for']}`);
}
socket.emit('message', { if (socket.handshake.headers['x-forwarded-for']) {
event: 'handshake', hLog(`[socket] ${socket.id} connected via ${socket.handshake.headers['x-forwarded-for']}`);
chain: fastify.manager.chain, }
});
if (this.relay) { socket.emit('message', {
this.relay.emit('event', { event: 'handshake',
type: 'client_count', chain: fastify.manager.chain,
counter: Object.keys(this.io.sockets.connected).length, });
});
}
socket.on('delta_stream_request', async (data: StreamDeltasRequest, callback) => { if (this.relay) {
if (typeof callback === 'function' && data) { this.relay.emit('event', {
try { type: 'client_count',
if (data.start_from) { counter: this.io.sockets.sockets.size,
await streamPastDeltas(this.server, socket, data); });
} }
this.emitToRelay(data, 'delta_request', socket, callback);
} catch (e) {
console.log(e);
}
}
});
socket.on('action_stream_request', async (data: StreamActionsRequest, callback) => { socket.on('delta_stream_request', async (data: StreamDeltasRequest, callback) => {
if (typeof callback === 'function' && data) { if (typeof callback === 'function' && data) {
try { try {
if (data.start_from) { if (data.start_from) {
await streamPastActions(this.server, socket, data); await streamPastDeltas(this.server, socket, data);
} }
this.emitToRelay(data, 'action_request', socket, callback); this.emitToRelay(data, 'delta_request', socket, callback);
} catch (e) { } catch (e) {
console.log(e); console.log(e);
} }
} }
}); });
socket.on('disconnect', (reason) => { socket.on('action_stream_request', async (data: StreamActionsRequest, callback) => {
console.log(`[socket] ${socket.id} disconnected - ${reason}`); if (typeof callback === 'function' && data) {
this.relay.emit('event', { try {
type: 'client_disconnected', if (data.start_from) {
id: socket.id, await streamPastActions(this.server, socket, data);
reason, }
}); this.emitToRelay(data, 'action_request', socket, callback);
}); } catch (e) {
}); console.log(e);
console.log('Websocket manager loaded!'); }
} }
});
startRelay() { socket.on('disconnect', (reason) => {
console.log(`starting relay - ${this.url}`); 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', () => { this.relay = io(this.url, {path: '/router'});
console.log('Relay Connected!');
if (this.relay_down) {
this.relay_restored = true;
this.relay_down = false;
this.io.emit('status', 'relay_restored');
}
});
this.relay.on('disconnect', () => { this.relay.on('connect', () => {
console.log('Relay disconnected!'); hLog('Relay Connected!');
this.io.emit('status', 'relay_down'); if (this.relay_down) {
this.relay_down = true; this.relay_restored = true;
this.relay_restored = false; this.relay_down = false;
}); this.io.emit('status', 'relay_restored');
}
});
this.relay.on('delta', (traceData) => { this.relay.on('disconnect', () => {
this.emitToClient(traceData, 'delta_trace'); hLog('Relay disconnected!');
}); this.io.emit('status', 'relay_down');
this.relay_down = true;
this.relay_restored = false;
});
this.relay.on('trace', (traceData) => { this.relay.on('delta', (traceData) => {
this.emitToClient(traceData, 'action_trace'); this.emitToClient(traceData, 'delta_trace');
}); });
// Relay LIB info to clients; this.relay.on('trace', (traceData) => {
this.relay.on('lib_update', (data) => { this.emitToClient(traceData, 'action_trace');
if (this.server.manager.conn.chains[this.server.manager.chain].chain_id === data.chain_id) { });
this.io.emit('lib_update', data);
}
});
// Relay LIB info to clients; // Relay LIB info to clients;
this.relay.on('fork_event', (data) => { this.relay.on('lib_update', (data) => {
console.log(data); if (this.server.manager.conn.chains[this.server.manager.chain].chain_id === data.chain_id) {
if (this.server.manager.conn.chains[this.server.manager.chain].chain_id === data.chain_id) { this.io.emit('lib_update', data);
this.io.emit('fork_event', data); }
} });
});
}
emitToClient(traceData, type) { // Relay LIB info to clients;
if (this.io.sockets.connected[traceData.client]) { this.relay.on('fork_event', (data) => {
this.io.sockets.connected[traceData.client].emit('message', { hLog(data);
type: type, if (this.server.manager.conn.chains[this.server.manager.chain].chain_id === data.chain_id) {
mode: 'live', this.io.emit('fork_event', data);
message: traceData.message, }
}); });
} }
}
emitToRelay(data, type, socket, callback) { emitToClient(traceData, type) {
if (this.relay.connected) { if (this.io.sockets.sockets.has(traceData.client)) {
this.relay.emit('event', { this.io.sockets.sockets.get(traceData.client).emit('message', {
type: type, type: type,
client_socket: socket.id, mode: 'live',
request: data, message: traceData.message,
}, (response) => { });
callback(response); }
}); }
} else {
callback('STREAMING_OFFLINE'); 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": { "api": {
"enabled": true,
"pm2_scaling": 1,
"node_max_old_space_size": 1024,
"chain_name": "EXAMPLE Chain", "chain_name": "EXAMPLE Chain",
"server_addr": "127.0.0.1", "server_addr": "127.0.0.1",
"server_port": 7000, "server_port": 7000,
@@ -19,19 +22,48 @@
"get_trx_actions": 200 "get_trx_actions": 200
}, },
"access_log": false, "access_log": false,
"enable_explorer": false,
"chain_api_error_log": false, "chain_api_error_log": false,
"custom_core_token": "", "custom_core_token": "",
"enable_export_action": false, "enable_export_action": false,
"disable_rate_limit": false,
"rate_limit_rpm": 1000,
"rate_limit_allow": [],
"disable_tx_cache": false, "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": { "settings": {
"preview": false, "preview": false,
"chain": "eos", "chain": "eos",
"eosio_alias": "eosio", "eosio_alias": "eosio",
"parser": "1.8", "parser": "2.1",
"auto_stop": 300, "auto_stop": 0,
"index_version": "v1", "index_version": "v1",
"debug": false, "debug": false,
"bp_logs": false, "bp_logs": false,
@@ -39,12 +71,14 @@
"ipc_debug_rate": 60000, "ipc_debug_rate": 60000,
"allow_custom_abi": false, "allow_custom_abi": false,
"rate_monitoring": true, "rate_monitoring": true,
"max_ws_payload_kb": 256, "max_ws_payload_mb": 256,
"ds_profiling": false, "ds_profiling": false,
"auto_mode_switch": false, "auto_mode_switch": false,
"hot_warm_policy": false, "hot_warm_policy": false,
"custom_policy": "", "custom_policy": "",
"bypass_index_map": false "bypass_index_map": false,
"index_partition_size": 10000000,
"es_replicas": 0
}, },
"blacklists": { "blacklists": {
"actions": [], "actions": [],
@@ -63,29 +97,16 @@
"ds_pool_size": 1, "ds_pool_size": 1,
"indexing_queues": 1, "indexing_queues": 1,
"ad_idx_queues": 1, "ad_idx_queues": 1,
"dyn_idx_queues": 1,
"max_autoscale": 4, "max_autoscale": 4,
"batch_size": 5000, "batch_size": 5000,
"resume_trigger": 5000, "resume_trigger": 5000,
"auto_scale_trigger": 20000, "auto_scale_trigger": 20000,
"block_queue_limit": 10000, "block_queue_limit": 10000,
"max_queue_limit": 100000, "max_queue_limit": 100000,
"routing_mode": "heatmap", "routing_mode": "round_robin",
"polling_interval": 10000 "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": { "features": {
"streaming": { "streaming": {
"enable": false, "enable": false,
@@ -109,5 +130,6 @@
"read": 50, "read": 50,
"block": 100, "block": 100,
"index": 500 "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"); debugLog("[AMQP] connection established");
return conn; return conn;
} catch (e) { } catch (e) {
console.log("[AMQP] failed to connect!"); hLog("[AMQP] failed to connect!");
console.log(e.message); hLog(e.message);
await new Promise(resolve => setTimeout(resolve, 5000)); await new Promise(resolve => setTimeout(resolve, 5000));
return await createConnection(config); return await createConnection(config);
} }
@@ -34,8 +34,8 @@ async function createChannels(connection) {
const confirmChannel = await connection.createConfirmChannel(); const confirmChannel = await connection.createConfirmChannel();
return [channel, confirmChannel]; return [channel, confirmChannel];
} catch (e) { } catch (e) {
console.log("[AMQP] failed to create channels"); hLog("[AMQP] failed to create channels");
console.error(e); hLog(e);
return null; return null;
} }
} }
@@ -70,7 +70,7 @@ export async function amqpConnect(onReconnect, config, onClose) {
export async function checkQueueSize(q_name, config) { export async function checkQueueSize(q_name, config) {
try { try {
const v = encodeURIComponent(config.vhost); 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 = { const opts = {
username: config.user, username: config.user,
password: config.pass password: config.pass
@@ -78,13 +78,13 @@ export async function checkQueueSize(q_name, config) {
const data = await got.get(apiUrl, opts).json() as any; const data = await got.get(apiUrl, opts).json() as any;
return data.messages; return data.messages;
} catch (e) { } catch (e) {
hLog('[WARNING] Checking queue size failed, HTTP API is not ready!'); hLog(`[WARNING] Checking queue size failed! - ${e.message}`);
if (e instanceof HTTPError) { if (e.response && e.response.body) {
if(e.response.body) { if (e instanceof HTTPError) {
hLog(e.response.body); hLog(e.response.body);
} else {
hLog(JSON.stringify(e.response.body, null, 2));
} }
} else {
hLog(JSON.stringify(e, null, 2));
} }
return 0; return 0;
} }
+148 -134
View File
@@ -1,5 +1,5 @@
import {ConfigurationModule} from "../modules/config"; import {ConfigurationModule} from "../modules/config";
import {JsonRpc} from "eosjs/dist"; import {JsonRpc} from "eosjs";
import got from "got"; import got from "got";
import {Client} from '@elastic/elasticsearch' import {Client} from '@elastic/elasticsearch'
import {HyperionConnections} from "../interfaces/hyperionConnections"; import {HyperionConnections} from "../interfaces/hyperionConnections";
@@ -12,153 +12,167 @@ import {hLog} from "../helpers/common_functions";
export class ConnectionManager { export class ConnectionManager {
config: HyperionConfig; config: HyperionConfig;
conn: HyperionConnections; conn: HyperionConnections;
chain: string; chain: string;
last_commit_hash: string; last_commit_hash: string;
current_version: string; current_version: string;
private esIngestClients: Client[]; private readonly esIngestClients: Client[];
private esIngestClient: Client; private esIngestClient: Client;
constructor(private cm: ConfigurationModule) { constructor(private cm: ConfigurationModule) {
this.config = cm.config; this.config = cm.config;
this.conn = cm.connections; this.conn = cm.connections;
this.chain = this.config.settings.chain; if (!this.conn.amqp.protocol) {
this.esIngestClients = []; this.conn.amqp.protocol = 'http';
this.prepareESClient(); }
this.prepareIngestClients(); this.chain = this.config.settings.chain;
} this.esIngestClients = [];
this.prepareESClient();
this.prepareIngestClients();
}
get nodeosJsonRPC() { get nodeosJsonRPC() {
// @ts-ignore // @ts-ignore
return new JsonRpc(this.conn.chains[this.chain].http, {fetch}); return new JsonRpc(this.conn.chains[this.chain].http, {fetch});
} }
async purgeQueues() { async purgeQueues() {
hLog(`Purging all ${this.chain} queues!`); hLog(`Purging all ${this.chain} queues!`);
const apiUrl = `http://${this.conn.amqp.api}`; const apiUrl = `http://${this.conn.amqp.api}`;
const getAllQueuesFromVHost = apiUrl + `/api/queues/%2F${this.conn.amqp.vhost}`; const vHost = encodeURIComponent(this.conn.amqp.vhost);
const opts = { const getAllQueuesFromVHost = apiUrl + `/api/queues/${vHost}`;
username: this.conn.amqp.user, const opts = {
password: this.conn.amqp.pass username: this.conn.amqp.user,
}; password: this.conn.amqp.pass
let result; };
try { let result;
const data = await got(getAllQueuesFromVHost, opts); try {
if (data) { const data = await got(getAllQueuesFromVHost, opts);
result = JSON.parse(data.body); if (data) {
} result = JSON.parse(data.body);
} catch (e) { }
console.log(e.message); } catch (e) {
console.error('failed to connect to rabbitmq http api'); console.log(e.message);
process.exit(1); console.error('failed to connect to rabbitmq http api');
} process.exit(1);
if (result) { }
for (const queue of result) { if (result) {
if (queue.name.startsWith(this.chain + ":")) { for (const queue of result) {
const msg_count = parseInt(queue.messages); if (queue.name.startsWith(this.chain + ":")) {
if (msg_count > 0) { const msg_count = parseInt(queue.messages);
try { if (msg_count > 0) {
await got.delete(apiUrl + `/api/queues/%2F${this.conn.amqp.vhost}/${queue.name}/contents`, opts); try {
hLog(`${queue.messages} messages deleted on queue ${queue.name}`); await got.delete(apiUrl + `/api/queues/${vHost}/${queue.name}/contents`, opts);
} catch (e) { hLog(`${queue.messages} messages deleted on queue ${queue.name}`);
console.log(e.message); } catch (e) {
console.error('failed to connect to rabbitmq http api'); console.log(e.message);
process.exit(1); console.error('failed to connect to rabbitmq http api');
} process.exit(1);
} }
} }
} }
} }
} }
}
prepareESClient() { prepareESClient() {
let es_url; let es_url;
const _es = this.conn.elasticsearch; const _es = this.conn.elasticsearch;
if (!_es.protocol) { if (!_es.protocol) {
_es.protocol = 'http'; _es.protocol = 'http';
} }
if (_es.user !== '') { if (_es.user !== '') {
es_url = `${_es.protocol}://${_es.user}:${_es.pass}@${_es.host}`; es_url = `${_es.protocol}://${_es.user}:${_es.pass}@${_es.host}`;
} else { } else {
es_url = `${_es.protocol}://${_es.host}` es_url = `${_es.protocol}://${_es.host}`
} }
this.esIngestClient = new Client({ this.esIngestClient = new Client({
node: es_url, node: es_url,
ssl: { ssl: {
rejectUnauthorized: false rejectUnauthorized: false
} }
}); });
} }
get elasticsearchClient() { get elasticsearchClient() {
return this.esIngestClient; return this.esIngestClient;
} }
prepareIngestClients() { prepareIngestClients() {
const _es = this.conn.elasticsearch; const _es = this.conn.elasticsearch;
if (!_es.protocol) { if (!_es.protocol) {
_es.protocol = 'http'; _es.protocol = 'http';
} }
if (_es.ingest_nodes) { if (_es.ingest_nodes) {
if (_es.ingest_nodes.length > 0) { if (_es.ingest_nodes.length > 0) {
for (const node of _es.ingest_nodes) { for (const node of _es.ingest_nodes) {
let es_url; let es_url;
if (_es.user !== '') { if (_es.user !== '') {
es_url = `${_es.protocol}://${_es.user}:${_es.pass}@${node}`; es_url = `${_es.protocol}://${_es.user}:${_es.pass}@${node}`;
} else { } else {
es_url = `${_es.protocol}://${node}` es_url = `${_es.protocol}://${node}`
} }
this.esIngestClients.push(new Client({ this.esIngestClients.push(new Client({
node: es_url, node: es_url,
pingTimeout: 100, pingTimeout: 100,
ssl: { ssl: {
rejectUnauthorized: false rejectUnauthorized: false
} }
})); }));
} }
} }
} }
} }
get ingestClients() { get ingestClients() {
if (this.esIngestClients.length > 0) { if (this.esIngestClients.length > 0) {
return this.esIngestClients; return this.esIngestClients;
} else { } else {
return [this.esIngestClient]; return [this.esIngestClient];
} }
} }
async createAMQPChannels(onReconnect, onClose) { async createAMQPChannels(onReconnect, onClose) {
return await amqpConnect(onReconnect, this.conn.amqp, onClose); return await amqpConnect(onReconnect, this.conn.amqp, onClose);
} }
async checkQueueSize(queue) { async checkQueueSize(queue) {
return await checkQueueSize(queue, this.conn.amqp); return await checkQueueSize(queue, this.conn.amqp);
} }
get shipClient(): StateHistorySocket { get shipClient(): StateHistorySocket {
return new StateHistorySocket(this.conn.chains[this.config.settings.chain]['ship'], this.config.settings.max_ws_payload_kb); return new StateHistorySocket(this.conn.chains[this.config.settings.chain]['ship'], this.config.settings.max_ws_payload_mb);
} }
get ampqUrl() { get ampqUrl() {
return getAmpqUrl(this.conn.amqp); return getAmpqUrl(this.conn.amqp);
} }
calculateServerHash() { calculateServerHash() {
exec('git rev-parse HEAD', (err, stdout) => { exec('git rev-parse HEAD', (err, stdout) => {
console.log('Last commit hash on this branch is:', stdout); if (err) {
this.last_commit_hash = stdout.trim(); // 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() { getServerHash() {
return this.last_commit_hash; return this.last_commit_hash;
} }
getHyperionVersion() { getHyperionVersion() {
this.current_version = require('../package.json').version 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"; import {debugLog, hLog} from "../helpers/common_functions";
const WebSocket = require('ws'); import WebSocket from 'ws';
export class StateHistorySocket { export class StateHistorySocket {
private ws; private ws;
private readonly shipUrl; private readonly shipUrl;
private readonly max_payload_kb; private readonly max_payload_mb;
constructor(ship_url, max_payload_kb) { constructor(ship_url, max_payload_mb) {
this.shipUrl = ship_url; this.shipUrl = ship_url;
if (max_payload_kb) { if (max_payload_mb) {
this.max_payload_kb = max_payload_kb; this.max_payload_mb = max_payload_mb;
} else { } else {
this.max_payload_kb = 256; this.max_payload_mb = 256;
} }
} }
connect(onMessage, onDisconnect, onError, onConnected) { 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}`);
});
}
close() { debugLog(`Connecting to ${this.shipUrl}...`);
this.ws.close(); 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) { close() {
this.ws.send(payload); this.ws.close();
} }
send(payload) {
this.ws.send(payload);
}
} }
+232 -228
View File
@@ -1,260 +1,264 @@
const AbiDefinitions = { const AbiDefinitions = {
version: "eosio::abi/1.1", version: 'eosio::abi/1.1',
structs: [ structs: [
{
name: 'extensions_entry',
base: '',
fields: [
{ {
name: "extensions_entry", name: 'tag',
base: "", type: 'uint16',
fields: [
{
name: "tag",
type: "uint16"
},
{
name: "value",
type: "bytes"
}
]
}, },
{ {
name: "type_def", name: 'value',
base: "", type: 'bytes',
fields: [ },
{ ],
name: "new_type_name", },
type: "string" {
}, name: 'type_def',
{ base: '',
name: "type", fields: [
type: "string" {
} name: 'new_type_name',
] type: 'string',
}, },
{ {
name: "field_def", name: 'type',
base: "", type: 'string',
fields: [ },
{ ],
name: "name", },
type: "string" {
}, name: 'field_def',
{ base: '',
name: "type", fields: [
type: "string" {
} name: 'name',
] type: 'string',
}, },
{ {
name: "struct_def", name: 'type',
base: "", type: 'string',
fields: [ },
{ ],
name: "name", },
type: "string" {
}, name: 'struct_def',
{ base: '',
name: "base", fields: [
type: "string" {
}, name: 'name',
{ type: 'string',
name: "fields",
type: "field_def[]"
}
]
}, },
{ {
name: "action_def", name: 'base',
base: "", type: 'string',
fields: [
{
name: "name",
type: "name"
},
{
name: "type",
type: "string"
},
{
name: "ricardian_contract",
type: "string"
}
]
}, },
{ {
name: "table_def", name: 'fields',
base: "", type: 'field_def[]',
fields: [ },
{ ],
name: "name", },
type: "name" {
}, name: 'action_def',
{ base: '',
name: "index_type", fields: [
type: "string" {
}, name: 'name',
{ type: 'name',
name: "key_names",
type: "string[]"
},
{
name: "key_types",
type: "string[]"
},
{
name: "type",
type: "string"
}
]
}, },
{ {
name: "clause_pair", name: 'type',
base: "", type: 'string',
fields: [
{
name: "id",
type: "string"
},
{
name: "body",
type: "string"
}
]
}, },
{ {
name: "error_message", name: 'ricardian_contract',
base: "", type: 'string',
fields: [ },
{ ],
name: "error_code", },
type: "uint64" {
}, name: 'table_def',
{ base: '',
name: "error_msg", fields: [
type: "string" {
} name: 'name',
] type: 'name',
}, },
{ {
name: "variant_def", name: 'index_type',
base: "", type: 'string',
fields: [
{
name: "name",
type: "string"
},
{
name: "types",
type: "string[]"
}
]
}, },
{ {
name: "abi_def", name: 'key_names',
base: "", type: 'string[]',
fields: [ },
{ {
name: "version", name: 'key_types',
type: "string" type: 'string[]',
}, },
{ {
name: "types", name: 'type',
type: "type_def[]" type: 'string',
}, },
{ ],
name: "structs", },
type: "struct_def[]" {
}, name: 'clause_pair',
{ base: '',
name: "actions", fields: [
type: "action_def[]" {
}, name: 'id',
{ type: 'string',
name: "tables", },
type: "table_def[]" {
}, name: 'body',
{ type: 'string',
name: "ricardian_clauses", },
type: "clause_pair[]" ],
}, },
{ {
name: "error_messages", name: 'error_message',
type: "error_message[]" base: '',
}, fields: [
{ {
name: "abi_extensions", name: 'error_code',
type: "extensions_entry[]" type: 'uint64',
}, },
{ {
name: "variants", name: 'error_msg',
type: "variant_def[]$" 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 = { const RexAbi = {
version: "eosio::abi/1.1", version: 'eosio::abi/1.1',
types: [], types: [],
structs: [ structs: [
{
name: 'buyresult',
base: '',
fields: [
{ {
name: "buyresult", name: 'rex_received',
base: "", type: 'asset',
fields: [{ },
name: "rex_received", ],
type: "asset" }, {
} name: 'orderresult',
] base: '',
fields: [
{
name: 'owner',
type: 'name',
}, { }, {
name: "orderresult", name: 'proceeds',
base: "", type: 'asset',
fields: [{
name: "owner",
type: "name"
}, {
name: "proceeds",
type: "asset"
}
]
}, {
name: "rentresult",
base: "",
fields: [{
name: "rented_tokens",
type: "asset"
}
]
}, },
],
}, {
name: 'rentresult',
base: '',
fields: [
{ {
name: "sellresult", name: 'rented_tokens',
base: "", type: 'asset',
fields: [{
name: "proceeds",
type: "asset"
}]
}
],
actions: [
{
name: "buyresult",
type: "buyresult",
ricardian_contract: ""
}, },
],
},
{
name: 'sellresult',
base: '',
fields: [
{ {
name: "orderresult", name: 'proceeds',
type: "orderresult", type: 'asset',
ricardian_contract: "" }],
}, },
{ ],
name: "rentresult", actions: [
type: "rentresult", {
ricardian_contract: "" name: 'buyresult',
}, type: 'buyresult',
{ ricardian_contract: '',
name: "sellresult", },
type: "sellresult", {
ricardian_contract: "" name: 'orderresult',
} type: 'orderresult',
] ricardian_contract: '',
},
{
name: 'rentresult',
type: 'rentresult',
ricardian_contract: '',
},
{
name: 'sellresult',
type: 'sellresult',
ricardian_contract: '',
},
],
}; };
module.exports = {AbiDefinitions, RexAbi}; module.exports = {AbiDefinitions, RexAbi};
+46 -33
View File
@@ -1,38 +1,51 @@
function addIndexer(chainName) { function interpreterArgs(heap) {
return { const arr = ['--trace-deprecation', '--trace-warnings'];
script: './launcher.js', if (heap) {
name: chainName + '-indexer', arr.push('--max-old-space-size=' + heap);
namespace: chainName, } else {
interpreter: 'node', arr.push('--max-old-space-size=2048');
interpreter_args: ['--max-old-space-size=4096', '--trace-deprecation'], }
autorestart: false, if (process.env.INSPECT) {
kill_timeout: 3600, arr.push('--inspect');
watch: false, }
time: true, return arr;
env: {
CONFIG_JSON: 'chains/' + chainName + '.config.json',
TRACE_LOGS: 'false',
},
};
} }
function addApiServer(chainName, threads) { function addIndexer(chainName, heap) {
return { return {
script: './api/server.js', script: './launcher.js',
name: chainName + '-api', name: chainName + '-indexer',
namespace: chainName, namespace: chainName,
node_args: ['--trace-deprecation'], interpreter: 'node',
exec_mode: 'cluster', interpreter_args: interpreterArgs(heap),
merge_logs: true, autorestart: false,
instances: threads, kill_timeout: 3600,
autorestart: true, watch: false,
exp_backoff_restart_delay: 100, time: true,
watch: false, env: {
time: true, CONFIG_JSON: 'chains/' + chainName + '.config.json',
env: { TRACE_LOGS: 'false',
CONFIG_JSON: 'chains/' + chainName + '.config.json', },
}, };
}; }
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}; module.exports = {addIndexer, addApiServer};
+1
View File
@@ -1,5 +1,6 @@
import {IlmPutLifecycle} from "@elastic/elasticsearch/api/requestParams"; import {IlmPutLifecycle} from "@elastic/elasticsearch/api/requestParams";
// noinspection JSUnusedGlobalSymbols
export const ILPs: IlmPutLifecycle[] = [ export const ILPs: IlmPutLifecycle[] = [
{ {
policy: "hyperion-rollover", policy: "hyperion-rollover",
+431 -428
View File
@@ -1,9 +1,7 @@
import {ConfigurationModule} from "../modules/config"; import {ConfigurationModule} from "../modules/config";
const shards = 2; const shards = 2;
const replicas = 0;
const refresh = "1s"; const refresh = "1s";
let defaultLifecyclePolicy = "200G";
export * from './index-lifecycle-policies'; export * from './index-lifecycle-policies';
@@ -15,507 +13,512 @@ const compression = "best_compression";
const cm = new ConfigurationModule(); const cm = new ConfigurationModule();
const chain = cm.config.settings.chain; const chain = cm.config.settings.chain;
if (cm.config.settings.hot_warm_policy) { // update number of replicas if set to larger than 0
defaultLifecyclePolicy = "hyperion-rollover"; let replicas = 0;
} if (cm.config.settings.es_replicas) {
replicas = cm.config.settings.es_replicas;
if(cm.config.settings.custom_policy) {
defaultLifecyclePolicy = cm.config.settings.custom_policy;
} }
const defaultIndexSettings = { const defaultIndexSettings = {
"index": { "index": {
"number_of_shards": shards, "number_of_shards": shards,
"refresh_interval": refresh, "refresh_interval": refresh,
"number_of_replicas": replicas, "number_of_replicas": replicas,
"codec": compression "codec": compression
} }
}; };
const actionSettings = { const actionSettings = {
index: { index: {
lifecycle: { codec: compression,
"name": defaultLifecyclePolicy, refresh_interval: refresh,
"rollover_alias": chain + "-action" number_of_shards: shards * 2,
}, number_of_replicas: replicas,
codec: compression, sort: {
refresh_interval: refresh, field: "global_sequence",
number_of_shards: shards * 2, order: "desc"
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) { if (cm.config.settings.hot_warm_policy) {
actionSettings["routing"] = {"allocation": {"exclude": {"data": "warm"}}}; actionSettings["routing"] = {"allocation": {"exclude": {"data": "warm"}}};
} }
export const action = { export const action = {
order: 0, order: 0,
index_patterns: [ index_patterns: [
chain + "-action-*" chain + "-action-*"
], ],
settings: actionSettings, settings: actionSettings,
mappings: { mappings: {
properties: { properties: {
"@timestamp": {"type": "date"}, "@timestamp": {"type": "date"},
"ds_error": {"type": "boolean"}, "ds_error": {"type": "boolean"},
"global_sequence": {"type": "long"}, "global_sequence": {"type": "long"},
"account_ram_deltas.delta": {"type": "integer"}, "account_ram_deltas.delta": {"type": "integer"},
"account_ram_deltas.account": {"type": "keyword"}, "account_ram_deltas.account": {"type": "keyword"},
"act.authorization.permission": {"enabled": false}, "act.authorization.permission": {"enabled": false},
"act.authorization.actor": {"type": "keyword"}, "act.authorization.actor": {"type": "keyword"},
"act.account": {"type": "keyword"}, "act.account": {"type": "keyword"},
"act.name": {"type": "keyword"}, "act.name": {"type": "keyword"},
"act.data": {"enabled": false}, "act.data": {"enabled": false},
"block_num": {"type": "long"}, "block_num": {"type": "long"},
"action_ordinal": {"type": "long"}, "block_id": {"type": "keyword"},
"creator_action_ordinal": {"type": "long"}, "action_ordinal": {"type": "long"},
"cpu_usage_us": {"type": "integer"}, "creator_action_ordinal": {"type": "long"},
"net_usage_words": {"type": "integer"}, "cpu_usage_us": {"type": "integer"},
"code_sequence": {"type": "integer"}, "net_usage_words": {"type": "integer"},
"abi_sequence": {"type": "integer"}, "code_sequence": {"type": "integer"},
"trx_id": {"type": "keyword"}, "abi_sequence": {"type": "integer"},
"producer": {"type": "keyword"}, "trx_id": {"type": "keyword"},
"notified": {"type": "keyword"}, "producer": {"type": "keyword"},
"signatures": {"enabled": false}, "notified": {"type": "keyword"},
"inline_count": {"type": "short"}, "signatures": {"enabled": false},
"max_inline": {"type": "short"}, "inline_count": {"type": "short"},
"inline_filtered": {"type": "boolean"}, "max_inline": {"type": "short"},
"receipts": { "inline_filtered": {"type": "boolean"},
"properties": { "receipts": {
"global_sequence": {"type": "long"}, "properties": {
"recv_sequence": {"type": "long"}, "global_sequence": {"type": "long"},
"receiver": {"type": "keyword"}, "recv_sequence": {"type": "long"},
"auth_sequence": { "receiver": {"type": "keyword"},
"properties": { "auth_sequence": {
"account": {"type": "keyword"}, "properties": {
"sequence": {"type": "long"} "account": {"type": "keyword"},
} "sequence": {"type": "long"}
} }
} }
}, }
},
// eosio::newaccount // eosio::newaccount
"@newaccount": { "@newaccount": {
"properties": { "properties": {
"active": {"type": "object"}, "active": {"type": "object"},
"owner": {"type": "object"}, "owner": {"type": "object"},
"newact": {"type": "keyword"} "newact": {"type": "keyword"}
} }
}, },
// eosio::updateauth // eosio::updateauth
"@updateauth": { "@updateauth": {
"properties": { "properties": {
"permission": {"type": "keyword"}, "permission": {"type": "keyword"},
"parent": {"type": "keyword"}, "parent": {"type": "keyword"},
"auth": {"type": "object"} "auth": {"type": "object"}
} }
}, },
// *::transfer // *::transfer
"@transfer": { "@transfer": {
"properties": { "properties": {
"from": {"type": "keyword"}, "from": {"type": "keyword"},
"to": {"type": "keyword"}, "to": {"type": "keyword"},
"amount": {"type": "float"}, "amount": {"type": "float"},
"symbol": {"type": "keyword"}, "symbol": {"type": "keyword"},
"memo": {"type": "text"} "memo": {"type": "text"}
} }
}, },
// eosio::unstaketorex // eosio::unstaketorex
"@unstaketorex": { "@unstaketorex": {
"properties": { "properties": {
"owner": {"type": "keyword"}, "owner": {"type": "keyword"},
"receiver": {"type": "keyword"}, "receiver": {"type": "keyword"},
"amount": {"type": "float"} "amount": {"type": "float"}
} }
}, },
// eosio::buyrex // eosio::buyrex
"@buyrex": { "@buyrex": {
"properties": { "properties": {
"from": {"type": "keyword"}, "from": {"type": "keyword"},
"amount": {"type": "float"} "amount": {"type": "float"}
} }
}, },
// eosio::buyram // eosio::buyram
"@buyram": { "@buyram": {
"properties": { "properties": {
"payer": {"type": "keyword"}, "payer": {"type": "keyword"},
"receiver": {"type": "keyword"}, "receiver": {"type": "keyword"},
"quant": {"type": "float"} "quant": {"type": "float"}
} }
}, },
// eosio::buyrambytes // eosio::buyrambytes
"@buyrambytes": { "@buyrambytes": {
"properties": { "properties": {
"payer": {"type": "keyword"}, "payer": {"type": "keyword"},
"receiver": {"type": "keyword"}, "receiver": {"type": "keyword"},
"bytes": {"type": "long"} "bytes": {"type": "long"}
} }
}, },
// eosio::delegatebw // eosio::delegatebw
"@delegatebw": { "@delegatebw": {
"properties": { "properties": {
"from": {"type": "keyword"}, "from": {"type": "keyword"},
"receiver": {"type": "keyword"}, "receiver": {"type": "keyword"},
"stake_cpu_quantity": {"type": "float"}, "stake_cpu_quantity": {"type": "float"},
"stake_net_quantity": {"type": "float"}, "stake_net_quantity": {"type": "float"},
"transfer": {"type": "boolean"}, "transfer": {"type": "boolean"},
"amount": {"type": "float"} "amount": {"type": "float"}
} }
}, },
// eosio::undelegatebw // eosio::undelegatebw
"@undelegatebw": { "@undelegatebw": {
"properties": { "properties": {
"from": {"type": "keyword"}, "from": {"type": "keyword"},
"receiver": {"type": "keyword"}, "receiver": {"type": "keyword"},
"unstake_cpu_quantity": {"type": "float"}, "unstake_cpu_quantity": {"type": "float"},
"unstake_net_quantity": {"type": "float"}, "unstake_net_quantity": {"type": "float"},
"amount": {"type": "float"} "amount": {"type": "float"}
} }
} }
} }
} }
}; };
const deltaSettings = { const deltaSettings = {
"index": { index: {
"lifecycle": { codec: compression,
"name": defaultLifecyclePolicy, number_of_shards: shards * 2,
"rollover_alias": chain + "-delta" refresh_interval: refresh,
}, number_of_replicas: replicas,
"codec": compression, sort: {
"number_of_shards": shards * 2, field: ["block_num", "scope", "primary_key"],
"refresh_interval": refresh, order: ["desc", "asc", "asc"]
"number_of_replicas": replicas, }
"sort.field": ["block_num", "scope", "primary_key"], }
"sort.order": ["desc", "asc", "asc"]
}
}; };
// deltaSettings.index["lifecycle"] = {
// "name": defaultLifecyclePolicy,
// "rollover_alias": chain + "-delta"
// };
if (cm.config.settings.hot_warm_policy) { if (cm.config.settings.hot_warm_policy) {
deltaSettings["routing"] = {"allocation": {"exclude": {"data": "warm"}}}; deltaSettings["routing"] = {"allocation": {"exclude": {"data": "warm"}}};
} }
export const delta = { export const delta = {
"index_patterns": [chain + "-delta-*"], "index_patterns": [chain + "-delta-*"],
"settings": deltaSettings, "settings": deltaSettings,
"mappings": { "mappings": {
"properties": { "properties": {
// base fields // base fields
"@timestamp": {"type": "date"}, "@timestamp": {"type": "date"},
"ds_error": {"type": "boolean"}, "present": {"type": "byte"},
"block_id": {"type": "keyword"}, "ds_error": {"type": "boolean"},
"block_num": {"type": "long"}, "block_id": {"type": "keyword"},
"deleted_at": {"type": "long"}, "block_num": {"type": "long"},
"code": {"type": "keyword"}, "deleted_at": {"type": "long"},
"scope": {"type": "keyword"}, "code": {"type": "keyword"},
"table": {"type": "keyword"}, "scope": {"type": "keyword"},
"payer": {"type": "keyword"}, "table": {"type": "keyword"},
"primary_key": {"type": "keyword"}, "payer": {"type": "keyword"},
"data": {"enabled": false}, "primary_key": {"type": "keyword"},
"value": {"enabled": false}, "data": {"enabled": false},
"value": {"enabled": false},
// eosio.msig::approvals // eosio.msig::approvals
"@approvals.proposal_name": {"type": "keyword"}, "@approvals.proposal_name": {"type": "keyword"},
"@approvals.provided_approvals": {"type": "object"}, "@approvals.provided_approvals": {"type": "object"},
"@approvals.requested_approvals": {"type": "object"}, "@approvals.requested_approvals": {"type": "object"},
// eosio.msig::proposal // eosio.msig::proposal
"@proposal.proposal_name": {"type": "keyword"}, "@proposal.proposal_name": {"type": "keyword"},
"@proposal.transaction": {"enabled": false}, "@proposal.transaction": {"enabled": false},
// *::accounts // *::accounts
"@accounts.amount": {"type": "float"}, "@accounts.amount": {"type": "float"},
"@accounts.symbol": {"type": "keyword"}, "@accounts.symbol": {"type": "keyword"},
// eosio::voters // eosio::voters
"@voters.is_proxy": {"type": "boolean"}, "@voters.is_proxy": {"type": "boolean"},
"@voters.producers": {"type": "keyword"}, "@voters.producers": {"type": "keyword"},
"@voters.last_vote_weight": {"type": "double"}, "@voters.last_vote_weight": {"type": "double"},
"@voters.proxied_vote_weight": {"type": "double"}, "@voters.proxied_vote_weight": {"type": "double"},
"@voters.staked": {"type": "float"}, "@voters.staked": {"type": "float"},
"@voters.proxy": {"type": "keyword"}, "@voters.proxy": {"type": "keyword"},
// eosio::producers // eosio::producers
"@producers.total_votes": {"type": "double"}, "@producers.total_votes": {"type": "double"},
"@producers.is_active": {"type": "boolean"}, "@producers.is_active": {"type": "boolean"},
"@producers.unpaid_blocks": {"type": "long"}, "@producers.unpaid_blocks": {"type": "long"},
// eosio::global // eosio::global
"@global": { "@global": {
"properties": { "properties": {
"last_name_close": {"type": "date"}, "last_name_close": {"type": "date"},
"last_pervote_bucket_fill": {"type": "date"}, "last_pervote_bucket_fill": {"type": "date"},
"last_producer_schedule_update": {"type": "date"}, "last_producer_schedule_update": {"type": "date"},
"perblock_bucket": {"type": "double"}, "perblock_bucket": {"type": "double"},
"pervote_bucket": {"type": "double"}, "pervote_bucket": {"type": "double"},
"total_activated_stake": {"type": "double"}, "total_activated_stake": {"type": "double"},
"total_voteshare_change_rate": {"type": "double"}, "total_voteshare_change_rate": {"type": "double"},
"total_unpaid_voteshare": {"type": "double"}, "total_unpaid_voteshare": {"type": "double"},
"total_producer_vote_weight": {"type": "double"}, "total_producer_vote_weight": {"type": "double"},
"total_ram_bytes_reserved": {"type": "long"}, "total_ram_bytes_reserved": {"type": "long"},
"total_ram_stake": {"type": "long"}, "total_ram_stake": {"type": "long"},
"total_unpaid_blocks": {"type": "long"}, "total_unpaid_blocks": {"type": "long"},
} }
} }
} }
} }
}; };
export const abi = { export const abi = {
"index_patterns": [chain + "-abi-*"], "index_patterns": [chain + "-abi-*"],
"settings": defaultIndexSettings, "settings": defaultIndexSettings,
"mappings": { "mappings": {
"properties": { "properties": {
"@timestamp": {"type": "date"}, "@timestamp": {"type": "date"},
"block": {"type": "long"}, "block": {"type": "long"},
"account": {"type": "keyword"}, "account": {"type": "keyword"},
"abi": {"enabled": false}, "abi": {"enabled": false},
"abi_hex": {"enabled": false}, "abi_hex": {"enabled": false},
"actions": {"type": "keyword"}, "actions": {"type": "keyword"},
"tables": {"type": "keyword"} "tables": {"type": "keyword"}
} }
} }
}; };
export const permissionLink = { export const permissionLink = {
"index_patterns": [chain + "-link-*"], "index_patterns": [chain + "-link-*"],
"settings": defaultIndexSettings, "settings": defaultIndexSettings,
"mappings": { "mappings": {
"properties": { "properties": {
"block_num": {"type": "long"}, "block_num": {"type": "long"},
"@timestamp": {"type": "date"}, "@timestamp": {"type": "date"},
"present": {"type": "boolean"}, "present": {"type": "byte"},
"account": {"type": "keyword"}, "account": {"type": "keyword"},
"code": {"type": "keyword"}, "code": {"type": "keyword"},
"action": {"type": "keyword"}, "action": {"type": "keyword"},
"permission": {"type": "keyword"} "permission": {"type": "keyword"}
} }
} }
}; };
export const permission = { export const permission = {
"index_patterns": [chain + "-perm-*"], "index_patterns": [chain + "-perm-*"],
"settings": defaultIndexSettings, "settings": defaultIndexSettings,
"mappings": { "mappings": {
"properties": { "properties": {
"block_num": {"type": "long"}, "block_num": {"type": "long"},
"present": {"type": "boolean"}, "present": {"type": "byte"},
"owner": {"type": "keyword"}, "owner": {"type": "keyword"},
"name": {"type": "keyword"}, "name": {"type": "keyword"},
"parent": {"type": "keyword"}, "parent": {"type": "keyword"},
"last_updated": {"type": "date"}, "last_updated": {"type": "date"},
"auth": {"type": "object"} "auth": {"type": "object"}
} }
} }
}; };
export const resourceLimits = { export const resourceLimits = {
"index_patterns": [chain + "-reslimits-*"], "index_patterns": [chain + "-reslimits-*"],
"settings": defaultIndexSettings, "settings": defaultIndexSettings,
"mappings": { "mappings": {
"properties": { "properties": {
"block_num": {"type": "long"}, "block_num": {"type": "long"},
"@timestamp": {"type": "date"}, "@timestamp": {"type": "date"},
"owner": {"type": "keyword"}, "owner": {"type": "keyword"},
"total_weight": {"type": "long"}, "total_weight": {"type": "long"},
"net_weight": {"type": "long"}, "net_weight": {"type": "long"},
"cpu_weight": {"type": "long"}, "cpu_weight": {"type": "long"},
"ram_bytes": {"type": "long"} "ram_bytes": {"type": "long"}
} }
} }
}; };
export const generatedTransaction = { export const generatedTransaction = {
"index_patterns": [chain + "-gentrx-*"], "index_patterns": [chain + "-gentrx-*"],
"settings": defaultIndexSettings, "settings": defaultIndexSettings,
"mappings": { "mappings": {
"properties": { "properties": {
"block_num": {"type": "long"}, "block_num": {"type": "long"},
"@timestamp": {"type": "date"}, "@timestamp": {"type": "date"},
"sender": {"type": "keyword"}, "sender": {"type": "keyword"},
"sender_id": {"type": "keyword"}, "sender_id": {"type": "keyword"},
"payer": {"type": "keyword"}, "payer": {"type": "keyword"},
"trx_id": {"type": "keyword"}, "trx_id": {"type": "keyword"},
"actions": {"enabled": false}, "actions": {"enabled": false},
"packed_trx": {"enabled": false} "packed_trx": {"enabled": false}
} }
} }
}; };
export const failedTransaction = { export const failedTransaction = {
"index_patterns": [chain + "-trxerr-*"], "index_patterns": [chain + "-trxerr-*"],
"settings": defaultIndexSettings, "settings": defaultIndexSettings,
"mappings": { "mappings": {
"properties": { "properties": {
"block_num": {"type": "long"}, "block_num": {"type": "long"},
"@timestamp": {"type": "date"}, "@timestamp": {"type": "date"},
"status": {"type": "short"} "status": {"type": "short"}
} }
} }
}; };
export const resourceUsage = { export const resourceUsage = {
"index_patterns": [chain + "-userres-*"], "index_patterns": [chain + "-userres-*"],
"settings": defaultIndexSettings, "settings": defaultIndexSettings,
"mappings": { "mappings": {
"properties": { "properties": {
"block_num": {"type": "long"}, "block_num": {"type": "long"},
"@timestamp": {"type": "date"}, "@timestamp": {"type": "date"},
"owner": {"type": "keyword"}, "owner": {"type": "keyword"},
"net_used": {"type": "long"}, "net_used": {"type": "long"},
"net_total": {"type": "long"}, "net_total": {"type": "long"},
"net_pct": {"type": "float"}, "net_pct": {"type": "float"},
"cpu_used": {"type": "long"}, "cpu_used": {"type": "long"},
"cpu_total": {"type": "long"}, "cpu_total": {"type": "long"},
"cpu_pct": {"type": "float"}, "cpu_pct": {"type": "float"},
"ram": {"type": "long"} "ram": {"type": "long"}
} }
} }
}; };
export const logs = { export const logs = {
"index_patterns": [chain + "-logs-*"], "index_patterns": [chain + "-logs-*"],
"settings": defaultIndexSettings "settings": defaultIndexSettings
}; };
export const block = { export const block = {
"index_patterns": [chain + "-block-*"], "index_patterns": [chain + "-block-*"],
"settings": { "settings": {
"index": { "index": {
"codec": compression, "codec": compression,
"number_of_shards": shards, "number_of_shards": shards,
"refresh_interval": refresh, "refresh_interval": refresh,
"number_of_replicas": replicas, "number_of_replicas": replicas,
"sort.field": "block_num", "sort.field": "block_num",
"sort.order": "desc" "sort.order": "desc"
} }
}, },
"mappings": { "mappings": {
"properties": { "properties": {
"@timestamp": {"type": "date"}, "@timestamp": {"type": "date"},
"block_num": {"type": "long"}, "block_num": {"type": "long"},
"block_id": {"type": "keyword"}, "block_id": {"type": "keyword"},
"prev_id": {"type": "keyword"}, "prev_id": {"type": "keyword"},
"producer": {"type": "keyword"}, "producer": {"type": "keyword"},
"new_producers.producers.block_signing_key": {"enabled": false}, "new_producers.producers.block_signing_key": {"enabled": false},
"new_producers.producers.producer_name": {"type": "keyword"}, "new_producers.producers.producer_name": {"type": "keyword"},
"new_producers.version": {"type": "long"}, "new_producers.version": {"type": "long"},
"schedule_version": {"type": "double"}, "schedule_version": {"type": "double"},
"cpu_usage": {"type": "integer"}, "cpu_usage": {"type": "integer"},
"net_usage": {"type": "integer"} "net_usage": {"type": "integer"}
} }
} }
}; };
export const tableProposals = { export const tableProposals = {
"index_patterns": [chain + "-table-proposals-*"], "index_patterns": [chain + "-table-proposals-*"],
"settings": { "settings": {
"index": { "index": {
"codec": compression, "codec": compression,
"number_of_shards": shards, "number_of_shards": shards,
"refresh_interval": refresh, "refresh_interval": refresh,
"number_of_replicas": replicas, "number_of_replicas": replicas,
"sort.field": "block_num", "sort.field": "block_num",
"sort.order": "desc" "sort.order": "desc"
} }
}, },
"mappings": { "mappings": {
"properties": { "properties": {
"block_num": {"type": "long"}, "block_num": {"type": "long"},
"present": {"type": "boolean"}, "present": {"type": "byte"},
"proposal_name": {"type": "keyword"}, "proposal_name": {"type": "keyword"},
"requested_approvals": {"type": "object"}, "requested_approvals": {"type": "object"},
"provided_approvals": {"type": "object"}, "provided_approvals": {"type": "object"},
"executed": {"type": "boolean"} "executed": {"type": "boolean"}
} }
} }
}; };
export const tableAccounts = { export const tableAccounts = {
"index_patterns": [chain + "-table-accounts-*"], "index_patterns": [chain + "-table-accounts-*"],
"settings": { "settings": {
"index": { "index": {
"codec": compression, "codec": compression,
"number_of_shards": shards, "number_of_shards": shards,
"refresh_interval": refresh, "refresh_interval": refresh,
"number_of_replicas": replicas, "number_of_replicas": replicas,
"sort.field": "amount", "sort.field": "amount",
"sort.order": "desc" "sort.order": "desc"
} }
}, },
"mappings": { "mappings": {
"properties": { "properties": {
"block_num": {"type": "long"}, "block_num": {"type": "long"},
"present": {"type": "boolean"}, "present": {"type": "byte"},
"code": {"type": "keyword"}, "code": {"type": "keyword"},
"scope": {"type": "keyword"}, "scope": {"type": "keyword"},
"amount": {"type": "float"}, "amount": {"type": "float"},
"symbol": {"type": "keyword"} "symbol": {"type": "keyword"}
} }
} }
}; };
// noinspection JSUnusedGlobalSymbols
export const tableDelBand = { export const tableDelBand = {
"index_patterns": [chain + "-table-delband-*"], "index_patterns": [chain + "-table-delband-*"],
"settings": { "settings": {
"index": { "index": {
"codec": compression, "codec": compression,
"number_of_shards": shards, "number_of_shards": shards,
"refresh_interval": refresh, "refresh_interval": refresh,
"number_of_replicas": replicas, "number_of_replicas": replicas,
"sort.field": "total_weight", "sort.field": "total_weight",
"sort.order": "desc" "sort.order": "desc"
} }
}, },
"mappings": { "mappings": {
"properties": { "properties": {
"block_num": {"type": "long"}, "block_num": {"type": "long"},
"from": {"type": "keyword"}, "from": {"type": "keyword"},
"to": {"type": "keyword"}, "to": {"type": "keyword"},
"total_weight": {"type": "float"}, "total_weight": {"type": "float"},
"net_weight": {"type": "float"}, "net_weight": {"type": "float"},
"cpu_weight": {"type": "float"} "cpu_weight": {"type": "float"}
} }
} }
}; };
export const tableVoters = { export const tableVoters = {
"index_patterns": [chain + "-table-voters-*"], "index_patterns": [chain + "-table-voters-*"],
"settings": { "settings": {
"index": { "index": {
"codec": compression, "codec": compression,
"number_of_shards": shards, "number_of_shards": shards,
"refresh_interval": refresh, "refresh_interval": refresh,
"number_of_replicas": replicas, "number_of_replicas": replicas,
"sort.field": "last_vote_weight", "sort.field": "last_vote_weight",
"sort.order": "desc" "sort.order": "desc"
} }
}, },
"mappings": { "mappings": {
"properties": { "properties": {
"block_num": {"type": "long"}, "block_num": {"type": "long"},
"voter": {"type": "keyword"}, "voter": {"type": "keyword"},
"producers": {"type": "keyword"}, "producers": {"type": "keyword"},
"last_vote_weight": {"type": "double"}, "last_vote_weight": {"type": "double"},
"is_proxy": {"type": "boolean"}, "is_proxy": {"type": "boolean"},
"proxied_vote_weight": {"type": "double"}, "proxied_vote_weight": {"type": "double"},
"staked": {"type": "double"}, "staked": {"type": "double"},
"proxy": {"type": "keyword"} "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 FROM ubuntu:20.04
LABEL version="2.1.0" description="EOSIO 2.1.0"
RUN apt-get update && apt-get upgrade -y && apt-get autoremove && apt-get install -y wget netcat 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 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.1.0-1-ubuntu-20.04_amd64.deb && rm -f ./eosio_2.1.0-1-ubuntu-20.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
+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 enable-stale-production = true
# ID of producer controlled by this node (e.g. inita; may specify multiple times) (eosio::producer_plugin)
producer-name = eosio producer-name = eosio
# print contract's output to console (eosio::chain_plugin) ### STATE HISTORY PLUGIN ###
contracts-console = true plugin = eosio::state_history_plugin
state-history-endpoint = 0.0.0.0:8080
# 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)
trace-history = true trace-history = true
# enable chain state history (eosio::state_history_plugin)
chain-state-history = true 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) ### CHAIN API PLUGIN ###
state-history-endpoint = 0.0.0.0:8080
# Plugin(s) to enable, may be specified multiple times
plugin = eosio::producer_api_plugin
plugin = eosio::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 FROM node:16.5
RUN npm install pm2 -g
RUN apt-get update && apt-get upgrade -y && apt-get autoremove && apt-get install -y build-essential git curl netcat RUN git clone https://github.com/eosrio/hyperion-history-api.git
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
WORKDIR /hyperion-history-api WORKDIR /hyperion-history-api
RUN git checkout dev-3.3
RUN npm install RUN npm install --production
EXPOSE 7001
-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