Files
2020-04-08 13:40:49 -03:00

1181 lines
44 KiB
TypeScript

import {HyperionWorker} from "./hyperionWorker";
import {Api} from "eosjs/dist";
import {ApiResponse} from "@elastic/elasticsearch";
import {AsyncCargo, AsyncQueue, cargo, queue} from 'async';
import * as AbiEOS from "@eosrio/node-abieos";
import {Serialize} from "../addons/eosjs-native";
import {Type} from "../addons/eosjs-native/eosjs-serialize";
import {hLog} from "../helpers/common_functions";
const index_queues = require('../definitions/index-queues').index_queues;
const {debugLog} = require("../helpers/functions");
const {AbiDefinitions} = require("../definitions/abi_def");
const abi_remapping = {
"_Bool": "bool"
};
interface QueuePayload {
queue: string;
content: Buffer;
headers?: any;
}
function extractDeltaStruct(deltas) {
const deltaStruct = {};
for (const table_delta of deltas) {
if (table_delta[0] === "table_delta_v0") {
deltaStruct[table_delta[1].name] = table_delta[1].rows;
}
}
return deltaStruct;
}
export default class MainDSWorker extends HyperionWorker {
ch_ready = false;
private consumerQueue: AsyncCargo;
private preIndexingQueue: AsyncQueue<QueuePayload>;
private abi: any;
private types: Map<string, Type>;
private tables = new Map();
private allowStreaming = false;
private dsPoolMap = {};
private ds_pool_counters = {};
private block_emit_idx = 1;
private local_block_count = 0;
common: any;
tableHandlers = {};
api: Api;
// generic queue id
emit_idx = 1;
tbl_acc_emit_idx = 1;
tbl_vote_emit_idx = 1;
tbl_prop_emit_idx = 1;
delta_emit_idx = 1;
temp_delta_counter = 0;
private monitoringLoop: NodeJS.Timeout;
constructor() {
super();
this.consumerQueue = cargo((payload, cb) => {
this.processMessages(payload).then(() => {
cb();
}).catch((err) => {
hLog('NACK ALL', err);
if (this.ch_ready) {
this.ch.nackAll();
}
});
}, this.conf.prefetch.block);
this.preIndexingQueue = queue((data, cb) => {
if (this.ch_ready) {
this.ch.sendToQueue(data.queue, data.content, {headers: data.headers});
cb();
} else {
hLog('Channel is not ready!');
}
}, 1);
this.api = new Api({
rpc: this.rpc,
signatureProvider: null,
chainId: this.chainId,
textDecoder: this.txDec,
textEncoder: this.txEnc,
});
this.populateTableHandlers();
}
async run(): Promise<void> {
this.startReports();
return undefined;
}
onIpcMessage(msg: any): void {
switch (msg.event) {
case 'initialize_abi': {
this.abi = JSON.parse(msg.data);
AbiEOS.load_abi("0", msg.data);
const initialTypes = Serialize.createInitialTypes();
this.types = Serialize.getTypesFromAbi(initialTypes, this.abi);
this.abi.tables.map(table => this.tables.set(table.name, table.type));
this.initConsumer();
break;
}
case 'update_abi': {
if (msg.abi) {
if (msg.abi.abi_hex) {
AbiEOS.load_abi_hex(msg.abi.account, msg.abi.abi_hex);
hLog(`Worker ${process.env.worker_id} updated the abi for ${msg.abi.account}`);
}
}
break;
}
case 'connect_ws': {
this.allowStreaming = true;
break;
}
case 'new_range': {
break;
}
case 'update_pool_map': {
this.dsPoolMap = msg.data;
break;
}
default: {
hLog('-----------> IPC Message <--------------');
hLog(msg);
hLog('----------------------------------------');
}
}
}
assertQueues(): void {
if (this.ch) {
this.ch_ready = true;
if (this.preIndexingQueue.paused) {
this.preIndexingQueue.resume();
}
this.ch.on('close', () => {
this.ch_ready = false;
this.preIndexingQueue.pause();
});
}
if (process.env['live_mode'] === 'false') {
for (let i = 0; i < this.conf.scaling.ds_queues; i++) {
this.ch.assertQueue(this.chain + ":blocks:" + (i + 1), {
durable: true
});
}
}
let qIdx = 0;
index_queues.forEach((q) => {
let n = this.conf.scaling.indexing_queues;
if (q.type === 'abi') n = 1;
qIdx = 0;
for (let i = 0; i < n; i++) {
let m = 1;
if (q.type === 'action' || q.type === 'delta') {
m = this.conf.scaling.ad_idx_queues;
}
for (let j = 0; j < m; j++) {
this.ch.assertQueue(q.name + ":" + (qIdx + 1), {durable: true});
qIdx++;
}
}
});
this.initConsumer();
}
attachActionExtras(self, action) {
self.mLoader.processActionData(action);
}
sendDsCounterReport() {
// send ds counters
if (this.temp_delta_counter > 0) {
process.send({
event: 'ds_report',
deltas: this.temp_delta_counter
});
this.temp_delta_counter = 0;
}
}
startReports() {
if (!this.monitoringLoop) {
this.monitoringLoop = setInterval(() => {
this.sendDsCounterReport();
}, 1000);
}
}
async processMessages(messages) {
await this.mLoader.parser.parseMessage(this, messages);
}
private initConsumer() {
if (this.ch_ready) {
this.ch.prefetch(this.conf.prefetch.block);
this.ch.consume(process.env['worker_queue'], (data) => {
this.consumerQueue.push(data);
});
}
}
async processBlock(res, block, traces, deltas) {
if (!res['this_block']) {
// missing current block data
hLog(res);
return null;
} else {
let producer = '';
let ts = '';
const block_num = res['this_block']['block_num'];
let block_ts = res['this_time'];
let light_block;
if (this.conf.indexer.fetch_block) {
if (!block) {
return null;
}
producer = block['producer'];
ts = block['timestamp'];
block_ts = ts;
// Collect total CPU and NET usage
let total_cpu = 0;
let total_net = 0;
block.transactions.forEach((trx) => {
total_cpu += trx['cpu_usage_us'];
total_net += trx['net_usage_words'];
});
// const cpu_pct = ((total_cpu / 200000) * 100).toFixed(2);
// const net_pct = ((total_net / 1048576) * 100).toFixed(2);
// hLog(`Block: ${res['this_block']['block_num']} | CPU: ${total_cpu} μs (${cpu_pct} %) | NET: ${total_net} bytes (${net_pct} %)`);
light_block = {
'@timestamp': block['timestamp'],
block_num: res['this_block']['block_num'],
producer: block['producer'],
new_producers: block['new_producers'],
schedule_version: block['schedule_version'],
cpu_usage: total_cpu,
net_usage: total_net
};
if (light_block.new_producers) {
process.send({
event: 'new_schedule',
block_num: light_block.block_num,
new_producers: light_block.new_producers,
live: process.env.live_mode
});
}
}
// Process Delta Traces
if (deltas && this.conf.indexer.process_deltas) {
const t1 = Date.now();
await this.processDeltas(deltas, block_num, block_ts);
const elapsed_time = Date.now() - t1;
if (elapsed_time > 1000) {
hLog(`[WARNING] Delta processing took ${elapsed_time} ms on block ${block_num}`);
}
}
// Process Action Traces
let _traces = traces;
if (traces["valueForKeyPath"]) {
_traces = traces['valueForKeyPath'](".");
}
if (_traces.length > 0 && this.conf.indexer.fetch_traces) {
const t2 = Date.now();
for (const trace of _traces) {
const transaction_trace = trace[1];
if (transaction_trace.action_traces.length > 0) {
// route trx trace to pool based on first action
if (this.conf.indexer.max_inline && transaction_trace.action_traces.length > this.conf.indexer.max_inline) {
transaction_trace.action_traces = transaction_trace.action_traces.slice(0, this.conf.indexer.max_inline);
}
this.routeToPool(transaction_trace, {block_num, producer, ts});
} else {
// hLog(transaction_trace, transaction_trace.partial[1].transaction_extensions);
}
}
const traces_elapsed_time = Date.now() - t2;
if (traces_elapsed_time > 10) {
debugLog(`[WARNING] Traces processing took ${traces_elapsed_time}ms on block ${block_num}`);
}
}
// Send light block to indexer
if (this.conf.indexer.fetch_block) {
this.pushToBlocksQueue(light_block);
}
return {block_num: res['this_block']['block_num'], size: traces.length};
}
}
pushToBlocksQueue(light_block) {
if (!this.conf.indexer.disable_indexing) {
const data = Buffer.from(JSON.stringify(light_block));
const q = this.chain + ":index_blocks:" + (this.block_emit_idx);
this.preIndexingQueue.push({
queue: q,
content: data
});
this.block_emit_idx++;
if (this.block_emit_idx > this.conf.scaling.indexing_queues) {
this.block_emit_idx = 1;
}
}
this.local_block_count++;
}
routeToPool(trace, headers) {
let first_action;
if (trace['action_traces'][0].length === 2) {
first_action = trace['action_traces'][0][1];
} else {
console.log('missing action_trace_v0');
console.log(trace['action_traces']);
console.log(trace);
return false;
}
if (this.checkBlacklist(first_action.act)) {
return false;
}
if (this.filters.action_whitelist.size > 0) {
if (!this.checkWhitelist(first_action.act)) {
return false;
}
}
let selected_q = 0;
const _code = first_action.act.account;
if (this.dsPoolMap[_code]) {
const workers = this.dsPoolMap[_code][2];
for (const w of workers) {
if (typeof this.ds_pool_counters[_code] === 'undefined') {
selected_q = w;
this.ds_pool_counters[_code] = w;
break;
} else {
if (this.ds_pool_counters[_code] === workers[workers.length - 1]) {
this.ds_pool_counters[_code] = workers[0];
selected_q = w;
this.ds_pool_counters[_code] = w;
break;
} else {
if (this.ds_pool_counters[_code] === w) {
continue;
}
if (w > this.ds_pool_counters[_code]) {
selected_q = w;
this.ds_pool_counters[_code] = w;
break;
}
}
}
}
}
const pool_queue = `${this.chain}:ds_pool:${selected_q}`;
// hLog('sent to ->', pool_queue);
if (this.ch_ready) {
this.ch.sendToQueue(pool_queue, Buffer.from(JSON.stringify(trace)), {headers});
return true;
} else {
return false;
}
}
createSerialBuffer(inputArray) {
return new Serialize.SerialBuffer({textEncoder: this.txEnc, textDecoder: this.txDec, array: inputArray});
}
async fetchAbiHexAtBlockElastic(contract_name, last_block, get_json) {
try {
const _includes = ["actions", "tables"];
if (get_json) {
_includes.push("abi");
} else {
_includes.push("abi_hex");
}
const query = {
bool: {
must: [
{term: {account: contract_name}},
{range: {block: {lte: last_block}}}
]
}
};
const queryResult: ApiResponse = await this.client.search({
index: `${this.chain}-abi-*`,
body: {
size: 1, query,
sort: [{block: {order: "desc"}}],
_source: {includes: _includes}
}
});
const results = queryResult.body.hits.hits;
return results.length > 0 ? results[0]._source : null;
} catch (e) {
hLog(e);
return null;
}
}
async verifyLocalType(contract, type, block_num, field) {
let _status;
let resultType;
try {
resultType = AbiEOS['get_type_for_' + field](contract, type);
_status = true;
} catch {
_status = false;
}
if (!_status) {
const savedAbi = await this.fetchAbiHexAtBlockElastic(contract, block_num, false);
if (savedAbi) {
if (savedAbi[field + 's'].includes(type)) {
if (savedAbi.abi_hex) {
_status = AbiEOS.load_abi_hex(contract, savedAbi.abi_hex);
}
if (_status) {
try {
resultType = AbiEOS['get_type_for_' + field](contract, type);
_status = true;
return [_status, resultType];
} catch (e) {
hLog(`(abieos/cached) >> ${e.message}`);
_status = false;
}
}
}
}
const currentAbi = await this.rpc.getRawAbi(contract);
if (currentAbi.abi.byteLength > 0) {
const abi_hex = Buffer.from(currentAbi.abi).toString('hex');
_status = AbiEOS.load_abi_hex(contract, abi_hex);
} else {
_status = false;
}
if (_status === true) {
try {
resultType = AbiEOS['get_type_for_' + field](contract, type);
_status = true;
} catch (e) {
// hLog(`(abieos/current) >> ${e.message}`);
_status = false;
}
}
}
return [_status, resultType];
}
async processContractRowNative(row, block) {
const [_status, tableType] = await this.verifyLocalType(row['code'], row['table'], block, "table");
if (_status) {
let result;
try {
if (typeof row.value === 'string') {
result = AbiEOS.hex_to_json(row['code'], tableType, row.value);
} else {
result = AbiEOS.bin_to_json(row['code'], tableType, row.value);
}
row['data'] = result;
delete row.value;
return row;
} catch {
}
}
return await this.processContractRow(row, block);
;
}
async getAbiFromHeadBlock(code) {
let _abi;
try {
_abi = (await this.rpc.get_abi(code)).abi;
} catch (e) {
hLog(e);
}
return {abi: _abi, valid_until: null, valid_from: null};
}
async getContractAtBlock(accountName: string, block_num: number, check_action?: string) {
let savedAbi, abi;
savedAbi = await this.fetchAbiHexAtBlockElastic(accountName, block_num, true);
if (savedAbi === null || !savedAbi.actions.includes(check_action)) {
savedAbi = await this.getAbiFromHeadBlock(accountName);
if (!savedAbi) return null;
abi = savedAbi.abi;
} else {
try {
abi = JSON.parse(savedAbi.abi);
} catch (e) {
hLog(e);
return null;
}
}
if (!abi) return null;
const initialTypes = Serialize.createInitialTypes();
let types;
try {
types = Serialize.getTypesFromAbi(initialTypes, abi);
} catch (e) {
let remapped = false;
for (const struct of abi.structs) {
for (const field of struct.fields) {
if (abi_remapping[field.type]) {
field.type = abi_remapping[field.type];
remapped = true;
}
}
}
if (remapped) {
try {
types = Serialize.getTypesFromAbi(initialTypes, abi);
} catch (e) {
hLog('failed after remapping abi');
hLog(accountName, block_num, check_action);
hLog(e);
}
} else {
hLog(accountName, block_num);
hLog(e);
}
}
const actions = new Map();
for (const {name, type} of abi.actions) {
actions.set(name, Serialize.getType(types, type));
}
const result = {types, actions, tables: abi.tables};
if (check_action) {
if (actions.has(check_action)) {
try {
AbiEOS['load_abi'](accountName, JSON.stringify(abi));
} catch (e) {
hLog(e);
}
}
}
return [result, abi];
}
async getTableType(code, table, block) {
let abi, contract, abi_tables;
[contract, abi] = await this.getContractAtBlock(code, block);
if (!contract.tables) {
// abi = (await this.getAbiAtBlock(code, block)).abi;
// abi_tables = abi.tables;
return;
} else {
abi_tables = contract.tables
}
let this_table, type;
for (let t of abi_tables) {
if (t.name === table) {
this_table = t;
break;
}
}
if (this_table) {
type = this_table.type;
} else {
// console.error(`Could not find table "${table}" in the abi for ${code} at block ${block}`);
// retry with the current abi
const currentABI = await this.getAbiFromHeadBlock(code);
if (!currentABI) {
return;
}
abi_tables = currentABI.abi.tables;
for (let t of abi_tables) {
if (t.name === table) {
this_table = t;
break;
}
}
if (this_table) {
type = this_table.type;
const initialTypes = Serialize.createInitialTypes();
contract.types = Serialize.getTypesFromAbi(initialTypes, currentABI.abi);
} else {
return;
}
}
let cType = contract.types.get(type);
if (!cType) {
if (this.types.has(type)) {
cType = this.types.get(type);
} else {
if (type === 'self_delegated_bandwidth') {
cType = contract.types.get('delegated_bandwidth')
}
}
if (!cType) {
hLog(code, block);
hLog(`code:${code} | table:${table} | block:${block} | type:${type}`);
hLog(Object.keys(contract));
hLog(Object.keys(abi));
}
}
return cType;
}
async processContractRow(row, block) {
const row_sb = this.createSerialBuffer(Serialize.hexToUint8Array(row['value']));
const tableType: Type = await this.getTableType(row['code'], row['table'], block);
let error;
if (tableType) {
try {
row['data'] = tableType.deserialize(row_sb);
delete row.value;
return row;
} catch (e) {
error = e.message;
}
}
process.send({
event: 'ds_error',
data: {
type: 'delta_ds_error',
block: block,
code: row['code'],
table: row['table'],
message: error
}
});
return row;
}
async processTableDelta(data) {
if (data['table']) {
data['primary_key'] = String(data['primary_key']);
let allowIndex;
let handled = false;
const key = `${data.code}:${data.table}`;
if (this.tableHandlers[key]) {
await this.tableHandlers[key](data);
handled = true;
}
if (this.tableHandlers[`${data.code}:*`]) {
await this.tableHandlers[`${data.code}:*`](data);
handled = true;
}
if (this.tableHandlers[`*:${data.table}`]) {
await this.tableHandlers[`*:${data.table}`](data);
handled = true;
}
if (!handled && this.conf.features.index_all_deltas) {
allowIndex = true;
} else {
allowIndex = handled;
}
return allowIndex;
}
}
async processDeltaContractRow(row, block_num, block_ts) {
try {
const payload = this.deserializeNative('contract_row', row.data)[1];
payload['@timestamp'] = block_ts;
payload['present'] = row.present;
payload['block_num'] = block_num;
if (this.conf.features.index_all_deltas || (payload.code === this.conf.settings.eosio_alias || payload.table === 'accounts')) {
const jsonRow = await this.processContractRowNative(payload, block_num);
if (jsonRow) {
const indexableData = await this.processTableDelta(jsonRow);
if (indexableData) {
if (!this.conf.indexer.disable_indexing && this.conf.features.index_deltas) {
const payload = Buffer.from(JSON.stringify(jsonRow));
this.pushToDeltaQueue(payload);
this.temp_delta_counter++;
this.pushToDeltaStreamingQueue(payload, jsonRow);
}
}
}
}
} catch (e) {
hLog(block_num, e);
}
}
pushToDeltaStreamingQueue(payload, jsonRow) {
if (this.allowStreaming && this.conf.features.streaming.deltas) {
this.ch.publish('', this.chain + ':stream', payload, {
headers: {
event: 'delta',
code: jsonRow.code,
table: jsonRow.table,
scope: jsonRow.scope,
payer: jsonRow.payer
}
});
}
}
pushToDeltaQueue(bufferdata) {
const q = this.chain + ":index_deltas:" + (this.delta_emit_idx);
this.preIndexingQueue.push({queue: q, content: bufferdata});
this.delta_emit_idx++;
if (this.delta_emit_idx > (this.conf.scaling.indexing_queues * this.conf.scaling.ad_idx_queues)) {
this.delta_emit_idx = 1;
}
}
pushToIndexQueue(data: any, type: string) {
const q = this.chain + ":index_generic:" + (this.emit_idx);
this.preIndexingQueue.push({
queue: q,
content: Buffer.from(JSON.stringify(data)),
headers: {type}
});
this.emit_idx++;
if (this.emit_idx > this.conf.scaling.indexing_queues) {
this.emit_idx = 1;
}
}
async processDeltas(deltas, block_num, block_ts) {
const deltaStruct = extractDeltaStruct(deltas);
// if (Object.keys(deltaStruct).length > 4) {
// hLog(Object.keys(deltaStruct));
// }
// Check account deltas for ABI changes
if (deltaStruct['account']) {
const rows = deltaStruct['account'];
for (const account_raw of rows) {
const data = this.deserializeNative('account', account_raw.data);
const account = data[1];
if (account['abi'] !== '') {
try {
const abiHex = account['abi'];
const abiBin = new Uint8Array(Buffer.from(abiHex, 'hex'));
const initialTypes = Serialize.createInitialTypes();
const abiDefTypes: Type = Serialize.getTypesFromAbi(initialTypes, AbiDefinitions).get('abi_def');
const abiObj = abiDefTypes.deserialize(this.createSerialBuffer(abiBin));
const jsonABIString = JSON.stringify(abiObj);
const abi_actions = abiObj.actions.map(a => a.name);
const abi_tables = abiObj.tables.map(t => t.name);
hLog(`📝 New code for ${account['name']} at block ${block_num} with ${abi_actions.length} actions`);
const new_abi_object = {
'@timestamp': block_ts,
account: account['name'],
block: block_num,
abi: jsonABIString,
abi_hex: abiHex,
actions: abi_actions,
tables: abi_tables
};
debugLog(`[Worker ${process.env.worker_id}] read ${account['name']} ABI at block ${block_num}`);
const q = this.chain + ":index_abis:1";
this.preIndexingQueue.push({
queue: q,
content: Buffer.from(JSON.stringify(new_abi_object))
});
// update locally cached abi
if (process.env['live_mode'] === 'true') {
hLog('Abi changed during live mode, updating local version...');
const abi_update_status = AbiEOS['load_abi_hex'](account['name'], abiHex);
if (!abi_update_status) {
hLog(`Reload status: ${abi_update_status}`);
}
}
process.send({
event: 'save_abi',
data: new_abi_object,
live_mode: process.env['live_mode'],
worker_id: process.env.worker_id
});
} catch (e) {
hLog(e);
hLog(account['abi'], block_num, account['name']);
}
} else {
if (account.name === 'eosio') {
hLog(`---------- ${block_num} ----------------`);
hLog(account);
}
}
}
}
if (!this.conf.indexer.abi_scan_mode && this.conf.indexer.process_deltas) {
// // Generated transactions
// if (process.env.PROCESS_GEN_TX === 'true') {
// if (deltaStruct['generated_transaction']) {
// const rows = deltaStruct['generated_transaction'];
// for (const gen_trx of rows) {
// const serialBuffer = createSerialBuffer(gen_trx.data);
// const data = types.get('generated_transaction').deserialize(serialBuffer);
// await processDeferred(data[1], block_num);
// }
// }
// }
// Contract Rows
if (deltaStruct['contract_row']) {
for (const row of deltaStruct['contract_row']) {
await this.processDeltaContractRow(row, block_num, block_ts);
}
}
if (deltaStruct['permission_link']) {
if (deltaStruct['permission_link'].length > 0) {
for (const link of deltaStruct['permission_link']) {
const data = this.deserializeNative('permission_link', link.data);
const payload = {
"@timestamp": block_ts,
block_num: block_num,
present: link.present,
account: data[1].account,
code: data[1].code,
action: data[1]['message_type'],
permission: data[1]['required_permission']
};
this.pushToIndexQueue(payload, 'permission_link');
}
}
}
// if (deltaStruct['permission']) {
// if (deltaStruct['permission'].length > 0) {
// for (const permission of deltaStruct['permission']) {
// const serialBuffer = createSerialBuffer(permission.data);
// const data = types.get('permission').deserialize(serialBuffer);
// hLog(prettyjson.render(data));
// }
// }
// }
// if (deltaStruct['contract_index64']) {
// if (deltaStruct['contract_index64'].length > 0) {
// for (const contract_index64 of deltaStruct['contract_index64']) {
// const serialBuffer = createSerialBuffer(contract_index64.data);
// const data = types.get('contract_index64').deserialize(serialBuffer);
// hLog(prettyjson.render(data));
// }
// }
// }
//
// if (deltaStruct['contract_index128']) {
// if (deltaStruct['contract_index128'].length > 0) {
// for (const contract_index128 of deltaStruct['contract_index128']) {
// const serialBuffer = createSerialBuffer(contract_index128.data);
// const data = types.get('contract_index128').deserialize(serialBuffer);
// hLog(prettyjson.render(data));
// }
// }
// }
// if (deltaStruct['account_metadata']) {
// if (deltaStruct['account_metadata'].length > 0) {
// for (const account_metadata of deltaStruct['account_metadata']) {
// const serialBuffer = createSerialBuffer(account_metadata.data);
// const data = types.get('account_metadata').deserialize(serialBuffer);
// hLog(prettyjson.render(data));
// }
// }
// }
// if (deltaStruct['resource_limits']) {
// if (deltaStruct['resource_limits'].length > 0) {
// for (const resource_limits of deltaStruct['resource_limits']) {
// const serialBuffer = createSerialBuffer(resource_limits.data);
// const data = types.get('resource_limits').deserialize(serialBuffer);
// hLog(prettyjson.render(data));
// }
// }
// }
// if (deltaStruct['resource_usage']) {
// if (deltaStruct['resource_usage'].length > 0) {
// for (const resource_usage of deltaStruct['resource_usage']) {
// const serialBuffer = createSerialBuffer(resource_usage.data);
// const data = types.get('resource_usage').deserialize(serialBuffer);
// hLog(prettyjson.render(data));
// }
// }
// }
// if (deltaStruct['resource_limits_state']) {
// if (deltaStruct['resource_limits_state'].length > 0) {
// for (const resource_limits_state of deltaStruct['resource_limits_state']) {
// const serialBuffer = createSerialBuffer(resource_limits_state.data);
// const data = types.get('resource_limits_state').deserialize(serialBuffer);
// hLog(prettyjson.render(data));
// }
// }
// }
// if (deltaStruct['contract_table']) {
// if (deltaStruct['contract_table'].length > 0) {
// for (const contract_table of deltaStruct['contract_table']) {
// const serialBuffer = createSerialBuffer(contract_table.data);
// const data = types.get('contract_table').deserialize(serialBuffer);
// hLog(prettyjson.render(data));
// }
// }
// }
}
}
deserializeNative(datatype: string, array: any): any {
try {
const parser = typeof array === 'string' ? 'hex_to_json' : 'bin_to_json';
return AbiEOS[parser]("0", datatype, array);
} catch (e) {
hLog(e);
}
return null;
}
async deserializeActionAtBlockNative(_action, block_num): Promise<any> {
const [_status, actionType] = await this.verifyLocalType(_action.account, _action.name, block_num, "action");
if (_status) {
try {
return AbiEOS.bin_to_json(_action.account, actionType, Buffer.from(_action.data, 'hex'));
} catch (e) {
hLog(e);
}
}
return null;
}
async storeProposal(data) {
const proposalDoc = {
proposer: data['scope'],
proposal_name: data['@approvals']['proposal_name'],
requested_approvals: data['@approvals']['requested_approvals'],
provided_approvals: data['@approvals']['provided_approvals'],
executed: data.present === false,
primary_key: data['primary_key'],
block_num: data['block_num']
};
// if (proposalDoc.executed) {
// hLog(proposalDoc);
// }
if (!this.conf.indexer.disable_indexing) {
const q = this.chain + ":index_table_proposals:" + (this.tbl_prop_emit_idx);
this.preIndexingQueue.push({
queue: q,
content: Buffer.from(JSON.stringify(proposalDoc))
});
this.tbl_prop_emit_idx++;
if (this.tbl_prop_emit_idx > (this.conf.scaling.indexing_queues)) {
this.tbl_prop_emit_idx = 1;
}
}
}
async storeVoter(data) {
const voterDoc: any = {
"voter": data['payer'],
"last_vote_weight": data['@voters']['last_vote_weight'],
"is_proxy": data['@voters']['is_proxy'],
"proxied_vote_weight": data['@voters']['proxied_vote_weight'],
"staked": data['@voters']['staked'],
"primary_key": data['primary_key'],
"block_num": data['block_num']
};
if (data['@voters']['proxy']) {
voterDoc.proxy = data['@voters']['proxy'];
}
if (data['@voters']['producers']) {
voterDoc.producers = data['@voters']['producers'];
}
if (!this.conf.indexer.disable_indexing) {
const q = this.chain + ":index_table_voters:" + (this.tbl_vote_emit_idx);
this.preIndexingQueue.push({
queue: q,
content: Buffer.from(JSON.stringify(voterDoc))
});
this.tbl_vote_emit_idx++;
if (this.tbl_vote_emit_idx > (this.conf.scaling.indexing_queues)) {
this.tbl_vote_emit_idx = 1;
}
}
}
async storeAccount(data) {
const accountDoc = {
"code": data['code'],
"scope": data['scope'],
"primary_key": data['primary_key'],
"block_num": data['block_num']
};
if (data['@accounts']) {
accountDoc['amount'] = data['@accounts']['amount'];
accountDoc['symbol'] = data['@accounts']['symbol'];
}
if (!this.conf.indexer.disable_indexing) {
const q = this.chain + ":index_table_accounts:" + (this.tbl_acc_emit_idx);
this.preIndexingQueue.push({
queue: q,
content: Buffer.from(JSON.stringify(accountDoc))
});
this.tbl_acc_emit_idx++;
if (this.tbl_acc_emit_idx > (this.conf.scaling.indexing_queues)) {
this.tbl_acc_emit_idx = 1;
}
}
}
private populateTableHandlers() {
const EOSIO_ALIAS = this.conf.settings.eosio_alias;
this.tableHandlers[EOSIO_ALIAS + ':voters'] = async (delta) => {
delta['@voters'] = {};
delta['@voters']['is_proxy'] = delta.data['is_proxy'];
delete delta.data['is_proxy'];
delete delta.data['owner'];
if (delta.data['proxy'] !== "") {
delta['@voters']['proxy'] = delta.data['proxy'];
}
delete delta.data['proxy'];
if (delta.data['producers'].length > 0) {
delta['@voters']['producers'] = delta.data['producers'];
}
delete delta.data['producers'];
delta['@voters']['last_vote_weight'] = parseFloat(delta.data['last_vote_weight']);
delete delta.data['last_vote_weight'];
delta['@voters']['proxied_vote_weight'] = parseFloat(delta.data['proxied_vote_weight']);
delete delta.data['proxied_vote_weight'];
delta['@voters']['staked'] = parseInt(delta.data['staked'], 10) / 10000;
delete delta.data['staked'];
if (this.conf.features.tables.voters) {
await this.storeVoter(delta);
}
};
this.tableHandlers[EOSIO_ALIAS + ':global'] = async (delta) => {
const data = delta['data'];
delta['@global'] = {
last_name_close: data['last_name_close'],
last_pervote_bucket_fill: data['last_pervote_bucket_fill'],
last_producer_schedule_update: data['last_producer_schedule_update'],
perblock_bucket: parseFloat(data['perblock_bucket']) / 10000,
pervote_bucket: parseFloat(data['perblock_bucket']) / 10000,
total_activated_stake: parseFloat(data['total_activated_stake']) / 10000,
total_producer_vote_weight: parseFloat(data['total_producer_vote_weight']),
total_ram_kb_reserved: parseFloat(data['total_ram_bytes_reserved']) / 1024,
total_ram_stake: parseFloat(data['total_ram_stake']) / 10000,
total_unpaid_blocks: data['total_unpaid_blocks']
};
delete delta['data'];
};
this.tableHandlers[EOSIO_ALIAS + ':producers'] = async (delta) => {
const data = delta['data'];
delta['@producers'] = {
total_votes: parseFloat(data['total_votes']),
is_active: data['is_active'],
unpaid_blocks: data['unpaid_blocks']
};
delete delta['data'];
};
this.tableHandlers[EOSIO_ALIAS + ':userres'] = async (delta) => {
const data = delta['data'];
const net = parseFloat(data['net_weight'].split(" ")[0]);
const cpu = parseFloat(data['cpu_weight'].split(" ")[0]);
delta['@userres'] = {
owner: data['owner'],
net_weight: net,
cpu_weight: cpu,
total_weight: parseFloat((net + cpu).toFixed(4)),
ram_bytes: parseInt(data['ram_bytes'])
};
delete delta['data'];
};
this.tableHandlers[EOSIO_ALIAS + ':delband'] = async (delta) => {
const data = delta['data'];
const net = parseFloat(data['net_weight'].split(" ")[0]);
const cpu = parseFloat(data['cpu_weight'].split(" ")[0]);
delta['@delband'] = {
from: data['from'],
to: data['to'],
net_weight: net,
cpu_weight: cpu,
total_weight: parseFloat((net + cpu).toFixed(4))
};
delete delta['data'];
// hLog(delta);
};
this.tableHandlers[EOSIO_ALIAS + '.msig:proposal'] = async (delta) => {
// decode packed_transaction
delta['@proposal'] = {
proposal_name: delta['data']['proposal_name']
};
const trx = this.api.deserializeTransaction(Serialize.hexToUint8Array(delta.data['packed_transaction']));
for (const action of trx.actions) {
action['hex_data'] = action['data'];
action['data'] = await this.deserializeActionAtBlockNative(action, delta.block_num);
}
delta['@proposal']['transaction'] = trx;
delete delta['data'];
};
this.tableHandlers[EOSIO_ALIAS + '.msig:approvals'] = async (delta) => {
delta['@approvals'] = {
proposal_name: delta['data']['proposal_name'],
requested_approvals: delta['data']['requested_approvals'],
provided_approvals: delta['data']['provided_approvals']
};
delete delta['data'];
if (this.conf.features.tables.proposals) {
await this.storeProposal(delta);
}
};
this.tableHandlers[EOSIO_ALIAS + '.msig:approvals2'] = async (delta) => {
delta['@approvals'] = {
proposal_name: delta['data']['proposal_name'],
requested_approvals: delta['data']['requested_approvals'].map((item) => {
return {actor: item.level.actor, permission: item.level.permission, time: item.time};
}),
provided_approvals: delta['data']['provided_approvals'].map((item) => {
return {actor: item.level.actor, permission: item.level.permission, time: item.time};
})
};
if (this.conf.features.tables.proposals) {
await this.storeProposal(delta);
}
};
this.tableHandlers['*:accounts'] = async (delta) => {
if (typeof delta['data']['balance'] === 'string') {
try {
const [amount, symbol] = delta['data']['balance'].split(" ");
delta['@accounts'] = {
amount: parseFloat(amount),
symbol: symbol
};
delete delta.data['balance'];
} catch (e) {
hLog(delta);
hLog(e);
}
}
if (this.conf.features.tables.accounts) {
await this.storeAccount(delta);
}
};
}
}