Compare commits

...

490 Commits

Author SHA1 Message Date
Igor Lins e Silva 76807c6c86 cleanup 2023-09-25 01:29:53 -03:00
Igor Lins e Silva 69bcbaad18 flow control 2023-09-25 01:25:40 -03:00
Igor Lins e Silva e5d0126d08 Revert "include amqp-connection-manager"
This reverts commit 0579da66
2023-09-25 01:05:33 -03:00
Igor Lins e Silva e804aff53c log cleanup 2023-09-25 01:03:35 -03:00
Igor Lins e Silva 5d3b248bcd Revert "include amqp-connection-manager"
This reverts commit 4dcb607fcc.
2023-09-25 01:03:04 -03:00
Igor Lins e Silva d1eca214ae log cleanup 2023-09-25 00:54:41 -03:00
Igor Lins e Silva 4dcb607fcc include amqp-connection-manager 2023-09-25 00:45:23 -03:00
Igor Lins e Silva 0579da6613 include amqp-connection-manager 2023-09-25 00:29:07 -03:00
Igor Lins e Silva b8faf2ef70 package updates 2023-09-24 19:33:55 -03:00
Igor Lins e Silva bc98cecb52 send signatures on payload 2023-09-24 19:30:40 -03:00
Igor Lins e Silva 29ed9d372d add logs 2023-09-24 19:14:52 -03:00
Igor Lins e Silva d259008cb4 add logs 2023-09-24 19:13:08 -03:00
Igor Lins e Silva ce6f307bbe add logs 2023-09-24 18:32:15 -03:00
Igor Lins e Silva 44062fbe0e add logs 2023-09-24 18:28:07 -03:00
Igor Lins e Silva 94547ce8fd add logs 2023-09-24 18:02:49 -03:00
Igor Lins e Silva c59005bba0 make ingest clients public 2023-09-23 14:13:03 -03:00
Igor Lins e Silva bee1948f19 make ingest clients public 2023-09-23 14:12:45 -03:00
Bruno Leal 616133dab8 fix: repair chunk file 2023-09-13 18:34:04 -03:00
Igor Lins e Silva 28ade29d00 implement v2/stats/get_api_usage 2023-09-01 00:45:25 -03:00
Igor Lins e Silva 6fe3acce7e update v1/chain/abi_bin_to_json interface 2023-06-23 12:18:43 -03:00
Igor Lins e Silva 572b2b11e5 update socket.io to 4.7.0 2023-06-23 12:16:00 -03:00
Igor Lins e Silva 2fbee90b73 update v1/chain/abi_json_to_bin interface 2023-06-23 12:13:30 -03:00
Igor Lins e Silva e79cad82a4 update socket.io 2023-06-11 03:08:29 -03:00
Igor Lins e Silva e087c2971e Merge branch 'main' of https://github.com/eosrio/hyperion-history-api 2023-06-11 03:04:59 -03:00
Igor Lins e Silva 74072bd6cb prevent a conflict between disable_reading and the repair mode 2023-06-11 03:04:15 -03:00
Igor Lins e Silva ee3bf26263 package update 2023-05-26 04:54:38 +00:00
Igor Lins e Silva 367f37e989 hyperion repair cli v0.2.2 2023-05-26 04:53:07 +00:00
Igor Lins e Silva 65698c344d on-demand delta filtering for streams 2023-05-20 08:56:46 +00:00
Igor Lins e Silva b5f17b0133 cleanup 2023-05-20 07:26:07 +00:00
Igor Lins e Silva 5b65bd7d7b cleanup 2023-05-20 07:22:21 +00:00
Igor Lins e Silva b289036b0c - multiple streaming improvements
- stream-client v2.0.0 compatibility
- improved transitions on high volume streams
- added api.stream_scroll_limit (default -1 -> unlimited)
specify the maximum number of documents that can be requested during a history request in the streaming api
- added api.stream_scroll_batch (default 500)
specify the number of documents per streamed history request
2023-05-20 07:20:46 +00:00
Igor Lins e Silva a2a040ab55 allow date formats with space between date and time, replace %20 with T 2023-05-20 04:53:58 +00:00
Igor Lins e Silva 480e29f278 don't sort producers on get_schedule response 2023-05-15 16:19:27 -03:00
Igor Lins e Silva ce9dc8524e wait for ES to be available on api start 2023-05-11 06:16:23 -03:00
Igor Lins e Silva bc2d25394c wait for nodeos to be available on api start, prevent fastify plugin timeout 2023-05-11 03:01:29 -03:00
Igor Lins e Silva 88493248e1 wait for services to be available on indexer start 2023-05-11 02:10:35 -03:00
Igor Lins e Silva 7ceb009d31 wait for nodeos to be available on indexer start 2023-05-11 01:46:53 -03:00
Igor Lins e Silva 81606e6252 add indexing of proposed schedules on dedicated chain-schedule-* index, improve api endpoint (v2/history/get_schedule) 2023-05-08 04:42:55 -03:00
Igor Lins e Silva a9fb4b5b80 cleanup 2023-05-08 02:25:58 -03:00
Igor Lins e Silva f426a3dd4e use default control port 7002 if not defined on connections.json 2023-05-08 01:50:02 -03:00
Igor Lins e Silva 99c2772781 ignore repair tool build files 2023-05-08 01:42:44 -03:00
Igor Lins e Silva 8876213ecc update repair tool 2023-05-08 01:41:35 -03:00
Igor Lins e Silva 0798b08e2e fix lib event forwarding on shared api hosts 2023-05-07 19:42:58 -03:00
Igor Lins e Silva eb6fb9827a v18 is still supported 2023-05-05 08:31:07 -03:00
Igor Lins e Silva 44e487272a v3.3.9-5
- Node.js 20 compatibility
- added last_indexed_block/time on API responses
- fix index progress monitoring
2023-05-05 08:15:20 -03:00
Igor Lins e Silva bbbbf91f79 Update hyp-repair script and other deserializer cleanups 2023-05-05 03:54:04 +00:00
Igor Lins e Silva cd277b4f12 merge repair tool 2023-05-04 16:49:26 -03:00
Igor Lins e Silva 16e870135b merge repair tool 2023-05-04 16:48:44 -03:00
Igor Lins e Silva aa989dc5e6 merge repair tool 2023-05-04 16:45:11 -03:00
Igor Lins e Silva 4555112b9c merge repair tool 2023-05-04 16:24:32 -03:00
Igor Lins e Silva 89f0bf586b update packages 2023-05-04 05:48:10 -03:00
Igor Lins e Silva be13e81437 fix resource handler on deltas 2023-05-04 05:45:29 -03:00
Igor Lins e Silva 70bab7545b Merge pull request #140 from ankh2054/patch-1
Update fix-missing-blocks.py
2023-04-10 19:52:28 -03:00
Charles Holtzkampf 3c1d9fcc2f Update fix-missing-blocks.py
Updated to work with all versions of Hyperion
2023-03-27 11:18:11 +01:00
Igor Lins e Silva 498bc8ec75 Update example.config.json
remove `bypass_index_map`
2023-03-01 18:17:02 -03:00
Igor Lins e Silva 58f40531fe remove deprecated index mapping 2023-03-01 18:15:55 -03:00
Igor Lins e Silva 57e4492556 v3.3.9-4
- patch for https://github.com/advisories/GHSA-rc47-6667-2j5j
2023-03-01 18:09:02 -03:00
Igor Lins e Silva 832da15808 fix v1 get_actions to match receipts var 2023-03-01 18:02:45 -03:00
Igor Lins e Silva 4107daa621 ignore example chain config 2023-01-26 20:26:45 -03:00
Igor Lins e Silva b3adad54d8 Update package.json 2023-01-26 20:06:14 -03:00
Igor Lins e Silva 176932a78c avoid failed tsc builds 2023-01-26 20:05:05 -03:00
Igor Lins e Silva 3a5997ca98 set default health cache to 10s 2023-01-26 13:56:54 -03:00
Igor Lins e Silva ea8dd6d3f7 add cache to cat/health 2023-01-26 13:54:58 -03:00
Igor Lins e Silva 8c0be344a6 fix notified list on simple action output 2023-01-26 13:42:47 -03:00
Igor Lins e Silva fbd5904e25 fix act_digest output on v1 2023-01-26 12:57:15 -03:00
Igor Lins e Silva 05b7ab0f3a v.3.3.9-2 2023-01-25 22:11:20 -03:00
Igor Lins e Silva da34871f09 add settings.use_global_agent as optional
- global-agent was interfering with default self-signed certificates on secure elasticsearch setup
2023-01-25 22:09:00 -03:00
Igor Lins e Silva fa3c2cb878 Update hyp-config.ts 2023-01-25 20:49:14 -03:00
Igor Lins e Silva ea5f6d0be0 fix backup directory creation 2023-01-25 20:43:17 -03:00
Igor Lins e Silva 88598ea551 remove temp files 2023-01-25 20:43:05 -03:00
Igor Lins e Silva b06a422b54 Update .gitignore 2023-01-25 20:42:54 -03:00
Igor Lins e Silva 29ff9824c8 Update .gitignore 2023-01-25 20:42:00 -03:00
Igor Lins e Silva f2f0d0f41c fix paths 2023-01-25 20:38:35 -03:00
Igor Lins e Silva 7830b11eaf backport "hyp-config connections init" command 2023-01-25 20:26:46 -03:00
Igor Lins e Silva 55cb7ca74a re-include @timestamp 2023-01-20 03:10:48 -03:00
Igor Lins e Silva 9eea8d4a99 v3.3.9 2023-01-18 14:24:42 -03:00
Igor Lins e Silva 2827f85f3e v3.3.8 stable
- fix readers stopping before having finished
2023-01-18 14:20:56 -03:00
Igor Lins e Silva 2ee60bc276 v3.3.8 2022-12-09 17:25:17 -03:00
Igor Lins e Silva da295aa2d2 v3.3.8
- removed redundant notifications array, since receipts.receiver was already indexed
- updated get_action output to include all fields
- removed unused fields to save raw data storage
- added block_id as a searchable field to get_actions
- added parser 3.2 as default for the latest Antelope versions, removed deprecated v1.7 parser
- fastify security updates
- get_actions/get_deltas `before` and `after` now support block_num input as well as timestamps
2022-12-08 19:33:46 -03:00
Igor Lins e Silva 4035de3dac add pino-pretty package 2022-11-15 03:08:44 +00:00
Igor Lins e Silva 3b3483333d update bundled stream client 2022-11-14 23:03:55 +00:00
Igor Lins e Silva a66b3e63e1 add stream port to chain config 2022-11-14 18:05:06 +00:00
Igor Lins e Silva 80c57ca4a8 add stream port to chain config 2022-11-14 18:02:27 +00:00
Igor Lins e Silva c9ac57cbb6 remove redundant parse 2022-11-14 15:11:07 +00:00
Igor Lins e Silva 57ea9717f5 v3.3.7, upgrade deprecated fastify plugins 2022-11-09 13:01:22 -03:00
Igor Lins e Silva 40fa86f6d8 update packages for v3.3.7 2022-11-09 12:55:59 -03:00
Igor Lins e Silva e21554a239 make the indexer behavior clearer when using rewrite: true 2022-11-09 12:48:14 -03:00
Igor Lins e Silva 160b2ebd32 add logging 2022-11-09 12:00:12 -03:00
Igor Lins e Silva bb28ac7139 update packages 2022-11-09 11:54:27 -03:00
Igor Lins e Silva 4139cd02c1 fix proposal execution flag 2022-11-03 17:22:04 -03:00
Igor Lins e Silva c66e71df0e Update get_actions.ts 2022-10-26 17:19:00 -03:00
Igor Lins e Silva 7f15648b38 patch API queries for nested indexed data 2022-10-26 17:18:15 -03:00
Igor Lins e Silva 04c738fffe fix nested act data on failed abieos deserialization 2022-10-26 17:08:18 -03:00
Igor Lins e Silva c6b3afc1d1 Update index.ts 2022-10-26 09:54:26 -03:00
Igor Lins e Silva 91fd57bd83 Update get_deltas.ts 2022-10-26 09:41:40 -03:00
Igor Lins e Silva 2d3a46d0fe fix get_deltas param parsing 2022-10-26 09:40:05 -03:00
Igor Lins e Silva d99e1cdd8a Merge pull request #126 from ankh2054/main
Update fix-missing-blocks.py
2022-10-20 12:39:53 -03:00
Igor Lins e Silva 24ad40c889 use uWebsockets.js as Socket.IO engine 2022-10-17 01:20:31 +00:00
Charles Holtzkampf db9dd56b9a Update fix-missing-blocks.py 2022-10-04 14:10:59 +01:00
Igor Lins e Silva f7a18b55ab Update get_actions.ts 2022-09-22 13:56:58 -03:00
Igor Lins e Silva 91b6da103b Update get_actions.ts 2022-09-22 13:55:37 -03:00
Igor Lins e Silva f6aef13f88 Update get_actions.ts 2022-09-22 13:52:28 -03:00
Igor Lins e Silva 61c69d0ab6 Update get_actions.ts 2022-09-22 13:51:23 -03:00
Igor Lins e Silva 061d7a443d add optional hex_data 2022-09-22 13:46:02 -03:00
Igor Lins e Silva b7e2ead86d remove wrong encoding for hex_data 2022-09-22 13:21:39 -03:00
Igor Lins e Silva 3b74decbe6 add error output on get_tokens 2022-09-12 12:34:33 -03:00
Igor Lins e Silva b2b3a9d674 cleanup 2022-09-12 12:28:28 -03:00
Igor Lins e Silva 8b6fd9a1a8 Update health.ts 2022-09-12 12:22:57 -03:00
Igor Lins e Silva bc19f2caae Update health.ts 2022-09-12 12:19:09 -03:00
Igor Lins e Silva 82160c72ad fix calculation for total indexed 2022-09-12 12:15:29 -03:00
Igor Lins e Silva 3f4fd975a2 Update README.md 2022-09-11 23:48:17 -03:00
Igor Lins e Silva 86de28aadd Update README.md 2022-09-11 23:38:16 -03:00
Igor Lins e Silva 2ac15b0e5b Update README.md 2022-09-11 20:42:29 -03:00
Igor Lins e Silva 4fcee923bd Update README.md 2022-09-11 20:38:04 -03:00
Igor Lins e Silva 89c6cd280d Update README.md 2022-09-11 20:29:44 -03:00
Igor Lins e Silva f372d8318e Update README.md 2022-09-11 20:22:20 -03:00
Igor Lins e Silva 8af456d155 Update health.ts 2022-09-11 06:42:57 -03:00
Igor Lins e Silva 0696e81f6d Update health.ts 2022-09-11 05:42:48 -03:00
Igor Lins e Silva fbf0f634c9 add more information and interfaces 2022-09-11 05:40:44 -03:00
Igor Lins e Silva ac9fafd3be add redis cache for firstIndexedBlock to speed-up the health endpoint 2022-09-11 05:07:51 -03:00
Igor Lins e Silva a467b81404 Update get_creator.ts 2022-09-11 05:07:20 -03:00
Igor Lins e Silva 3c11a5109a add first_indexed_block to v2/health 2022-09-11 04:53:45 -03:00
Igor Lins e Silva 640eccb7bd Update get_creator.ts 2022-09-11 04:30:38 -03:00
Igor Lins e Silva de981f32b8 revert swagger to 6.1 2022-09-11 04:26:31 -03:00
Igor Lins e Silva 91821b6365 Update get_creator.ts 2022-09-11 04:24:34 -03:00
Igor Lins e Silva 5801801967 update deprecated swagger plugin 2022-09-11 04:24:29 -03:00
Igor Lins e Silva 206d92034b update packages + cleanup 2022-09-11 03:55:35 -03:00
Igor Lins e Silva 49bf80b57f Delete connections-demo.json 2022-09-11 03:42:58 -03:00
Igor Lins e Silva 279f5d9474 merge 3.3.5 to master 2022-09-11 03:42:18 -03:00
Igor Lins e Silva 7cf9f0613c rollback fastify-elasticsearch 2021-12-21 19:47:38 -03:00
Igor Lins e Silva a816efe82a 3.1.5 2021-12-21 19:13:04 -03:00
Igor Lins e Silva 56305d4138 update fastify-oas 2021-12-21 19:09:15 -03:00
Igor Lins e Silva 1aaeb6b78f update TS to fix elasticsearch.js build 2021-12-21 18:43:05 -03:00
Igor Lins e Silva a68aeb79c6 use alias to speedup search 2021-12-20 19:48:26 -03:00
Igor Lins e Silva 1ced41dfc6 Update master.ts 2021-12-20 19:43:46 -03:00
Igor Lins e Silva 0c3dad17cd update debug logs 2021-12-20 19:41:13 -03:00
Igor Lins e Silva c39b1df4a0 add extra logging 2021-12-20 19:18:36 -03:00
Igor Lins e Silva af59050e0b Update package.json 2021-12-20 18:55:31 -03:00
Igor Lins e Silva f7f5c2b738 Update ecosystem_settings.js 2021-08-20 00:17:05 -03:00
Igor Lins e Silva 994025c2c4 Merge pull request #61 from xebb82/patch-5
API logs need timestamp
2021-08-20 00:14:49 -03:00
Eric f859840318 API logs need timestamp
This fix will make sure the api logs gets timestamped.
2021-08-19 08:52:46 +02:00
Igor Lins e Silva c80eaaa9fc deprecate install env + package updates 2021-08-05 22:13:02 -03:00
Igor Lins e Silva c98f2dc431 remove unlisted worker logging 2021-07-31 05:12:33 -03:00
Igor Lins e Silva 797fb867b1 improve tx_cache 2021-07-31 04:59:05 -03:00
Igor Lins e Silva 38e939a640 Update .gitignore 2021-07-31 04:48:51 -03:00
Igor Lins e Silva 88a24c579f Update package.json 2021-07-21 10:16:55 -03:00
Igor Lins e Silva e17d6e8209 Merge pull request #59 from eosrio/3.1.4
v3.1.4
2021-07-21 10:12:42 -03:00
Igor Lins e Silva 787b5aba65 implementing preemptive caching on redis for get_transaction
TTL is configurable via `api.tx_cache_expiration_sec`
2021-07-21 10:10:10 -03:00
Igor Lins e Silva 6494e57b7f remove node-fetch 2021-07-21 08:03:13 -03:00
Igor Lins e Silva fc03425190 Update package.json 2021-07-21 08:01:11 -03:00
Igor Lins e Silva 630f0e286c update packages 2021-07-21 07:52:18 -03:00
Igor Lins e Silva 54d446adc3 bump to 3.1.3
added get_supported_apis
2021-07-20 11:28:22 -03:00
Igor Lins e Silva 5d40aadbac improve ignore list 2021-07-20 06:27:03 -03:00
Igor Lins e Silva b3ae01b2c7 fix typings 2021-07-20 06:23:56 -03:00
Igor Lins e Silva 5d73e452dc add enhanced /v1/node/get_supported_apis 2021-07-20 06:19:55 -03:00
Igor Lins e Silva e3c7342b4d remove full log on amqp http fail 2021-06-03 15:45:17 -03:00
Igor Lins e Silva 6531c7bf8a [indexer/amqp] allow frameMax connection param 2021-05-21 14:28:42 -03:00
Igor Lins e Silva 612f373725 [api] fix get_transaction to return only the highest block_num, in case of fork/duplicates 2021-04-28 18:25:49 -03:00
Igor Lins e Silva b386be795e Update get_transaction.ts 2021-04-28 18:20:25 -03:00
Igor Lins e Silva 417bb96294 [indexer] fix amqp credentials encoding 2021-03-26 02:58:30 -03:00
Igor Lins e Silva a5f14071d3 Update .gitignore 2021-03-24 14:06:36 -03:00
Igor Lins e Silva 1f18f54933 [api] define before/after 2021-03-20 12:37:03 -03:00
Igor Lins e Silva fdd75e8393 [api] improve get_proposals 2021-03-20 12:25:27 -03:00
Igor Lins e Silva 7223ecd8a0 cleanup 2021-03-20 12:25:05 -03:00
Igor Lins e Silva 6169d54474 [api] get_deltas optimization 2021-03-20 12:24:48 -03:00
Igor Lins e Silva 7d3b7451ea [api] fallback method on get_creator 2021-03-17 02:07:56 -03:00
Igor Lins e Silva b7ab2f055f [api] fix parsing bug 2021-03-17 02:07:37 -03:00
Igor Lins e Silva 8cdd29fda9 [api] ignoreCache flag 2021-03-17 02:07:15 -03:00
Igor Lins e Silva d67bbe9c21 [api] fix get_creator on eosio 2021-03-16 23:07:37 -03:00
Igor Lins e Silva ee708a22f1 Update functions.ts 2021-03-16 22:57:02 -03:00
Igor Lins e Silva 27141e6e89 [api] custom cache on get_resource_usage 2021-03-16 22:38:58 -03:00
Igor Lins e Silva 0386bc7ce8 Update get_resource_usage.ts
- set period to 48h
- allow `@transfer.to`  filter
2021-03-16 22:29:21 -03:00
Igor Lins e Silva 1f0e696308 Update ds-pool.ts 2021-03-09 09:57:20 -03:00
Igor Lins e Silva 1748035656 Update master.ts 2021-03-09 09:52:15 -03:00
Igor Lins e Silva 3b58aa28c7 Update ds-pool.ts 2021-03-09 09:43:26 -03:00
Igor Lins e Silva 8bffd2d1e7 [indexer] allow custom ilm policy name
use settings.custom_policy = "POLICY_NAME" on the chain config
2021-02-14 07:35:16 -03:00
Igor Lins e Silva 01570f1a96 Update server.ts 2021-02-13 14:52:46 -03:00
Igor Lins e Silva 6e3510ebb1 Update index-lifecycle-policies.ts 2021-02-13 12:31:43 -03:00
Igor Lins e Silva 985cd7e189 [indexer] clean debug messages on router 2021-02-13 12:29:43 -03:00
Igor Lins e Silva 6853f7e897 [indexer] ILM policy updates 2021-02-13 12:27:46 -03:00
Igor Lins e Silva 28c163a500 [streaming] fix exception on missing filters 2021-01-17 18:29:52 -03:00
Igor Lins e Silva b7798e41f4 [indexer] allow chain::*::name whitelisting 2021-01-16 02:04:34 -03:00
Igor Lins e Silva 404808f1b7 update interfaces 2021-01-16 02:03:33 -03:00
Igor Lins e Silva 565a34b366 Update example.config.json 2021-01-16 02:02:32 -03:00
Igor Lins e Silva 11b7b28212 update got to 11.8.1 2021-01-12 06:49:42 -03:00
Igor Lins e Silva 7ca25d6791 [api] stats/get_action_usage improvements
- use global_sequence as sort method
- include optional unique_actors computation
- dedicated caching
2021-01-07 01:12:31 -03:00
Igor Lins e Silva fc99cc2e3f fix hardcoded actions limit 2021-01-05 06:08:23 -03:00
Igor Lins e Silva 2bb5b801b4 [api] add /v2/stats/get_action_usage 2021-01-05 03:29:13 -03:00
Igor Lins e Silva 8e1d0db9cb Update hyperionConfig.ts 2020-12-31 16:27:04 -03:00
Igor Lins e Silva 8d96e4f525 [api] time fix on get_deltas 2020-12-31 16:25:12 -03:00
Igor Lins e Silva 00f063e2e4 [indexer] hot-warm index policy option 2020-12-31 16:24:47 -03:00
Igor Lins e Silva d5b12d89bd [api] add trx_id as primary filter 2020-12-24 13:41:41 -03:00
Igor Lins e Silva ad8a3bedb7 [api] add hot_only param to get_transaction 2020-12-24 13:40:02 -03:00
Igor Lins e Silva 20a0b1d4a2 [api] add hot_only param
allow limiting query to the current hot index for lower latency
2020-12-24 13:20:05 -03:00
Igor Lins e Silva d13e186fdc Update socketManager.ts 2020-12-17 14:19:28 -03:00
Igor Lins e Silva 50400787f1 Update socketManager.ts 2020-12-17 14:13:51 -03:00
Igor Lins e Silva b451619555 Update socketManager.ts 2020-12-17 13:38:49 -03:00
Igor Lins e Silva b82dddac7b Update socketManager.ts 2020-12-17 13:32:14 -03:00
Igor Lins e Silva f372f792e5 Update socketManager.ts 2020-12-17 13:28:29 -03:00
Igor Lins e Silva d6533332c6 Update socketManager.ts 2020-12-17 13:23:51 -03:00
Igor Lins e Silva 96d9a5aca0 Update example.config.json 2020-10-11 14:35:12 -03:00
Igor Lins e Silva f2c8b9bdf9 [streaming] prevent invalid requests from crashing the api 2020-09-23 21:43:01 -03:00
Igor Lins e Silva d122d79022 [api] use body hash as key for POST cache 2020-09-11 02:43:35 -03:00
Igor Lins e Silva aab5c66c89 Update package.json 2020-09-07 20:24:34 -03:00
Igor Lins e Silva 4f0d7b18f3 fix fetch import 2020-09-07 20:24:25 -03:00
Igor Lins e Silva e6d66181e1 increase get_tokens default limit 2020-09-07 20:24:00 -03:00
Igor Lins e Silva 4ac735dd73 increase get_links default and add present filter 2020-09-07 20:23:47 -03:00
Igor Lins e Silva 341769d92a Merge branch 'master' of https://github.com/eosrio/Hyperion-History-API 2020-09-02 16:55:36 -03:00
Igor Lins e Silva c7a841e6cd Update package-lock.json 2020-09-02 16:54:13 -03:00
Igor Lins e Silva 71831f0948 Update package.json 2020-09-02 16:54:11 -03:00
Igor Lins e Silva ca2ae004f2 apply flatstr 2020-09-02 16:53:02 -03:00
Igor Lins e Silva 908a0e5636 Merge pull request #43 from eosrio/dependabot/npm_and_yarn/lodash-4.17.19
Bump lodash from 4.17.15 to 4.17.19
2020-08-25 06:49:13 -03:00
Igor Lins e Silva aaada8cfb7 Merge pull request #45 from eosrio/dependabot/npm_and_yarn/fastify-2.15.1
Bump fastify from 2.14.1 to 2.15.1
2020-08-25 06:48:59 -03:00
Igor Lins e Silva c721eea8a6 Merge pull request #41 from AlohaEOS/master
Correct offset accuracy when system timezone is not UTC
2020-08-25 06:48:04 -03:00
Igor Lins e Silva 769d942fc5 increase get_key_accounts default limit to 100 accounts 2020-08-21 20:46:31 -03:00
Igor Lins e Silva d44be3b2ee add skip limit to get_key_accounts 2020-08-21 04:33:46 -03:00
dependabot[bot] 888171efe6 Bump fastify from 2.14.1 to 2.15.1
Bumps [fastify](https://github.com/fastify/fastify) from 2.14.1 to 2.15.1.
- [Release notes](https://github.com/fastify/fastify/releases)
- [Commits](https://github.com/fastify/fastify/compare/v2.14.1...v2.15.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-08-05 15:17:40 +00:00
Igor Lins e Silva c3acca1516 get_tokens schema info update 2020-07-27 16:36:33 -03:00
Igor Lins e Silva 02472751a5 [api] add skip/limit to get_tokens 2020-07-27 16:22:22 -03:00
dependabot[bot] 27dd37bb4c Bump lodash from 4.17.15 to 4.17.19
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-16 17:34:15 +00:00
Nate Denning 61d9309a2d Correct offset accuracy when system timezone is not UTC 2020-06-30 11:36:41 -06:00
Igor Lins e Silva 3a7d422445 Update example.config.json 2020-06-24 12:08:33 -03:00
Igor Lins e Silva 64452c8cd6 Update README.md 2020-06-15 17:34:12 -03:00
Igor Lins e Silva e91ece0a39 3.1.0-beta.4
- improved index remap on rewrite
- v2/stats/get_resource_usage
- include sorting on deltas
2020-06-11 10:57:29 -03:00
Igor Lins e Silva c53ef3ee61 Update package.json 2020-06-08 03:55:32 -03:00
Igor Lins e Silva 86c00f68b5 [indexer] flow control limiter fixed
fixes an issue where the state reader would get stuck on a artificial flow rate
2020-06-08 02:07:49 -03:00
Igor Lins e Silva 3a3e6acd7a [indexer] leave max_inline limiter undefined as default 2020-06-08 02:05:15 -03:00
Igor Lins e Silva ceecc977e2 [indexer] fix missing cached abi for live reading 2020-06-06 09:43:46 -03:00
Igor Lins e Silva ba637daa14 Update package.json 2020-06-06 08:28:16 -03:00
Igor Lins e Silva ba18937cd5 [hub] include direct path to chain logo 2020-06-05 07:16:06 -03:00
Igor Lins e Silva 3bf0226c8e [hub] add location and explorer info 2020-06-05 07:12:57 -03:00
João Barbosa 9b990926c3 docker updated 2020-06-04 13:39:42 -03:00
Igor Lins e Silva 163f68f795 3.1.0-beta.3
- add "settings.auto_mode_switch" (experimental)
- logger cleanup
- better shutdown timing
2020-06-03 12:53:05 -03:00
Igor Lins e Silva b6ae493894 cleanup 2020-06-03 11:21:45 -03:00
Igor Lins e Silva 40cffc34fb [common] remove redundant size field 2020-06-03 10:11:01 -03:00
Igor Lins e Silva 40f01c6783 [indexer] add total abis on "block range completed" message 2020-06-03 10:10:17 -03:00
Frederik Schöll c3447674e6 Merge branch 'upstream_master' into health_total_blocks 2020-06-03 11:01:16 +02:00
Igor Lins e Silva df9697f8fa [indexer] add details to deserializer errors 2020-06-03 04:14:44 -03:00
Frederik Schöll 135a4152f0 add total indexed blocks to health api 2020-06-02 16:36:28 +02:00
Igor Lins e Silva 11614daa3a [indexer/experimental] implements fill_state to sync current account data 2020-06-02 06:48:23 -03:00
Igor Lins e Silva 627d889f78 [indexer] use symbol instead of primary_key for account table id 2020-06-02 04:08:05 -03:00
Igor Lins e Silva a2c2c4cf8a Update elastic-routes.ts 2020-06-02 04:05:37 -03:00
Igor Lins e Silva 89aa03bdba [indexer] update id constructors 2020-06-02 04:01:11 -03:00
Igor Lins e Silva 01fd572376 [indexer] remove pk from synced tables 2020-06-02 03:59:46 -03:00
Igor Lins e Silva 5b351452f8 [indexer] fix missing data upon rewrite for remapped indices
this issue only affects rewrite operations, first time indexed data for action and delta indices were not affected, if the indexing was done with rewrite=true a reindex is recommended
2020-06-02 02:47:27 -03:00
Igor Lins e Silva ac8872bf22 [indexer] add max_depth option for whitelist validation 2020-06-01 23:36:56 -03:00
Igor Lins e Silva e065c1c6ef [indexer] make the whitelist optimization for root only checks optional
- added "whitelists.root_only"
2020-06-01 22:37:58 -03:00
Igor Lins e Silva 64548cf3e1 3.1.0-beta.1 2020-05-31 18:53:27 -03:00
Igor Lins e Silva 2fbc9ecda4 3.1.0-beta.1
- update delta index id (add pk)
- improve error handling for whitelist optimized mode
- add block_id to deltas
- add settings.ds_profiling
- disable indexing on delta "value" field for non-deserialized data
2020-05-31 17:27:14 -03:00
Igor Lins e Silva eae3f16b44 [indexer] optimization for whitelisted operation
ds_profiling option added, logging 1st stage deserialization steps
2020-05-31 06:22:03 -03:00
Igor Lins e Silva 2129945d4d [indexer] include signatures to root action 2020-05-31 06:20:26 -03:00
Igor Lins e Silva 6cd4a4d315 [indexer] apply ds_threads to live_blocks queue 2020-05-30 20:30:08 -03:00
Igor Lins e Silva e562fb3104 [api] implement query params on stats/get_missed_blocks 2020-05-30 16:43:49 -03:00
Igor Lins e Silva 8214a4ad82 [indexer] add settings.max_ws_payload_kb 2020-05-30 08:57:18 -03:00
Igor Lins e Silva fabcc5e96c [indexer] fix leak on streaming deltas 2020-05-30 07:40:33 -03:00
Igor Lins e Silva 41eab518e0 [indexer] fix crash on missing traces 2020-05-29 12:30:56 -03:00
Igor Lins e Silva d6d1c05bbf Update master.ts 2020-05-29 10:37:37 -03:00
Igor Lins e Silva 50fe19bce1 hyperion hub support 2020-05-28 21:48:38 -03:00
Igor Lins e Silva 61a81e68e8 [indexer] fetch_deltas param 2020-05-26 23:55:28 -03:00
Igor Lins e Silva 5b54b043f6 [api] missing config file error handling 2020-05-26 23:54:04 -03:00
Igor Lins e Silva a186d63b6d [indexer] include hard_fails 2020-05-24 01:02:17 -03:00
Igor Lins e Silva d48c439dfb 3.0.1-beta.1 2020-05-23 23:54:43 -03:00
João Barbosa 1409453556 docker cleanup script added 2020-05-22 22:55:49 -03:00
Igor Lins e Silva a633142e3d [indexer] correctly handle non-standard transfers 2020-05-22 21:46:54 -03:00
Igor Lins e Silva 73cca21fae [indexer] add all params to the heap report 2020-05-22 21:46:23 -03:00
Igor Lins e Silva d261cd4bc1 [explorer] fix typo 2020-05-22 20:13:46 -03:00
Igor Lins e Silva 47e833a92e Update package.json 2020-05-22 13:07:19 -03:00
Igor Lins e Silva 953633dd33 [indexer] fix round_robin mode 2020-05-22 12:59:00 -03:00
Igor Lins e Silva a183eb420a Update environment.ts 2020-05-22 00:51:45 -03:00
Igor Lins e Silva fe3692b387 3.0.0-beta.3 2020-05-22 00:47:48 -03:00
Igor Lins e Silva 983673d775 logger cleanup 2020-05-22 00:42:22 -03:00
Igor Lins e Silva dd6594a6d5 update packages 2020-05-22 00:42:12 -03:00
Igor Lins e Silva 40ce288d20 Update example.config.json 2020-05-22 00:27:19 -03:00
Igor Lins e Silva da0ff2f3c8 Update account.component.ts 2020-05-22 00:27:16 -03:00
Igor Lins e Silva 74ccb53358 [explorer] fix for missing accounts on key search 2020-05-22 00:27:12 -03:00
Igor Lins e Silva f498774496 [explorer] dynamic page titles 2020-05-21 23:35:07 -03:00
Igor Lins e Silva 99e957f418 explorer dist update 2020-05-21 23:05:50 -03:00
Igor Lins e Silva 106f45a7ee [explorer/api] include custom_core_token 2020-05-21 23:02:46 -03:00
Igor Lins e Silva 257c26d7b8 Update package.json 2020-05-21 17:21:50 -03:00
Igor Lins e Silva 4ff6e3d209 [explorer] fix token symbol on liquid=0 2020-05-21 17:20:40 -03:00
Igor Lins e Silva f4be437bbd Update README.md 2020-05-21 15:23:45 -03:00
Igor Lins e Silva c11155456c v3.0.0-beta.2 2020-05-21 14:58:35 -03:00
Igor Lins e Silva 49ef6696ca Update deserializer.ts 2020-05-21 06:42:03 -03:00
Igor Lins e Silva 0f38ca6a6f v3.0.0-beta.1 2020-05-21 06:38:29 -03:00
Igor Lins e Silva 9ae9e420ad v3.0.0-beta.1 2020-05-21 06:32:12 -03:00
Igor Lins e Silva 34931afbdf Update socketManager.ts 2020-05-21 06:31:08 -03:00
Igor Lins e Silva cb2b8bfdef [api] add generated trx to get_transaction 2020-05-21 06:30:50 -03:00
Igor Lins e Silva 8d8dee60a1 Update base-parser.ts 2020-05-17 22:16:59 -03:00
Igor Lins e Silva 4c9f6e80f2 [indexer] catch errors on custom action enhancers 2020-05-17 22:13:34 -03:00
Igor Lins e Silva d2633cdded Update elastic-routes.ts 2020-05-17 08:54:22 -03:00
Igor Lins e Silva 6a10502240 [indexer] minor updates 2020-05-17 08:43:37 -03:00
Igor Lins e Silva 582f423a1e Update deserializer.ts 2020-05-16 06:02:25 -03:00
Igor Lins e Silva 5a09572e22 Update deserializer.ts 2020-05-16 05:46:12 -03:00
Igor Lins e Silva e001770045 Update deserializer.ts 2020-05-16 05:43:02 -03:00
Igor Lins e Silva 4c8d625eca Update deserializer.ts 2020-05-16 03:48:43 -03:00
Igor Lins e Silva d725bda2b7 Update ds-pool.ts 2020-05-16 03:38:07 -03:00
Igor Lins e Silva fc48586365 cleanup 2020-05-16 03:21:04 -03:00
Igor Lins e Silva 74f146ae2a update packages 2020-05-15 23:30:28 -03:00
Igor Lins e Silva e7e60f4e51 [indexer] only deserialize account deltas on abi scan mode 2020-05-15 22:59:33 -03:00
Igor Lins e Silva 59e8e51a8d Update master.ts 2020-05-15 22:58:30 -03:00
Igor Lins e Silva f445fc9dd0 set http as default for es connector 2020-05-15 22:58:02 -03:00
Igor Lins e Silva 64e727bbff [indexer] allow self-signed https connections 2020-05-14 21:00:41 -03:00
Igor Lins e Silva 0655806ab4 Update manager.class.ts 2020-05-14 20:58:54 -03:00
Igor Lins e Silva b310b4327e Update manager.class.ts 2020-05-14 20:56:04 -03:00
Igor Lins e Silva 5e78f79c4f [indexer] specifiy ES connection protocol 2020-05-14 20:53:32 -03:00
Igor Lins e Silva 4838379bce Update example.config.json 2020-05-14 20:48:48 -03:00
Igor Lins e Silva 13504e3d20 Update example.config.json 2020-05-14 20:38:25 -03:00
Igor Lins e Silva 595599294a [indexer] update deserializer 2020-05-14 13:27:46 -03:00
Igor Lins e Silva c5d9cf4a96 [indexer] fix delta whitelist map 2020-05-14 12:36:39 -03:00
Igor Lins e Silva 23cb750527 [explorer] update production build 2020-05-14 01:56:25 -03:00
Igor Lins e Silva fec31246da [explorer] UX updates 2020-05-14 01:45:27 -03:00
Igor Lins e Silva c411b17431 [indexer] relay fork events & improve sequential block handling 2020-05-14 01:14:53 -03:00
Igor Lins e Silva 94cdc6b9a5 [api] add stats/get_missed_block history/get_schedule 2020-05-14 01:12:11 -03:00
Dominique Deschatre 26884b9c27 [explorer] responsive improvements and dinamic symbol 2020-05-14 00:53:54 -03:00
Igor Lins e Silva a2496283e5 [explorer] add lazy pagination 2020-05-13 14:29:06 -03:00
Igor Lins e Silva 4f66e6143e cleanup 2020-05-13 14:28:50 -03:00
Igor Lins e Silva 11a93481ef [explorer] dist update 2020-05-13 14:28:38 -03:00
Igor Lins e Silva 33d93a9f60 [api] track total on get_account actions 2020-05-13 14:28:23 -03:00
Igor Lins e Silva 5caf21d656 Delete INSTALL.md 2020-05-13 12:46:06 -03:00
Igor Lins e Silva f7a499cb97 [explorer] dist update 2020-05-13 00:53:36 -03:00
Igor Lins e Silva 96f00f4969 [explorer] minor updates 2020-05-13 00:53:20 -03:00
Igor Lins e Silva 9aaca9bca9 [explorer] add account creation info 2020-05-13 00:47:26 -03:00
Igor Lins e Silva 0c272f9bb4 [api/stream] fix mixed lib reports 2020-05-13 00:31:01 -03:00
Igor Lins e Silva 4354482e0d [explorer] update account page 2020-05-13 00:30:29 -03:00
Igor Lins e Silva 9d2aacfdfa [master] fix lib_update with disabled streaming 2020-05-12 16:04:36 -03:00
Igor Lins e Silva 64e71cce82 [indexer] don't index userres or reslimits on abi_scan_mode 2020-05-12 14:34:05 -03:00
Igor Lins e Silva 5b3fbd0eb7 [indexer/reader] fix fork log display 2020-05-12 14:30:08 -03:00
Igor Lins e Silva 649a584f05 [indexer/monitor] disable live monitoring on abi scan pass 2020-05-12 14:29:42 -03:00
Igor Lins e Silva 1f4016693e [api] update routes 2020-05-12 14:28:57 -03:00
Igor Lins e Silva ebaaaf4bb7 [explorer] remove root redirect 2020-05-12 14:28:36 -03:00
Dominique Deschatre 8bfa6e1c66 [explorer] block page responsive fix 2020-05-11 22:16:16 -03:00
Igor Lins e Silva 920ad417d4 [explorer] fix loader initial state 2020-05-11 22:02:32 -03:00
Igor Lins e Silva f0366232e2 [explorer] update loader on all pages 2020-05-11 21:54:54 -03:00
Igor Lins e Silva c8ce39131e [explorer] add loader on account page 2020-05-11 21:47:30 -03:00
Dominique Deschatre f1ee6acdf4 [explorer] tokens panel responsive fix 2020-05-11 21:15:22 -03:00
Dominique Deschatre daba656a5a [explorer] minor improvements 2020-05-11 20:59:18 -03:00
Dominique Deschatre f17016160b [explorer] resources ui & votes card 2020-05-11 20:29:43 -03:00
Igor Lins e Silva f3c6a07ab4 [explorer] dist update 2020-05-08 00:36:33 -03:00
Igor Lins e Silva 86d618e84c [explorer] add lib tags to block and tx pages 2020-05-07 13:04:12 -03:00
Dominique Deschatre 57b71cc82e Update account.component.ts 2020-05-07 11:42:31 -03:00
Dominique Deschatre 39c74e28dd Merge branch 'master' of https://github.com/eosrio/hyperion-history-api 2020-05-07 11:42:24 -03:00
Dominique Deschatre 13b2932a00 [explorer] conversion fix 2020-05-07 11:40:21 -03:00
Igor Lins e Silva df4517d90b [api/explorer] allow lib tracking via streaming 2020-05-07 01:31:01 -03:00
Igor Lins e Silva a996185802 [explorer] dist package 2020-05-07 01:30:13 -03:00
Dominique Deschatre 3c0df91013 [explorer] unit conversion 2020-05-07 00:24:47 -03:00
Igor Lins e Silva 034c8860a2 Merge branch 'master' of https://github.com/eosrio/Hyperion-History-API 2020-05-06 00:55:48 -03:00
Igor Lins e Silva 4e61626858 [api] response fixes 2020-05-06 00:55:38 -03:00
Dominique Deschatre a64af59725 [explorer] livestream off & table pagination on acc page 2020-05-06 00:55:28 -03:00
Igor Lins e Silva 4a762b64bc Merge branch 'master' of https://github.com/eosrio/Hyperion-History-API 2020-05-06 00:32:01 -03:00
Igor Lins e Silva b018a8fc34 Update package-lock.json 2020-05-06 00:31:58 -03:00
Igor Lins e Silva 00df96e05c [explorer] add live streaming 2020-05-06 00:31:54 -03:00
João Barbosa d22540167b Improvements to hyperion docker startup script 2020-05-05 22:10:56 -03:00
João Barbosa 957ec2a0c8 Error fixed in Hyperion docker when starting chain from snapshot 2020-05-05 18:35:34 -03:00
João Barbosa da29a1d562 Improvements to hyperion docker startup sequence 2020-05-05 16:54:36 -03:00
João Barbosa 77af9982e6 Allow chain to start from snapshot in Hyperion docker 2020-05-05 00:47:41 -03:00
João Barbosa 9624a1e860 several improvements to hyperion docker 2020-05-04 19:15:19 -03:00
Felipe Faria 60baffb2f8 Merge branch 'master' of https://github.com/eosrio/Hyperion-History-API 2020-05-03 15:24:17 -03:00
Felipe Faria 581d43e44d help menu 2020-05-03 15:24:06 -03:00
Igor Lins e Silva b469964370 Merge branch 'master' of https://github.com/eosrio/Hyperion-History-API 2020-05-03 02:10:44 -03:00
Igor Lins e Silva a1fa6d20e8 [explorer] fix case-insensitive search 2020-05-03 02:10:41 -03:00
Igor Lins e Silva 9dd78d4f49 [explorer] add apple icon 2020-05-03 02:10:12 -03:00
Igor Lins e Silva ed5fdebbd3 [api] cleanup 2020-05-03 02:08:01 -03:00
Dominique Deschatre 78b5647e59 Merge branch 'master' of https://github.com/eosrio/hyperion-history-api 2020-05-03 02:05:07 -03:00
Dominique Deschatre a7ea1314ab [explorer] responsive fix 2020-05-03 02:03:37 -03:00
Felipe Faria 35630441b8 Merge branch 'master' of https://github.com/eosrio/Hyperion-History-API 2020-05-03 01:43:31 -03:00
Felipe Faria e6091f630b modify rabbit installation and some few other improvements 2020-05-03 01:42:53 -03:00
Igor Lins e Silva 0e3096c2ab [api] start script improvements 2020-05-03 01:34:29 -03:00
Igor Lins e Silva bf6ba4f815 [get_tokens] api cleanup 2020-05-03 01:10:05 -03:00
Igor Lins e Silva 3dbd7a3ec3 [explorer] fix PWA build 2020-05-03 01:03:19 -03:00
Igor Lins e Silva 9d1c749670 [explorer] v1 dist 2020-05-02 20:49:39 -03:00
Igor Lins e Silva fc24298531 Merge branch 'master' of https://github.com/eosrio/Hyperion-History-API 2020-05-02 19:49:55 -03:00
Igor Lins e Silva 33b82a1d76 [explorer] v0 dist 2020-05-02 19:49:52 -03:00
Dominique Deschatre 8e2d6f240f [explorer] scroll to top on route change 2020-05-02 19:48:42 -03:00
Dominique Deschatre a8b08dd1ee Merge branch 'master' of https://github.com/eosrio/hyperion-history-api 2020-05-02 19:47:10 -03:00
Dominique Deschatre 3d79a3c568 [explorer] minor improvements 2020-05-02 19:47:05 -03:00
Igor Lins e Silva d74cd49a78 Update .gitignore 2020-05-02 19:37:53 -03:00
Igor Lins e Silva 756e554bfc Update package.json 2020-05-02 19:34:01 -03:00
Igor Lins e Silva 206a818563 Update package.json 2020-05-02 19:29:38 -03:00
Igor Lins e Silva c34e9adde0 [explorer] angular updates 2020-05-02 19:28:22 -03:00
Igor Lins e Silva 80438ba8c4 [api] optional chain api error logs 2020-05-02 19:28:04 -03:00
Dominique Deschatre d08244d5c5 Update icon.png 2020-05-02 19:14:22 -03:00
Dominique Deschatre dae915edf8 Create icon.png 2020-05-02 19:11:31 -03:00
Igor Lins e Silva f54b91088f [explorer] fix serving from deep paths 2020-05-01 03:12:22 -03:00
Igor Lins e Silva 7ab8a16895 [explorer] account UI 2020-05-01 00:22:44 -03:00
Igor Lins e Silva 1d2d20ebde Update app.module.ts 2020-04-30 23:28:58 -03:00
Dominique Deschatre adc79b5754 [explorer] minor fixes 2020-04-30 23:27:50 -03:00
Igor Lins e Silva 9b9f04530d [explorer] cleanup 2020-04-30 23:25:37 -03:00
Igor Lins e Silva 6b182ec9c0 Update search.service.ts 2020-04-30 23:22:54 -03:00
Igor Lins e Silva 3b3e0615c7 [explorer] fix permissions display 2020-04-30 23:19:32 -03:00
Igor Lins e Silva 83ceee1626 [explorer] update search 2020-04-30 21:48:09 -03:00
Igor Lins e Silva 471d60e0b3 [explorer] fix hyperion logo paths 2020-04-30 21:29:19 -03:00
Igor Lins e Silva 185d11dbd5 Merge branch 'master' of https://github.com/eosrio/Hyperion-History-API 2020-04-30 21:22:23 -03:00
Igor Lins e Silva 8996203cf9 cleanup 2020-04-30 21:22:14 -03:00
Igor Lins e Silva 24b700021f [indexer] fix index rate counter 2020-04-30 21:22:03 -03:00
Igor Lins e Silva fe549376cd [indexer] include present info on account tables 2020-04-30 21:21:39 -03:00
Dominique Deschatre 36c8183c9d [explorer] search by pub key page 2020-04-30 21:20:47 -03:00
Igor Lins e Silva fa1b16baf7 [explorer] Update environment 2020-04-30 21:20:38 -03:00
Igor Lins e Silva 2b86ac8e58 [explorer] fix error for invalid accounts 2020-04-30 20:19:59 -03:00
Igor Lins e Silva 77baa27210 [explorer] update packages 2020-04-30 20:06:58 -03:00
Igor Lins e Silva 211a210575 [explorer] dynamic search 2020-04-30 20:06:45 -03:00
Igor Lins e Silva f11a108cb9 [get_key_accounts] add detail option
include extended info about associated permissions
2020-04-30 19:39:00 -03:00
Dominique Deschatre 4fd064949a explorer new pages 2020-04-30 19:35:36 -03:00
Dominique Deschatre eac9221204 explorer updates 2020-04-30 18:17:12 -03:00
Igor Lins e Silva 3051b618e5 Merge branch 'master' of https://github.com/eosrio/Hyperion-History-API 2020-04-30 05:52:14 -03:00
Igor Lins e Silva 73e3865f7b allow indexer to redistribute docs to non-alias ES indices
this logic mitigates duplicated data across different index versions
2020-04-30 05:52:11 -03:00
Igor Lins e Silva 05d50842e2 Update server.ts 2020-04-30 05:50:24 -03:00
João Barbosa 6eb90312ef Docker compose updated 2020-04-30 00:46:07 -03:00
Igor Lins e Silva 0fe292ffef set default port and host to WS_ROUTER 2020-04-29 17:42:28 -03:00
Igor Lins e Silva 11d88be525 Update package.json 2020-04-29 17:34:25 -03:00
Igor Lins e Silva 42e402f721 Update example.config.json 2020-04-29 17:20:53 -03:00
Igor Lins e Silva 09c279d472 include all v1/chain routes
with options for dedicated chain and push apis
2020-04-29 17:19:40 -03:00
Igor Lins e Silva 34c9986834 update packages 2020-04-29 16:13:26 -03:00
Igor Lins e Silva 1f33017578 Update ecosystem_settings.js 2020-04-29 16:13:16 -03:00
Igor Lins e Silva c0fac8ba7d fix recursive mkdir 2020-04-29 16:13:09 -03:00
Igor Lins e Silva 0c4000626e fix routing of limited inline actions 2020-04-28 06:08:13 -03:00
Igor Lins e Silva 044c1b62f3 ignore routing rate message if 0 2020-04-27 03:18:55 -03:00
Igor Lins e Silva 8e8b20c871 safeguard against ds errors 2020-04-27 03:18:43 -03:00
Igor Lins e Silva 1834b6d812 implement delta blacklist parser 2020-04-27 03:18:20 -03:00
Igor Lins e Silva d063b46621 Update package.json 2020-04-27 03:17:31 -03:00
Igor Lins e Silva 87464a6687 exit after completion on non-live mode 2020-04-27 03:17:28 -03:00
Igor Lins e Silva 2a2904949f update default blacklists 2020-04-27 03:17:07 -03:00
Igor Lins e Silva bd6985d414 Update deserializer.ts 2020-04-26 02:33:30 -03:00
Igor Lins e Silva 97ed78782f catch type errors 2020-04-26 02:13:52 -03:00
Igor Lins e Silva 90a0359430 WIP - auto repair mode 2020-04-26 02:13:30 -03:00
Igor Lins e Silva 191e1cd69d fix mapping structure 2020-04-26 02:13:12 -03:00
Igor Lins e Silva e3523e7e24 remove deprecated params 2020-04-26 02:12:59 -03:00
Igor Lins e Silva dac4ae69bc Update auto_setup.sh 2020-04-25 21:36:35 -03:00
Igor Lins e Silva 51078fe4aa update install scripts 2020-04-25 21:33:41 -03:00
Igor Lins e Silva 20f2a44b48 Update install_env.sh 2020-04-25 20:53:57 -03:00
Igor Lins e Silva e436e822a6 fix callback being called twice 2020-04-25 07:53:06 -03:00
Igor Lins e Silva 149e5fcecf move logs to debug mode 2020-04-25 07:52:42 -03:00
Igor Lins e Silva 43d31b950c refactor delta processing 2020-04-25 07:52:21 -03:00
Igor Lins e Silva 511893dcda WIP - overwrite index alias on rewrite 2020-04-25 07:52:02 -03:00
Igor Lins e Silva 5f36caab08 async generic router 2020-04-25 07:50:56 -03:00
Igor Lins e Silva 9bf61f1cab Update index-templates.ts 2020-04-25 07:50:25 -03:00
Igor Lins e Silva 39513f1716 new ILP 2020-04-25 07:50:16 -03:00
Igor Lins e Silva 8424329b49 fix creating logs folder for standalone api usage 2020-04-25 07:50:04 -03:00
Igor Lins e Silva d2f095700a make failed indices creation a hard fail 2020-04-24 16:37:05 -03:00
Igor Lins e Silva 2079195f82 update get_actions search terms to match v3 mappings 2020-04-23 18:18:51 -03:00
Igor Lins e Silva 478bb57fd8 cleanup 2020-04-22 23:43:12 -03:00
Igor Lins e Silva 410526856e Update deserializer.ts 2020-04-22 23:42:58 -03:00
Igor Lins e Silva 017a6e5852 update primary query from get_key_accounts
fallback to action index during transition
2020-04-22 23:42:49 -03:00
Igor Lins e Silva 15197ccfd7 new 1.7 parser 2020-04-22 10:46:15 -03:00
Igor Lins e Silva da0dd9a51e update get_actions 2020-04-21 07:32:44 -03:00
Igor Lins e Silva cbb2b3390f remove eslint 2020-04-21 06:28:48 -03:00
Igor Lins e Silva f470caad2a update modules
got v11 fix
2020-04-21 03:41:04 -03:00
Igor Lins e Silva 855e1355f8 add noBinary option
allow reducing bandwidth usage by removing large binary actions data
2020-04-21 03:40:20 -03:00
Igor Lins e Silva da1efd54ab remove unused api routes 2020-04-21 03:39:14 -03:00
Igor Lins e Silva 4bff17dca4 Update state-reader.ts 2020-04-17 16:12:45 -03:00
Igor Lins e Silva d3a76f45ed update debugLogger 2020-04-17 15:54:26 -03:00
Igor Lins e Silva eeeec411a1 update tags 2020-04-16 01:45:06 -03:00
Igor Lins e Silva 90b9fffc4a add internal tag 2020-04-16 01:31:29 -03:00
Igor Lins e Silva 952390b262 add limit settings to get_transaction 2020-04-16 01:31:06 -03:00
Igor Lins e Silva adca13c45d explorer api updates 2020-04-15 02:02:15 -03:00
Igor Lins e Silva 19f2e0decd improve error handling 2020-04-15 02:01:58 -03:00
Igor Lins e Silva 66e0ef9922 get_block - allow [block_num: -1] for the most recent block 2020-04-14 04:32:39 -03:00
Igor Lins e Silva ee0f8e6584 update get_account 2020-04-14 04:22:38 -03:00
Igor Lins e Silva b26dba6653 v1 trace api polyfill for get_block 2020-04-14 04:22:11 -03:00
Igor Lins e Silva be95e0bb5b explorer update 2020-04-13 23:14:38 -03:00
Igor Lins e Silva 7442663647 Update package.json 2020-04-13 23:06:04 -03:00
Igor Lins e Silva 258bd34b2b Update example.config.json 2020-04-13 19:02:51 -03:00
Igor Lins e Silva 8fba5569b6 Update example.config.json 2020-04-13 14:14:41 -03:00
Igor Lins e Silva 1b3135b68e Update example.config.json 2020-04-13 13:53:14 -03:00
Igor Lins e Silva 2085649285 improve get_tokens and get_voters 2020-04-10 00:48:03 -03:00
Igor Lins e Silva b53464c824 FIO custom actions 2020-04-09 23:36:31 -03:00
Igor Lins e Silva 8f3ad3e6aa fix FIO missing data 2020-04-09 23:35:04 -03:00
Igor Lins e Silva 39d92b8533 custom abi overwrite 2020-04-09 23:34:53 -03:00
Igor Lins e Silva 4dd5d4b387 add get_abi_snapshot 2020-04-09 16:43:55 -03:00
Igor Lins e Silva a1cb97eb32 add get_created_accounts 2020-04-09 16:34:03 -03:00
Igor Lins e Silva 5b7586f870 updated get_creator 2020-04-09 15:53:27 -03:00
Igor Lins e Silva 3966c93b14 Delete _config.yml 2020-04-09 13:48:11 -03:00
Igor Lins e Silva 3b7ac6702f fix track total hits 2020-04-09 13:35:29 -03:00
Igor Lins e Silva 7b0a666211 move get_tokens to state query 2020-04-09 13:22:13 -03:00
Igor Lins e Silva 901e6de4c9 Update README.md 2020-04-08 16:19:49 -03:00
Igor Lins e Silva 36a19670ff remove docs from master 2020-04-08 14:38:01 -03:00
Igor Lins e Silva 976372aa40 Merge branch 'master' of https://github.com/eosrio/Hyperion-History-API 2020-04-08 14:16:03 -03:00
Igor Lins e Silva 2bdcef524a Update mkdocs.yml 2020-04-08 14:15:53 -03:00
Igor Lins e Silva f0b78ad063 Create CNAME 2020-04-08 14:03:43 -03:00
Igor Lins e Silva b8640c7dc8 Set theme jekyll-theme-cayman 2020-04-08 14:01:34 -03:00
Igor Lins e Silva cc600263b4 Set theme jekyll-theme-cayman 2020-04-08 14:00:57 -03:00
Igor Lins e Silva 35bbe827dc Update README.md 2020-04-08 14:00:13 -03:00
Igor Lins e Silva c312850240 Merge branch 'develop' 2020-04-08 13:57:01 -03:00
Igor Lins e Silva 84267a0b13 Set theme jekyll-theme-hacker 2020-04-08 13:42:33 -03:00
Igor Lins e Silva 9ee7746cd2 Update state-reader.worker.js 2020-02-05 19:52:51 -03:00
Igor Lins e Silva 70cf9f34c4 include head block time 2020-02-03 23:22:56 -03:00
Igor Lins e Silva 2a22055870 merge action metadata 2020-01-28 22:51:23 -03:00
Igor Lins e Silva 8376845e49 Update functions.js 2020-01-28 22:47:46 -03:00
Igor Lins e Silva ea2ad7c28e Update functions.js 2020-01-28 22:43:56 -03:00
Igor Lins e Silva 42bd0d3992 Update functions.js 2020-01-28 22:36:21 -03:00
Igor Lins e Silva a9819b26ee fix double notifications 2020-01-28 22:20:02 -03:00
311 changed files with 18578 additions and 15461 deletions
+24 -34
View File
@@ -11,33 +11,8 @@ pids
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
.npm
@@ -51,18 +26,11 @@ typings/
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# next.js build output
.next
.idea
*.pem
ecosystem.config.js
package-lock.json
connections.json
connections.json2
@@ -79,8 +47,6 @@ api/**/*.js
api/**/*.js.map
api/tests
hyperion-explorer/
hyperion-launcher.js
hyperion-launcher.js.map
@@ -107,3 +73,27 @@ modules/**/*.js.map
addons/**/*.js
addons/**/*.js.map
scripts/hpm.js
scripts/hpm.js.map
plugins/.state.json
plugins/repos
plugins/*.js
.gitmodules
docker/redis/data
docker/elasticsearch/data
docker/rabbitmq/data
docker/eosio/data
configuration_backups
scripts/hyp-config.js
scripts/hyp-config.js.map
scripts/hyp-repair.js
scripts/hyp-repair.js.map
scripts/repair-cli/*.js
scripts/repair-cli/*.js.map
-195
View File
@@ -1,195 +0,0 @@
### nodeos config.ini
```
state-history-dir = "state-history"
trace-history = true
chain-state-history = true
state-history-endpoint = 127.0.0.1:8080
plugin = eosio::state_history_plugin
```
### NodeJS
```bash
curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash -
sudo apt-get install -y nodejs
```
### PM2
```bash
sudo npm install pm2@latest -g
sudo pm2 startup
```
### Elasticsearch Installation
* Follow instructions on https://www.elastic.co/guide/en/elasticsearch/reference/current/deb.html (Ubuntu/Debian)
Edit `/etc/elasticsearch/elasticsearch.yml`
```
cluster.name: myCluster
bootstrap.memory_lock: true
```
Edit `/etc/elasticsearch/jvm.options`
```
# Set your heap size, avoid allocating more than 31GB, even if you have enought RAM.
# Test on your specific machine by changing -Xmx32g in the following command:
# java -Xmx32g -XX:+UseCompressedOops -XX:+PrintFlagsFinal Oops | grep Oops
-Xms16g
-Xmx16g
```
Disable swap on /etc/fstab
Allow memlock:
run `sudo systemctl edit elasticsearch` and add the following lines
```
[Service]
LimitMEMLOCK=infinity
```
Start elasticsearch and check the logs (verify if the memory lock was successful)
```bash
sudo service elasticsearch start
sudo less /var/log/elasticsearch/myCluster.log
sudo systemctl enable elasticsearch
```
Test the REST API `curl http://localhost:9200`
```
{
"name" : "ip-172-31-5-121",
"cluster_name" : "hyperion",
"cluster_uuid" : "....",
"version" : {
"number" : "7.1.0",
"build_flavor" : "default",
"build_type" : "deb",
"build_hash" : "606a173",
"build_date" : "2019-05-16T00:43:15.323135Z",
"build_snapshot" : false,
"lucene_version" : "8.0.0",
"minimum_wire_compatibility_version" : "6.8.0",
"minimum_index_compatibility_version" : "6.0.0-beta1"
},
"tagline" : "You Know, for Search"
}
```
### Kibana Installation
```bash
wget https://artifacts.elastic.co/downloads/kibana/kibana-7.4.0-amd64.deb
sudo apt install ./kibana-7.4.0-amd64.deb
sudo systemctl enable kibana
```
Open and test Kibana on `http://localhost:5601`
### RabbitMQ Installation
`https://www.rabbitmq.com/install-debian.html#installation-methods`
```bash
sudo apt install rabbitmq-server
sudo rabbitmq-plugins enable rabbitmq_management
sudo rabbitmqctl add_vhost /hyperion
sudo rabbitmqctl add_user my_user my_password
sudo rabbitmqctl set_user_tags my_user administrator
sudo rabbitmqctl set_permissions -p /hyperion my_user ".*" ".*" ".*"
```
Check access to the WebUI `http://localhost:15672`
### Redis Installation
```bash
sudo apt install redis-server
```
Edit `/etc/redis/redis.conf` and change supervised to `systemd`
```bash
sudo systemctl restart redis.service
```
### Hyperion Indexer
```bash
git clone https://github.com/eosrio/Hyperion-History-API.git
cd Hyperion-History-API
npm install
cp example-ecosystem.config.js ecosystem.config.js
```
Edit `ecosystem.config.js`
### Setup Indices and Aliases
Load templates first by starting the Hyperion Indexer in preview mode `PREVIEW: 'true'`
Indices and aliases are created automatically using the `CREATE_INDICES` option (set it to your version suffix e.g, v1, v2, v3)
If you want to create them manually, use the commands bellow on the kibana dev console
```
PUT mainnet-action-v1-000001
PUT mainnet-abi-v1-000001
PUT mainnet-block-v1-000001
POST _aliases
{
"actions": [
{
"add": {
"index": "mainnet-abi-v1-000001",
"alias": "mainnet-abi"
}
},
{
"add": {
"index": "mainnet-action-v1-000001",
"alias": "mainnet-action"
}
},
{
"add": {
"index": "mainnet-block-v1-000001",
"alias": "mainnet-block"
}
}
]
}
```
Before indexing actions into elasticsearch its required to do a ABI scan pass
Start with
```
ABI_CACHE_MODE: 'true',
FETCH_BLOCK: 'false',
FETCH_TRACES: 'false',
INDEX_DELTAS: 'false',
INDEX_ALL_DELTAS: 'false',
```
When indexing is finished, change the settings back and restart the indexer. In case you do not have much contract updates, you do not need to run a full pass.
Tune your configs to your specific hardware using the following settings:
```
BATCH_SIZE
READERS
DESERIALIZERS
DS_MULT
ES_IDX_QUEUES
ES_AD_IDX_QUEUES
READ_PREFETCH
BLOCK_PREFETCH
INDEX_PREFETCH
```
+78 -156
View File
@@ -1,177 +1,99 @@
# Hyperion History API - Private
Scalable Full History API Solution for EOSIO based blockchains
<!--suppress HtmlUnknownTarget, HtmlDeprecatedAttribute -->
<br></br>
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://eosrio.io/hyperion-white.png">
<img alt="Hyperion Logo"
src="https://eosrio.io/hyperion.png">
</picture>
</p>
Made with ♥ by [EOS Rio](https://eosrio.io/)
<h4 align="center">
Scalable Full History API Solution for
<a href="https://antelope.io">
Antelope
</a>
(former EOSIO) based blockchains
</h4>
### Introducing an storage-optimized action format for EOSIO
<br>
The original *history_plugin* bundled with eosio, that provided the v1 api, stored inline action traces nested inside their root actions. This led to an excessive amount of data being stored and also transferred whenever a user requested the action history for a given account. Also inline actions are used as a "event" mechanism to notify parties on a transaction. Based on those Hyperion implements some changes
Made with ♥ by [Rio Blocks / EOS Rio](https://rioblocks.io/?lang=en)
1. actions are stored in a flattened format
2. a parent field is added to the inline actions to point to the parent global sequence
3. if the inline action data is identical to the parent it is considered a notification and thus removed from the database
4. no blocks or transaction data is stored, all information can be reconstructed from actions
### [Official documentation](https://hyperion.docs.eosrio.io)
With those changes the API format focus on delivering faster search times, lower bandwidth overhead and easier usability for UI/UX developers.
### How to use
#### Action Data Structure (work in progress)
- [For Providers](https://hyperion.docs.eosrio.io/manual_installation/)
- `@timestamp` - block time
- `global_sequence` - unique action global_sequence, used as index id
- `parent` - points to the parent action (in the case of an inline action) or equal to 0 if root level
- `block_num` - block number where the action was processed
- `trx_id` - transaction id
- `producer` - block producer
- `act`
- `account` - contract account
- `name` - contract method name
- `authorization` - array of signers
- `actor` - signing actor
- `permission` - signing permission
- `data` - action data input object
- `account_ram_deltas` - array of ram deltas and payers
- `account`
- `delta`
- `notified` - array of accounts that were notified (via inline action events)
- [For Developers](https://hyperion.docs.eosrio.io/howtouse/)
## Dependencies
### Official plugins:
This setup has only been tested with Ubuntu 18.04, but should work with other OS versions too
- [Hyperion Lightweight Explorer](https://github.com/eosrio/hyperion-explorer-plugin)
- [Elasticsearch 7.5.X](https://www.elastic.co/downloads/elasticsearch)
- [RabbitMQ](https://www.rabbitmq.com/install-debian.html)
- [Redis](https://redis.io/topics/quickstart)
- [Node.js v13](https://github.com/nodesource/distributions/blob/master/README.md#installation-instructions)
- [PM2](http://pm2.keymetrics.io/docs/usage/quick-start/)
- Nodeos w/ state_history_plugin and chain_api_plugin
> The indexer requires redis, pm2 and node.js to be on the same machine. Other dependencies might be installed on other machines, preferably over a very high speed and low latency network. Indexing speed will vary greatly depending on this configuration.
## Setup Instructions
### 1. Overview
Install, configure and test all dependencies above before continuing
Hyperion is a full history solution for indexing, storing and retrieving Antelope blockchain's historical data.
Antelope protocol is highly scalable reaching up to tens of thousands of transactions per second demanding high
performance indexing and optimized storage and querying solutions. Hyperion is developed to tackle those challenges
providing open source software to be operated by block producers, infrastructure providers and dApp developers.
Read the step-by-step instructions here - [INSTALL.md](https://github.com/eosrio/Hyperion-History-API/blob/master/INSTALL.md)
Focused on delivering faster search times, lower bandwidth overhead and easier usability for UI/UX developers,
Hyperion implements an improved data structure. Actions are stored in a flattened format, transaction ids are added to
all inline actions, allowing to group by transaction without storing a full transaction index. Besides that if the inline
action data is identical to the parent, it is considered a notification and thus removed from the database.
No full block or transaction data is stored, all information can be reconstructed from actions and deltas, only a block
header index is stored.
#### 1. Clone & Install packages
```bash
git clone https://github.com/eosrio/Hyperion-History-API.git
cd Hyperion-History-API
npm install
```
### 2. Architecture
#### 2. Edit configs
```
cp example-ecosystem.config.js ecosystem.config.js
nano ecosystem.config.js
The following components are required in order to have a fully functional Hyperion API deployment.
* For small use cases, it is 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.
# Enter connection details here (chain name must match on the ecosystem file)
cp example-connections.json connections.json
nano connections.json
```
#### 2.1 Elasticsearch Cluster
connections.json Reference
```
{
"amqp": {
"host": "127.0.0.1:5672", // RabbitMQ Server
"api": "127.0.0.1:15672", // RabbitMQ API Endpoint
"user": "username",
"pass": "password",
"vhost": "hyperion" // RabbitMQ vhost
},
"elasticsearch": {
"host": "127.0.0.1:9200", // Elasticsearch HTTP API Endpoint
"ingest_nodes": ["127.0.0.1:9200"], // List of ES ingest nodes
"user": "elastic",
"pass": "password"
},
"redis": {
"host": "127.0.0.1",
"port": "6379"
},
"chains": {
"eos": { // Chain name (must match on the ecosystem file)
"http": "http://127.0.0.1:8888", // Nodeos Chain API Endpoint
"ship": "ws://127.0.0.1:8080" // Nodeos State History Endpoint
},
"other_chain": {...}
}
}
```
The ES cluster is responsible for storing all indexed data.
Direct access to the Hyperion API and Indexer must be provided. We recommend nodes in the
cluster to have at least 32 GB of RAM and 8 cpu cores. SSD/NVME drives are recommended for
maximum indexing throughput, although HDDs can be used for cold storage nodes.
For production environments, a multi-node cluster is highly recommended.
ecosystem.config.js Reference
```
CHAIN: 'eos', // chain prefix for indexing
ABI_CACHE_MODE: 'false', // only cache historical ABIs to redis
DEBUG: 'false', // debug mode - display extra logs for debugging
LIVE_READER: 'true', // enable continuous reading after reaching the head block
FETCH_DELTAS: 'false', // read table deltas
CREATE_INDICES: 'v1', // index suffix to be created, set to false to use existing aliases
START_ON: 0, // start indexing on block (0=disable)
STOP_ON: 0, // stop indexing on block (0=disable)
AUTO_STOP: 0, // automatically stop Indexer after X seconds if no more blocks are being processed (0=disable)
REWRITE: 'false', // force rewrite the target replay range
PURGE_QUEUES: 'false', // clear rabbitmq queues before starting the indexer
BATCH_SIZE: 2000, // parallel reader batch size in blocks
QUEUE_THRESH: 8000, // queue size limit on rabbitmq
LIVE_ONLY: 'false', // only reads realtime data serially
FETCH_BLOCK: 'true', // Request full blocks from the state history plugin
FETCH_TRACES: 'true', // Request traces from the state history plugin
PREVIEW: 'false', // preview mode - prints worker map and exit
DISABLE_READING: 'false', // completely disable block reading, for lagged queue processing
READERS: 3, // parallel state history readers
DESERIALIZERS: 4, // deserialization queues
DS_MULT: 4, // deserialization threads per queue
ES_IDX_QUEUES: 4, // elastic indexers per queue
ES_AD_IDX_QUEUES: 2, // multiplier for action indexing queues
READ_PREFETCH: 50, // Stage 1 prefecth size
BLOCK_PREFETCH: 5, // Stage 2 prefecth size
INDEX_PREFETCH: 500, // Stage 3 prefetch size
ENABLE_INDEXING: 'true', // enable elasticsearch indexing
INDEX_DELTAS: 'true', // index common table deltas (see delta on definitions/mappings)
INDEX_ALL_DELTAS: 'false' // index all table deltas (WARNING)
```
#### 3. Starting
`./run.sh Indexer`
or
```
pm2 start --only Indexer --update-env
pm2 logs Indexer
```
#### 4. Stopping
Stop reading and wait for queues to flush
```
pm2 trigger Indexer stop
```
Force stop
```
pm2 stop Indexer
```
#### 5. Starting the API node
`./run.sh API`
or
```
pm2 start --only API --update-env
pm2 logs API
```
## API Reference
#### 2.2 Hyperion Indexer
Documentation is automatically generated by Swagger/OpenAPI.
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](https://pm2.keymetrics.io) is used to launch and operate the indexer. The configuration
flexibility is very extensive,
so system recommendations will depend on the use case and data load. It will require access to at least one ES node,
RabbitMQ and the state history node.
Example: [OpenAPI Docs](https://eos.hyperion.eosrio.io/v2/docs)
#### 2.3 Hyperion API
### Roadmap
Parallelizable API server that provides the V2 and V1 (legacy history plugin) endpoints.
It is launched by PM2 and can also operate in cluster mode. It requires direct access to
at least one ES node for the queries and all other services for full healthcheck
- Table deltas storage & queries (in progress)
- Real-time streaming support (in progress)
- Plugin system (in progress)
- Control GUI
#### 2.4 RabbitMQ
Used as messaging queue and data transport between the indexer stages and for real-time data streaming
#### 2.5 Redis
Used for transient data storage across processes and for the preemptive transaction caching used on
the `v2/history/get_transaction` and `v2/history/check_transaction` endpoints
#### 2.6 Leap State History
[Leap / Nodeos](https://github.com/AntelopeIO/leap/tree/main/plugins/state_history_plugin) plugin used
to collect action traces and state deltas. Provides data via websocket to the indexer
#### 2.7 Hyperion Stream Client (optional)
Web and Node.js client for real-time streaming on enabled hyperion
providers. [Documentation](https://hyperion.docs.eosrio.io/stream_client/)
#### 2.8 Hyperion Plugins (optional)
Hyperion includes a flexible plugin architecture to allow further customization.
Plugins are managed by the `hpm` (hyperion plugin manager) command line tool.
+9 -2
View File
@@ -4,6 +4,7 @@ export function generateOpenApiConfig(config: HyperionConfig) {
const packageData = require('../../package');
const health_link = `https://${config.api.server_name}/v2/health`;
const explorer_link = `https://${config.api.server_name}/v2/explore`;
let description = `
<img height="64" src="https://eosrio.io/hyperion.png">
### Scalable Full History API Solution for EOSIO based blockchains
@@ -13,9 +14,15 @@ export function generateOpenApiConfig(config: HyperionConfig) {
#### Provided by [${config.api.provider_name}](${config.api.provider_url})
#### Health API: <a target="_blank" href="${health_link}">${health_link}</a>
`;
if(config.api.enable_explorer) {
description += `#### Integrated Explorer: <a target="_blank" href="${explorer_link}">${explorer_link}</a>`
if (config.plugins) {
if (config.plugins.explorer) {
if (config.plugins.explorer.enabled) {
description += `#### Integrated Explorer: <a target="_blank" href="${explorer_link}">${explorer_link}</a>`
}
}
}
return {
routePrefix: '/v2/docs',
exposeRoute: true,
+86
View File
@@ -0,0 +1,86 @@
import {HyperionConfig} from "../../interfaces/hyperionConfig";
import {createHash} from "crypto";
import {FastifyRequest} from "fastify";
export interface CacheConfig {
ttl: number;
}
export interface CachedEntry {
data: string;
exp: number;
}
export class CacheManager {
v1CacheConfigs: Map<string, CacheConfig> = new Map<string, CacheConfig>();
v1Caches: Map<string, Map<string, CachedEntry>> = new Map<string, Map<string, CachedEntry>>();
constructor(conf: HyperionConfig) {
if (conf.api.v1_chain_cache) {
conf.api.v1_chain_cache.forEach(value => {
this.v1CacheConfigs.set(value.path, {
ttl: value.ttl
});
this.v1Caches.set(value.path, new Map<string, CachedEntry>());
});
}
setInterval(() => {
try {
// remove expired entries
// let removeCount = 0;
const now = Date.now();
this.v1Caches.forEach((pathCacheMap: Map<string, CachedEntry>, pathKey: string) => {
pathCacheMap.forEach((cache: CachedEntry, entryKey: string, map: Map<string, CachedEntry>) => {
if (cache.exp < now) {
map.delete(entryKey);
// removeCount++;
}
});
});
// if (removeCount > 0) {
// console.log(`${removeCount} expired cache entries removed`);
// }
} catch (e) {
console.log(e);
}
}, 5000);
}
hashPayload(input: string): string {
return createHash('sha256').update(input).digest().toString('hex');
}
setCachedData(hash: string, path: string, payload: any): void {
if (this.v1CacheConfigs.has(path) && this.v1Caches.has(path)) {
this.v1Caches.get(path).set(hash, {
data: payload,
exp: this.v1CacheConfigs.get(path).ttl + Date.now()
});
}
}
getCachedData(request: FastifyRequest): [string | null, string, string] {
const urlParts = request.url.split("?");
const pathComponents = urlParts[0].split('/');
const path = pathComponents.at(-1);
let payload = '';
if (request.method === 'POST') {
payload = JSON.stringify(request.body);
} else if (request.method === 'GET') {
payload = request.url;
}
const hashedString = this.hashPayload(payload);
if (this.v1Caches.has(path)) {
const entry = this.v1Caches.get(path).get(hashedString);
if (entry && entry.exp > Date.now()) {
return [entry.data, hashedString, path];
} else {
return [null, hashedString, path];
}
} else {
return [null, hashedString, path];
}
}
}
+674 -21
View File
@@ -1,13 +1,370 @@
import {createHash} from "crypto";
import * as _ from "lodash";
import {FastifyInstance, FastifyReply, FastifyRequest, HTTPMethod, RouteSchema} from "fastify";
import {ServerResponse} from "http";
import {FastifyInstance, FastifyReply, FastifyRequest, FastifySchema, HTTPMethods} from "fastify";
import got from "got";
import {checkDeltaFilter, checkFilter, hLog} from "../../helpers/common_functions";
import {Socket} from "socket.io";
const deltaQueryFields = ['code', 'table', 'scope', 'payer'];
export async function streamPastDeltas(fastify: FastifyInstance, socket, data) {
const search_body = {
query: {bool: {must: []}},
sort: {block_num: 'asc'},
};
await addBlockRangeOpts(data, search_body, fastify);
deltaQueryFields.forEach(f => {
addTermMatch(data, search_body, f);
});
const onDemandDeltaFilters = [];
if (data.filters.length > 0) {
data.filters.forEach(f => {
if (f.field && f.value) {
if (f.field.startsWith('@') && !f.field.startsWith('data')) {
const _q = {};
_q[f.field] = f.value;
search_body.query.bool.must.push({'term': _q});
} else {
onDemandDeltaFilters.push(f);
}
}
});
}
const responseQueue = [];
let counter = 0;
let total = 0;
let totalFiltered = 0;
let longScroll = false;
// console.log(JSON.stringify(search_body, null, 2));
const init_response = await fastify.elastic.search({
index: fastify.manager.chain + '-delta-*',
scroll: '30s',
size: fastify.manager.config.api.stream_scroll_batch || 500,
body: search_body,
});
const scrollLimit = fastify.manager.config.api.stream_scroll_limit;
if (scrollLimit && scrollLimit !== -1 && init_response.body.hits.total.value > scrollLimit) {
const errorMsg = `Requested ${init_response.body.hits.total.value} deltas, limit is ${scrollLimit}.`;
socket.emit('message', {
reqUUID: socket.data.reqUUID,
type: 'delta_trace', mode: 'history', messages: [],
error: errorMsg
});
return {status: false, error: errorMsg};
}
if (init_response.body.hits.total.value > 10000) {
total = init_response.body.hits.total.value;
longScroll = true;
hLog(`Attention! Long scroll (deltas) is running!`);
}
// emit first block
if (init_response.body.hits.hits.length > 0) {
emitTraceInit(socket, init_response.body.hits.hits[0]._source.block_num, init_response.body.hits.total.value);
}
responseQueue.push(init_response);
let lastTransmittedBlock = 0;
let pendingScrollId = '';
while (responseQueue.length) {
let filterCount = 0;
const {body} = responseQueue.shift();
pendingScrollId = body['_scroll_id'];
const enqueuedMessages = [];
counter += body['hits']['hits'].length;
for (const doc of body['hits']['hits']) {
let allow = false;
if (onDemandDeltaFilters.length > 0) {
allow = onDemandDeltaFilters.every(filter => {
return checkDeltaFilter(filter, doc._source);
});
} else {
allow = true;
}
if (allow) {
enqueuedMessages.push(doc._source);
} else {
filterCount++;
}
// set last block
if (doc._source.block_num > lastTransmittedBlock) {
lastTransmittedBlock = doc._source.block_num;
}
}
totalFiltered += filterCount;
if (socket.connected) {
if (enqueuedMessages.length > 0) {
socket.emit('message', {
reqUUID: socket.data.reqUUID,
type: 'delta_trace',
mode: 'history',
messages: enqueuedMessages,
filtered: filterCount
});
}
} else {
hLog('LOST CLIENT');
break;
}
if (longScroll) {
hLog(`Progress: ${counter + totalFiltered}/${total}`);
}
if (body['hits'].total.value === counter) {
hLog(`${counter} past deltas streamed to ${socket.id} (${totalFiltered} filtered)`);
break;
}
const next_response = await fastify.elastic.scroll({
body: {scroll_id: body['_scroll_id'], scroll: '30s'}
});
responseQueue.push(next_response);
}
// destroy scroll context
await fastify.elastic.clearScroll({scroll_id: pendingScrollId});
return {status: true, lastTransmittedBlock};
}
export function emitTraceInit(socket: Socket, firstBlock: number, totalResults: number) {
socket.emit('message', {
reqUUID: socket.data.reqUUID,
type: 'trace_init',
mode: 'history',
first_block: firstBlock,
results: totalResults
});
}
export async function streamPastActions(fastify: FastifyInstance, socket, data) {
const search_body = {query: {bool: {must: []}}, sort: {global_sequence: 'asc'}};
await addBlockRangeOpts(data, search_body, fastify);
if (data.account !== '') {
search_body.query.bool.must.push({
bool: {
should: [
{term: {'notified': data.account}},
{term: {'act.authorization.actor': data.account}},
],
},
});
}
if (data.contract !== '*' && data.contract !== '') {
search_body.query.bool.must.push({'term': {'act.account': data.contract}});
}
if (data.action !== '*' && data.action !== '') {
search_body.query.bool.must.push({'term': {'act.name': data.action}});
}
const onDemandFilters = [];
if (data.filters.length > 0) {
data.filters.forEach(f => {
if (f.field && f.value) {
if (f.field.startsWith('@') && !f.field.startsWith('act.data')) {
const _q = {};
_q[f.field] = f.value;
search_body.query.bool.must.push({'term': _q});
} else {
onDemandFilters.push(f);
}
}
});
}
const responseQueue = [];
let counter = 0;
let total = 0;
let totalFiltered = 0;
let longScroll = false;
const init_response = await fastify.elastic.search({
index: fastify.manager.chain + '-action-*',
scroll: '30s',
size: fastify.manager.config.api.stream_scroll_batch || 500,
body: search_body,
});
const scrollLimit = fastify.manager.config.api.stream_scroll_limit;
if (scrollLimit && scrollLimit !== -1 && init_response.body.hits.total.value > scrollLimit) {
const errorMsg = `Requested ${init_response.body.hits.total.value} actions, limit is ${scrollLimit}.`;
socket.emit('message', {
reqUUID: socket.data.reqUUID,
type: 'action_trace', mode: 'history', messages: [],
error: `Requested ${init_response.body.hits.total.value} actions, limit is ${scrollLimit}.`
});
return {status: false, error: errorMsg};
}
if (init_response.body.hits.total.value > 10000) {
total = init_response.body.hits.total.value;
longScroll = true;
hLog(`Attention! Long scroll (actions) is running!`);
}
// emit first block
if (init_response.body.hits.hits.length > 0) {
emitTraceInit(socket, init_response.body.hits.hits[0]._source.block_num, init_response.body.hits.total.value);
}
responseQueue.push(init_response);
let lastTransmittedBlock = 0;
let pendingScrollId = '';
while (responseQueue.length) {
let filterCount = 0;
const {body} = responseQueue.shift();
pendingScrollId = body['_scroll_id'];
const enqueuedMessages = [];
counter += body['hits']['hits'].length;
for (const doc of body['hits']['hits']) {
let allow = false;
if (onDemandFilters.length > 0) {
allow = onDemandFilters.every(filter => {
return checkFilter(filter, doc._source);
});
} else {
allow = true;
}
if (allow) {
enqueuedMessages.push(doc._source);
} else {
filterCount++;
}
// set last block
if (doc._source.block_num > lastTransmittedBlock) {
lastTransmittedBlock = doc._source.block_num;
}
}
totalFiltered += filterCount;
if (socket.connected) {
if (enqueuedMessages.length > 0) {
socket.emit('message', {
reqUUID: socket.data.reqUUID,
type: 'action_trace',
mode: 'history',
messages: enqueuedMessages,
filtered: filterCount
});
}
} else {
hLog('LOST CLIENT');
break;
}
if (longScroll) {
hLog(`Progress: ${counter + totalFiltered}/${total}`);
}
if (body['hits'].total.value === counter) {
hLog(`${counter} past actions streamed to ${socket.id} (${totalFiltered} filtered)`);
break;
}
const next_response = await fastify.elastic.scroll({
body: {scroll_id: body['_scroll_id'], scroll: '30s'}
});
responseQueue.push(next_response);
}
// destroy scroll context
await fastify.elastic.clearScroll({scroll_id: pendingScrollId});
return {status: true, lastTransmittedBlock};
}
export function addTermMatch(data, search_body, field) {
if (data[field] && data[field] !== '*' && data[field] !== '') {
const termQuery = {};
termQuery[field] = data[field];
search_body.query.bool.must.push({'term': termQuery});
}
}
export async function addBlockRangeOpts(data, search_body, fastify: FastifyInstance) {
let timeRange;
let blockRange;
let head;
if (typeof data['start_from'] === 'string' && data['start_from'] !== '') {
if (!timeRange) {
timeRange = {"@timestamp": {}};
}
timeRange["@timestamp"]['gte'] = data['start_from'];
}
if (typeof data['read_until'] === 'string' && data['read_until'] !== '') {
if (!timeRange) {
timeRange = {"@timestamp": {}};
}
timeRange["@timestamp"]['lte'] = data['read_until'];
}
if (typeof data['start_from'] === 'number' && data['start_from'] !== 0) {
if (!blockRange) {
blockRange = {"block_num": {}};
}
if (data['start_from'] < 0) {
if (!head) {
head = (await fastify.eosjs.rpc.get_info()).head_block_num;
}
blockRange["block_num"]['gte'] = head + data['start_from'];
} else {
blockRange["block_num"]['gte'] = data['start_from'];
}
}
if (typeof data['read_until'] === 'number' && data['read_until'] !== 0) {
if (!blockRange) {
blockRange = {"block_num": {}};
}
if (data['read_until'] < 0) {
if (!head) {
head = (await fastify.eosjs.rpc.get_info()).head_block_num;
}
blockRange["block_num"]['lte'] = head + data['read_until'];
} else {
blockRange["block_num"]['lte'] = data['read_until'];
}
}
if (timeRange) {
search_body.query.bool.must.push({
range: timeRange,
});
}
if (blockRange) {
search_body.query.bool.must.push({
range: blockRange,
});
}
}
export function extendResponseSchema(responseProps: any) {
const props = {
query_time_ms: {type: "number"},
cached: {type: "boolean"},
hot_only: {type: "boolean"},
lib: {type: "number"},
last_indexed_block: {type: "number"},
last_indexed_block_time: {type: "string"},
total: {
type: "object",
properties: {
@@ -29,7 +386,7 @@ export function extendResponseSchema(responseProps: any) {
};
}
export function extendQueryStringSchema(queryParams: any) {
export function extendQueryStringSchema(queryParams: any, required?: string[]) {
const params = {
limit: {
description: 'limit of [n] results per page',
@@ -47,10 +404,14 @@ export function extendQueryStringSchema(queryParams: any) {
params[p] = queryParams[p];
}
}
return {
const schema = {
type: 'object',
properties: params
}
if (required && required.length > 0) {
schema["required"] = required;
}
return schema;
}
export async function getCacheByHash(redis, key, chain) {
@@ -80,14 +441,15 @@ export function mergeDeltaMeta(delta: any) {
return delta;
}
export function setCacheByHash(fastify, hash, response) {
export function setCacheByHash(fastify, hash, response, expiration?: number) {
if (fastify.manager.config.api.enable_caching) {
fastify.redis
.set(hash,
JSON.stringify(response),
'EX',
fastify.manager.config.api.cache_life)
.catch(console.log);
let exp;
if (expiration) {
exp = expiration;
} else {
exp = fastify.manager.config.api.cache_life;
}
fastify.redis.set(hash, JSON.stringify(response), 'EX', exp).catch(console.log);
}
}
@@ -98,9 +460,11 @@ export function getRouteName(filename: string) {
export function addApiRoute(
fastifyInstance: FastifyInstance,
method: HTTPMethod | HTTPMethod[],
method: HTTPMethods | HTTPMethods[],
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>,
schema: FastifySchema
) {
fastifyInstance.route({
url: '/' + routeName,
method,
@@ -113,8 +477,7 @@ export async function getCachedResponse(server: FastifyInstance, route: string,
const chain = server.manager.chain;
let resp, hash;
if (server.manager.config.api.enable_caching) {
const keystring = JSON.stringify(key);
[resp, hash] = await getCacheByHash(server.redis, route + keystring, chain);
[resp, hash] = await getCacheByHash(server.redis, route + JSON.stringify(key), chain);
if (resp) {
resp = JSON.parse(resp);
resp['cached'] = true;
@@ -135,8 +498,10 @@ export function getTrackTotalHits(query) {
} else if (query.track === 'false') {
trackTotalHits = false;
} else {
trackTotalHits = parseInt(query.track, 10);
if (isNaN(trackTotalHits)) {
const parsed = parseInt(query.track, 10);
if (parsed > 0) {
trackTotalHits = parsed;
} else {
throw new Error('failed to parse track param');
}
}
@@ -148,6 +513,12 @@ function bigint2Milliseconds(input: bigint) {
return parseFloat((parseInt(input.toString()) / 1000000).toFixed(3));
}
const defaultRouteCacheMap = {
get_resource_usage: 3600,
get_creator: 3600 * 24,
health: 10
}
export async function timedQuery(
queryFunction: (fastify: FastifyInstance, request: FastifyRequest) => Promise<any>,
fastify: FastifyInstance, request: FastifyRequest, route: string): Promise<any> {
@@ -156,27 +527,309 @@ export async function timedQuery(
const t0 = process.hrtime.bigint();
// check for cached data, return the response hash if caching is enabled
const [cachedResponse, hash] = await getCachedResponse(fastify, route, request.query);
const [cachedResponse, hash] = await getCachedResponse(
fastify,
route,
request.method === 'POST' ? request.body : request.query
);
if (cachedResponse) {
let lastIndexedBlockNumber = null;
let lastIndexedBlockTime = null;
const lastIndexedBlock = await fastify.redis.get(`${fastify.manager.chain}:last_idx_block`);
if (lastIndexedBlock) {
const arr = lastIndexedBlock.split("@");
if (arr.length === 2) {
lastIndexedBlockNumber = parseInt(arr[0], 10);
lastIndexedBlockTime = arr[1];
}
}
if (cachedResponse && !request.query["ignoreCache"]) {
// add cached query time
cachedResponse['query_time_ms'] = bigint2Milliseconds(process.hrtime.bigint() - t0);
if (lastIndexedBlock) {
cachedResponse['last_indexed_block'] = lastIndexedBlockNumber;
cachedResponse['last_indexed_block_time'] = lastIndexedBlockTime;
}
return cachedResponse;
}
// call query function
const response = await queryFunction(fastify, request);
// save response to cash
// save response to cache
if (hash) {
setCacheByHash(fastify, hash, response);
let EX = null;
if (defaultRouteCacheMap[route]) {
EX = defaultRouteCacheMap[route];
}
setCacheByHash(fastify, hash, response, EX);
}
// add normal query time
if (response) {
response['query_time_ms'] = bigint2Milliseconds(process.hrtime.bigint() - t0);
if (lastIndexedBlock) {
response['last_indexed_block'] = lastIndexedBlockNumber;
response['last_indexed_block_time'] = lastIndexedBlockTime;
}
return response;
} else {
return {};
}
}
export function chainApiHandler(fastify: FastifyInstance) {
return async (request: FastifyRequest, reply: FastifyReply) => {
// check cache
const [cachedData, hash, path] = fastify.cacheManager.getCachedData(request);
if (cachedData) {
console.log('cache hit:', path, hash);
reply.headers({'hyperion-cached': true}).send(cachedData);
} else {
// call actual request
const apiResponse = await handleChainApiRedirect(request, reply, fastify);
// console.log('cache miss:', path, hash);
fastify.cacheManager.setCachedData(hash, path, apiResponse);
}
}
}
export async function handleChainApiRedirect(
request: FastifyRequest,
reply: FastifyReply,
fastify: FastifyInstance
): Promise<string> {
const urlParts = request.url.split("?");
let reqUrl = fastify.chain_api + urlParts[0];
// const pathComponents = urlParts[0].split('/');
// const path = pathComponents.at(-1);
if (urlParts[0] === '/v1/chain/push_transaction' && fastify.push_api && fastify.push_api !== "") {
reqUrl = fastify.push_api + urlParts[0];
}
const opts = {};
if (request.method === 'POST') {
if (request.body) {
if (typeof request.body === 'string') {
opts['body'] = request.body;
} else if (typeof request.body === 'object') {
opts['body'] = JSON.stringify(request.body);
}
} else {
opts['body'] = "";
}
} else if (request.method === 'GET') {
opts['json'] = request.query;
}
try {
const apiResponse = await got.post(reqUrl, opts);
reply.headers({"Content-Type": "application/json"});
if (request.method === 'HEAD') {
reply.headers({"Content-Length": apiResponse.body.length});
reply.send("");
return '';
} else {
reply.send(apiResponse.body);
return apiResponse.body;
}
} catch (error) {
if (error.response) {
reply.status(error.response.statusCode).send(error.response.body);
} else {
console.log(error);
reply.status(500).send();
}
if (fastify.manager.config.api.chain_api_error_log) {
try {
if (error.response) {
const error_msg = JSON.parse(error.response.body).error.details[0].message;
console.log(`endpoint: ${request.raw.url} | status: ${error.response.statusCode} | error: ${error_msg}`);
} else {
console.log(error);
}
// if (request.req.url === '/v1/chain/push_transaction') {
// const packedTrx = JSON.parse(opts['body']).packed_trx;
// const trxBuffer = Buffer.from(packedTrx, 'hex');
// const trxData = await fastify.eosjs.api.deserializeTransactionWithActions(trxBuffer);
// console.log(trxData);
// }
} catch (e) {
console.log(e);
}
}
return '';
}
}
export function addChainApiRoute(fastify: FastifyInstance, routeName, description, props?, required?) {
const baseSchema = {
description: description,
summary: description,
tags: ['chain']
};
addApiRoute(
fastify,
['GET', 'HEAD'],
routeName,
chainApiHandler,
{
...baseSchema,
querystring: props ? {
type: 'object',
properties: props,
required: required
} : undefined
}
);
addApiRoute(
fastify,
'POST',
routeName,
chainApiHandler,
{
...baseSchema,
body: props ? {
type: ['object', 'string'],
properties: props,
required: required
} : undefined
}
);
}
export function addSharedSchemas(fastify: FastifyInstance) {
fastify.addSchema({
$id: "WholeNumber",
title: "Integer",
description: "Integer or String",
anyOf: [
{
type: "string",
pattern: "^\\d+$"
},
{
type: "integer"
}
],
});
fastify.addSchema({
$id: "Symbol",
type: "string",
description: "A symbol composed of capital letters between 1-7.",
pattern: "^([A-Z]{1,7})$",
title: "Symbol"
});
fastify.addSchema({
$id: "Signature",
type: "string",
description: "String representation of an EOSIO compatible cryptographic signature",
pattern: "^SIG_([RK]1|WA)_[1-9A-HJ-NP-Za-km-z]+$",
title: "Signature"
})
fastify.addSchema({
$id: "AccountName",
description: "String representation of an EOSIO compatible account name",
"anyOf": [
{
"type": "string",
"description": "String representation of privileged EOSIO name type",
"pattern": "^(eosio[\\.][a-z1-5]{1,6})([a-j]{1})?$",
"title": "NamePrivileged"
},
{
"type": "string",
"description": "String representation of basic EOSIO name type, must be 12 characters and contain only a-z and 0-5",
"pattern": "^([a-z]{1}[a-z1-5]{11})([a-j]{1})?$",
"title": "NameBasic"
},
{
"type": "string",
"description": "String representation of EOSIO bid name type, 1-12 characters and only a-z and 0-5 are allowed",
"pattern": "^([a-z1-5]{1,12})([a-j]{1})?$",
"title": "NameBid"
},
{
"type": "string",
"description": "String representation of EOSIO name type",
"pattern": "^([a-z1-5]{1}[a-z1-5\\.]{0,10}[a-z1-5]{1})([a-j]{1})?$",
"title": "NameCatchAll"
}
],
"title": "Name"
});
fastify.addSchema({
$id: "Expiration",
"description": "Time that transaction must be confirmed by.",
"type": "string",
"pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}$",
"title": "DateTime"
});
fastify.addSchema({
$id: "BlockExtensions",
"type": "array",
"items": {
"anyOf": [
{"type": "integer"},
{"type": "string"}
]
},
"title": "Extension"
})
fastify.addSchema({
$id: "ActionItems",
"type": "object",
"additionalProperties": false,
"minProperties": 5,
"required": [
"account",
"name",
"authorization",
"data",
"hex_data"
],
"properties": {
"account": {$ref: 'AccountName#'},
"name": {$ref: 'AccountName#'},
"authorization": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"minProperties": 2,
"required": [
"actor",
"permission"
],
"properties": {
"actor": {$ref: 'AccountName#'},
"permission": {$ref: 'AccountName#'}
},
"title": "Authority"
}
},
"data": {
"type": "object",
"additionalProperties": true
},
"hex_data": {
"type": "string"
}
},
"title": "Action"
});
}
+22 -12
View File
@@ -2,22 +2,32 @@ import * as Fastify from "fastify";
import {IncomingMessage, Server, ServerResponse} from "http";
// fastify plugins
import * as fastify_elasticsearch from 'fastify-elasticsearch';
import * as fastify_oas from 'fastify-oas';
import * as fastify_cors from 'fastify-cors';
import * as fastify_formbody from 'fastify-formbody';
import * as fastify_redis from 'fastify-redis';
import * as fastify_rate_limit from 'fastify-rate-limit';
import * as fastifyElasticsearch from 'fastify-elasticsearch';
import fastifySwagger from '@fastify/swagger';
import fastifyCors from '@fastify/cors';
import formBodyPlugin from '@fastify/formbody';
import fastifyRedis from '@fastify/redis';
import fastifyRateLimit from '@fastify/rate-limit';
// custom plugins
import fastify_eosjs from "./plugins/fastify-eosjs";
export function registerPlugins(server: Fastify.FastifyInstance<Server, IncomingMessage, ServerResponse>, params: any) {
server.register(fastify_elasticsearch, params.fastify_elasticsearch);
server.register(fastify_oas, params.fastify_oas);
server.register(fastify_cors);
server.register(fastify_formbody);
server.register(fastify_redis, params.fastify_redis);
server.register(fastifyElasticsearch, params.fastify_elasticsearch);
if (params.fastify_swagger) {
server.register(fastifySwagger, params.fastify_swagger);
}
server.register(fastifyCors);
server.register(formBodyPlugin);
server.register(fastifyRedis, params.fastify_redis);
if (params.fastify_rate_limit) {
server.register(fastifyRateLimit, params.fastify_rate_limit);
}
server.register(fastify_eosjs, params.fastify_eosjs);
server.register(fastify_rate_limit, params.fastify_rate_limit);
}
-21
View File
@@ -1,21 +0,0 @@
const fp = require('fastify-plugin');
const {Api} = require("eosjs");
const {ConnectionManager} = require('../../connections/manager');
const manager = new ConnectionManager();
module.exports = fp(async (fastify, options, next) => {
const rpc = manager.nodeosJsonRPC;
const chain_data = await rpc.get_info();
const api = new Api({
rpc,
signatureProvider: null,
chainId: chain_data.chain_id,
textDecoder: new TextDecoder(),
textEncoder: new TextEncoder(),
});
fastify.decorate('eosjs', {api, rpc});
next();
}, {
fastify: '>=2.0.0',
name: 'fastify-eosjs'
});
+4 -6
View File
@@ -1,19 +1,17 @@
import * as fp from 'fastify-plugin';
import {FastifyInstance} from "fastify";
import {FastifyInstance, FastifyPluginOptions} from "fastify";
import {Api} from "eosjs/dist";
import fp from "fastify-plugin";
export default fp(async (fastify: FastifyInstance, options, next) => {
export default fp(async (fastify: FastifyInstance, options: FastifyPluginOptions): Promise<void> => {
const rpc = fastify.manager.nodeosJsonRPC;
const chain_data = await rpc.get_info();
const api = new Api({
rpc,
signatureProvider: null,
chainId: chain_data.chain_id,
chainId: options.chain_id,
textDecoder: new TextDecoder(),
textEncoder: new TextEncoder(),
});
fastify.decorate('eosjs', {api, rpc});
next();
}, {
fastify: '>=2.0.0',
name: 'fastify-eosjs'
-30
View File
@@ -1,30 +0,0 @@
const fp = require('fastify-plugin');
const WebSocket = require('ws');
module.exports = fp((fastify, opts, next) => {
const wss = new WebSocket.Server({
server: fastify.server
});
const ws = new WebSocket('ws://127.0.0.1:7001', [], {
perMessageDeflate: false
});
ws.on('open', function open() {
console.log('router connected');
});
fastify.decorate('wss', wss);
fastify.decorate('ws', ws);
fastify.addHook('onClose', (fastify, done) => {
fastify.ws.close(done);
fastify.wss.close(done);
});
next();
}, {
fastify: '2.x',
name: 'fastify-websocket'
});
+105 -21
View File
@@ -1,56 +1,140 @@
import * as fastify_static from "fastify-static";
import {join} from "path";
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import {FastifyError, FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {createReadStream} from "fs";
import * as AutoLoad from "fastify-autoload";
import {addSharedSchemas, handleChainApiRedirect} from "./helpers/functions";
import autoLoad from '@fastify/autoload';
import got from "got";
function addRedirect(server: FastifyInstance, url: string, redirectTo: string) {
server.route({
url,
method: 'GET',
schema: {hide: true},
handler: async (request, reply) => {
schema: {
hide: true
},
handler: async (request: FastifyRequest, reply: FastifyReply) => {
reply.redirect(redirectTo);
}
});
}
function addRoute(server: FastifyInstance, handlersPath: string, prefix: string) {
server.register(AutoLoad, {
server.register(autoLoad, {
dir: join(__dirname, 'routes', handlersPath),
ignorePattern: /.*(handler|schema).js/,
dirNameRoutePrefix: false,
options: {prefix}
});
}
export function registerRoutes(server: FastifyInstance) {
// build internal map of routes
const routeSet = new Set<string>();
server.decorate('routeSet', routeSet);
const ignoreList = [
'/v2',
'/v2/history',
'/v2/state',
'/v1/chain/*',
'/v1/chain'
];
server.addHook('onRoute', opts => {
if (!ignoreList.includes(opts.url)) {
if (opts.url.startsWith('/v')) {
routeSet.add(opts.url);
}
}
});
// Register fastify api routes
addRoute(server, 'v2', '/v2');
addRoute(server, 'v2-history', '/v2/history');
addRoute(server, 'v2-state', '/v2/state');
addRoute(server, 'v2-stats', '/v2/stats');
// legacy routes
addRoute(server, 'v1-history', '/v1/history');
// addRoute(server,'v1-chain', '/v1/chain');
addRoute(server, 'v1-trace', '/v1/trace_api');
// Serve integrated explorer
if(server.manager.config.api.enable_explorer) {
server.register(fastify_static, {
root: join(__dirname, '..', 'hyperion-explorer', 'dist'),
redirect: true,
wildcard: true,
prefix: '/v2/explore'
});
}
addSharedSchemas(server);
// steam client lib
server.get('/stream-client.js', (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => {
const stream = createReadStream('./client_bundle.js');
reply.type('application/javascript').send(stream);
// chain api redirects
addRoute(server, 'v1-chain', '/v1/chain');
// other v1 requests
server.route({
url: '/v1/chain/*',
method: ["GET", "POST"],
schema: {
summary: "Wildcard chain api handler",
tags: ["chain"]
},
handler: async (request: FastifyRequest, reply: FastifyReply) => {
await handleChainApiRedirect(request, reply, server);
}
});
// /v1/node/get_supported_apis
server.route({
url: '/v1/node/get_supported_apis',
method: ["GET"],
schema: {
summary: "Get list of supported APIs",
tags: ["node"]
},
handler: async (request: FastifyRequest, reply: FastifyReply) => {
const data = await got.get(`${server.chain_api}/v1/node/get_supported_apis`).json() as any;
if (data.apis && data.apis.length > 0) {
const apiSet = new Set(server.routeSet);
data.apis.forEach((a) => apiSet.add(a));
reply.send({apis: [...apiSet]});
} else {
reply.send({apis: [...server.routeSet], error: 'nodeos did not send any data'});
}
}
});
// global onRequest hook to collect hourly statistics for each path on redis
server.addHook('onResponse', (request: FastifyRequest, reply: FastifyReply, done) => {
const path = request.url.split('?')[0].replace(/\/$/, '');
const now = new Date();
const ts = now.getTime();
// normalized time in hours
const factor = 1000 * 60 * 60;
const ts_hour_unix = Math.floor(ts / factor) * factor;
const key = `stats:${server.manager.chain}:H:${ts_hour_unix}`;
// console.log(reply.statusCode, key, new Date(ts_hour_unix));
server.redis.multi()
.incr(`stats:${server.manager.chain}:total:${reply.statusCode}:${path}`)
.zincrby(key, 1, `[${reply.statusCode}]${path}`)
.expire(key, 60 * 60 * 24 * 7)
.exec()
.catch(console.log);
done();
});
server.addHook('onError', (request: FastifyRequest, reply: FastifyReply, error: FastifyError, done) => {
console.log(`[${request.headers['x-real-ip'] || request.ip}] ${request.method} ${request.url} failed >> ${error.message}`);
done();
});
if (server.manager.config.features.streaming) {
// steam client lib
server.get(
'/stream-client.js',
{schema: {tags: ['internal']}},
(request: FastifyRequest, reply: FastifyReply) => {
const stream = createReadStream('./hyperion-stream-client.js');
reply.type('application/javascript').send(stream);
});
}
// Redirect routes to documentation
addRedirect(server, '/v2', '/v2/docs');
addRedirect(server, '/v2/history', '/v2/docs/index.html#/history');
addRedirect(server, '/v2/state', '/v2/docs/index.html#/state');
addRedirect(server, '/v1/chain', '/v2/docs/index.html#/chain');
addRedirect(server, '/explorer', '/v2/explore');
addRedirect(server, '/explore', '/v2/explore');
}
-227
View File
@@ -1,227 +0,0 @@
const async = require('async');
const {Serialize} = require('eosjs');
const {ConnectionManager} = require('../../connections/manager');
let abi, types;
let connected = false;
let tables = new Map();
const textEncoder = new TextEncoder();
const textDecoder = new TextDecoder();
const ws_opts = {compress: true, binary: true, mask: true, fin: true};
const maxMessagesInFlight = 1;
const blockReadingQueue = async.cargo(async.ensureAsync(processIncomingBlockArray), maxMessagesInFlight);
class BlockStore {
localBlocks;
pendingBlocks;
pendingRequests;
constructor() {
this.pendingRequests = [];
this.localBlocks = new Map();
this.pendingBlocks = new Map();
}
addBlock(num, data) {
if (this.pendingBlocks.has(num)) {
this.localBlocks.set(num, data);
this.pendingBlocks.get(num)['callback']();
this.pendingBlocks.delete(num);
}
}
requestRange(from_block, to_block, onBlock, onCompletion) {
// console.log(`Requesting blocks from ${from_block} to ${to_block}`);
const range = to_block - from_block;
// console.log(`Range size: ${range}`);
let requested = range;
for (let i = 0; i < range; i++) {
const blk_num = from_block + i;
// console.log('Looking on local cache for block ' + blk_num);
if (this.localBlocks.has(blk_num)) {
onBlock(this.localBlocks.get(blk_num), true);
requested--;
} else {
this.pendingBlocks.set(blk_num, {
callback: () => {
onBlock(this.localBlocks.get(blk_num), false);
}
});
}
}
if (requested > 0) {
requestBlockRange(from_block, to_block);
const checker = setInterval(() => {
if (this.pendingBlocks.size === 0) {
onCompletion();
clearInterval(checker);
}
}, 30);
} else {
onCompletion();
}
}
}
const blockStore = new BlockStore();
const baseRequest = {
max_messages_in_flight: maxMessagesInFlight,
have_positions: [],
irreversible_only: false,
fetch_block: true,
fetch_traces: true,
fetch_deltas: false
};
const manager = new ConnectionManager();
const shipClient = manager.shipClient;
startWS();
function createSerialBuffer(array_data) {
return new Serialize.SerialBuffer({textEncoder, textDecoder, array: array_data});
}
function serialize(type, value) {
const buffer = createSerialBuffer();
Serialize.getType(types, type).serialize(buffer, value);
return buffer.asUint8Array();
}
function deserialize(type, array) {
const buffer = new Serialize.SerialBuffer({textEncoder, textDecoder, array});
const sState = new Serialize.SerializerState({bytesAsUint8Array: true});
return Serialize.getType(types, type).deserialize(buffer, sState);
}
function processIncomingBlockArray(payload, cb) {
processIncomingBlocks(payload).then(() => {
cb();
}).catch((err) => {
console.log('FATAL ERROR READING BLOCKS', err);
process.exit(1);
})
}
function handleLostConnection() {
console.log('connection lost');
connected = false;
startWS();
}
function ackBlocks(num) {
shipClient.send(serialize('request',
['get_blocks_ack_request_v0', {num_messages: num}]
), ws_opts, (err) => {
if (err) console.warn(err);
});
}
async function processIncomingBlocks(block_array) {
if (abi) {
ackBlocks(block_array.length);
for (const block of block_array) {
try {
await onMessage(block);
} catch (e) {
console.log(e);
}
}
} else {
await onMessage(block_array[0]);
}
return true;
}
function processFirstABI(data) {
abi = JSON.parse(data);
types = Serialize.getTypesFromAbi(Serialize.createInitialTypes(), abi);
abi.tables.map(table => tables.set(table.name, table.type));
}
function deserializeType(obj) {
return deserialize(obj[0], obj[1], textEncoder, textDecoder, types);
}
function onMessage(data) {
if (abi) {
const res = deserialize('result', data)[1];
if (res['this_block']) {
const blk_num = res['this_block']['block_num'];
const block_data = {
block: null,
traces: [],
deltas: []
};
if (res.block && res.block.length) {
block_data.block = deserialize('signed_block', res.block, textEncoder, textDecoder, types);
if (block_data.block === null) {
console.log(res);
} else {
block_data.block.id = res['this_block']['block_id'];
block_data.block.block_num = blk_num;
block_data.block.transactions = [];
if (res['traces'] && res['traces'].length) {
const trx_traces = deserialize('transaction_trace[]', res['traces'], textEncoder, textDecoder, types).map(trace => trace[1]);
for (const trace of trx_traces) {
trace.partial = trace.partial[1];
trace.actions = [];
for (const action_trace of trace.action_traces) {
// console.log(action_trace);
// action_trace.receipt = action_trace.receipt[1];
const action = action_trace[1];
action.receipt = action.receipt[1];
action['act']['data'] = Buffer.from(action['act']['data']).toString('hex');
trace.actions.push(action);
}
delete trace.action_traces;
if (trace.actions[0]['act']['account'] === 'eosio' && trace.actions[0]['act']['name'] === 'onblock') {
// ingore onblock
} else {
block_data.block.transactions.push(trace);
}
}
}
}
}
// if (res['deltas'] && res['deltas'].length) {
// block_data.deltas = deserialize('table_delta[]', res['deltas'], textEncoder, textDecoder, types);
// }
blockStore.addBlock(blk_num, block_data.block);
}
} else {
processFirstABI(data);
return 1;
}
}
function send(req_data) {
shipClient.send(serialize('request', req_data, textEncoder, textDecoder, types));
}
function requestBlockRange(start, finish) {
const request = baseRequest;
request.start_block_num = start;
request.end_block_num = finish;
// console.log(request);
if (connected) {
send(['get_blocks_request_v0', request]);
} else {
startWS(() => {
send(['get_blocks_request_v0', request]);
});
}
}
function startWS(onConnect) {
if (onConnect) {
shipClient.connect(blockReadingQueue.push, handleLostConnection, null, onConnect);
} else {
shipClient.connect(blockReadingQueue.push, handleLostConnection);
}
connected = true;
}
module.exports = {blockStore};
-72
View File
@@ -1,72 +0,0 @@
const {getCacheByHash} = require("../../helpers/functions");
const {getAbiSnapshotSchema} = require("../../schemas");
const enable_caching = process.env.ENABLE_CACHING === 'true';
let cache_life = 30;
if (process.env.CACHE_LIFE) {
cache_life = parseInt(process.env.CACHE_LIFE);
}
async function getAbiSnapshot(fastify, request) {
const t0 = Date.now();
const {redis, elastic} = fastify;
const [cachedResponse, hash] = await getCacheByHash(redis, JSON.stringify(request.query));
if (cachedResponse) {
return JSON.parse(cachedResponse);
}
const response = {
query_time: 0,
block_num: null
};
const code = request.query.contract;
const block = request.query.block;
const should_fetch = request.query.fetch;
if (should_fetch) {
console.log(should_fetch);
}
const mustArray = [];
mustArray.push({"term": {"account": code}});
if (block) {
mustArray.push({"range": {"block": {"lte": parseInt(block)}}});
}
const results = await elastic.search({
index: process.env.CHAIN + '-abi',
size: 1,
body: {
query: {bool: {must: mustArray}},
sort: [{block: {order: "desc"}}]
}
});
if (results['body']['hits']['hits'].length > 0) {
if (should_fetch) {
response['abi'] = JSON.parse(results['body']['hits']['hits'][0]['_source']['abi']);
} else {
response['present'] = true;
}
response.block_num = results['body']['hits']['hits'][0]['_source']['block'];
} else {
response['present'] = false;
response['error'] = 'abi not found for ' + code + ' until block ' + block;
}
response['query_time'] = Date.now() - t0;
if (enable_caching) {
redis.set(hash, JSON.stringify(response), 'EX', cache_life);
}
return response;
}
module.exports = function (fastify, opts, next) {
fastify.get('/get_abi_snapshot', {
schema: getAbiSnapshotSchema.GET
}, async (request, reply) => {
reply.send(await getAbiSnapshot(fastify, request));
});
next()
};
-378
View File
@@ -1,378 +0,0 @@
const prettyjson = require("prettyjson");
const {getActionsSchema} = require("../../schemas");
const {getCacheByHash, mergeActionMeta} = require("../../helpers/functions");
const maxActions = 1000;
const route = '/get_actions';
const terms = [
"notified.keyword",
"act.authorization.actor"
];
const extendedActions = new Set([
"transfer",
"newaccount",
"updateauth",
"buyram",
"buyrambytes"
]);
const primaryTerms = [
"notified",
"block_num",
"global_sequence",
"producer",
"@timestamp",
"creator_action_ordinal",
"action_ordinal",
"cpu_usage_us",
"net_usage_words"
];
const enable_caching = process.env.ENABLE_CACHING === 'true';
let cache_life = 30;
if (process.env.CACHE_LIFE) {
cache_life = parseInt(process.env.CACHE_LIFE);
}
function getTrackTotalHits(query) {
let trackTotalHits = 15000;
if (query.track) {
if (query.track === 'true') {
trackTotalHits = true;
} else if (query.track === 'false') {
trackTotalHits = false;
} else {
trackTotalHits = parseInt(query.track, 10);
if (trackTotalHits !== trackTotalHits) {
throw new Error('failed to parse track param');
}
}
}
return trackTotalHits;
}
function addSortedBy(query, queryBody, sort_direction) {
if (query['sortedBy']) {
const opts = query['sortedBy'].split(":");
const sortedByObj = {};
sortedByObj[opts[0]] = opts[1];
queryBody['sort'] = sortedByObj;
} else {
queryBody['sort'] = {
"global_sequence": sort_direction
};
}
}
function processMultiVars(queryStruct, parts, field) {
const must = [];
const mustNot = [];
parts.forEach(part => {
if (part.startsWith("!")) {
mustNot.push(part.replace("!", ""));
} else {
must.push(part);
}
});
if (must.length > 1) {
queryStruct.bool.must.push({
bool: {
should: must.map(elem => {
const _q = {};
_q[field] = elem;
return {term: _q}
})
}
});
} else if (must.length === 1) {
const mustQuery = {};
mustQuery[field] = must[0];
queryStruct.bool.must.push({term: mustQuery});
}
if (mustNot.length > 1) {
queryStruct.bool.must_not.push({
bool: {
should: mustNot.map(elem => {
const _q = {};
_q[field] = elem;
return {term: _q}
})
}
});
} else if (mustNot.length === 1) {
const mustNotQuery = {};
mustNotQuery[field] = mustNot[0].replace("!", "");
queryStruct.bool.must_not.push({term: mustNotQuery});
}
}
function addRangeQuery(queryStruct, prop, pkey, query) {
const _termQuery = {};
const parts = query[prop].split("-");
_termQuery[pkey] = {
"gte": parts[0],
"lte": parts[1]
};
queryStruct.bool.must.push({range: _termQuery});
}
function applyTimeFilter(query, queryStruct) {
if (query['after'] || query['before']) {
let _lte = "now";
let _gte = 0;
if (query['before']) {
_lte = query['before'];
if (!_lte.endsWith("Z")) {
_lte += "Z";
}
}
if (query['after']) {
_gte = query['after'];
if (!_gte.endsWith("Z")) {
_gte += "Z";
}
}
if (!queryStruct.bool['filter']) {
queryStruct.bool['filter'] = [];
}
queryStruct.bool['filter'].push({
range: {
"@timestamp": {
"gte": _gte,
"lte": _lte
}
}
});
}
}
function applyGenericFilters(query, queryStruct) {
for (const prop in query) {
if (Object.prototype.hasOwnProperty.call(query, prop)) {
const pair = prop.split(".");
if (pair.length > 1 || primaryTerms.includes(pair[0])) {
let pkey;
if (pair.length > 1) {
pkey = extendedActions.has(pair[0]) ? "@" + prop : prop;
} else {
pkey = prop;
}
if (query[prop].indexOf("-") !== -1) {
addRangeQuery(queryStruct, prop, pkey, query);
} else {
const _termQuery = {};
const parts = query[prop].split(",");
if (parts.length > 1) {
processMultiVars(queryStruct, parts, prop);
} else if (parts.length === 1) {
const andParts = parts[0].split(" ");
if (andParts.length > 1) {
andParts.forEach(value => {
const _q = {};
console.log(value);
_q[pkey] = value;
queryStruct.bool.must.push({term: _q});
});
} else {
if (parts[0].startsWith("!")) {
_termQuery[pkey] = parts[0].replace("!", "");
queryStruct.bool.must_not.push({term: _termQuery});
} else {
_termQuery[pkey] = parts[0];
queryStruct.bool.must.push({term: _termQuery});
}
}
}
}
}
}
}
}
function makeShouldArray(query) {
const should_array = [];
for (const entry of terms) {
const tObj = {term: {}};
tObj.term[entry] = query.account;
should_array.push(tObj);
}
return should_array;
}
function applyCodeActionFilters(query, queryStruct) {
let filterObj = [];
if (query.filter) {
for (const filter of query.filter.split(',')) {
if (filter !== '*:*') {
const _arr = [];
const parts = filter.split(':');
if (parts.length === 2) {
[code, method] = parts;
if (code && code !== "*") {
_arr.push({'term': {'act.account': code}});
}
if (method && method !== "*") {
_arr.push({'term': {'act.name': method}});
}
}
if (_arr.length > 0) {
filterObj.push({bool: {must: _arr}});
}
}
}
if (filterObj.length > 0) {
queryStruct.bool['should'] = filterObj;
queryStruct.bool['minimum_should_match'] = 1;
}
}
}
function getSkipLimit(query) {
let skip, limit;
skip = parseInt(query.skip, 10);
if (skip < 0) {
throw new Error('invalid skip parameter');
}
limit = parseInt(query.limit, 10);
if (limit < 1) {
throw new Error('invalid limit parameter');
}
return {skip, limit};
}
function getSortDir(query) {
let sort_direction = 'desc';
if (query.sort) {
if (query.sort === 'asc' || query.sort === '1') {
sort_direction = 'asc';
} else if (query.sort === 'desc' || query.sort === '-1') {
sort_direction = 'desc'
} else {
throw new Error('invalid sort direction');
}
}
return sort_direction;
}
function applyAccountFilters(query, queryStruct) {
if (query.account) {
queryStruct.bool.must.push({"bool": {should: makeShouldArray(query)}});
}
}
async function getActions(fastify, request) {
const t0 = Date.now();
const {redis, elastic, eosjs} = fastify;
const query = request.query;
let cachedResponse, hash;
if (enable_caching) {
[cachedResponse, hash] = await getCacheByHash(redis, route + JSON.stringify(query));
if (cachedResponse) {
cachedResponse = JSON.parse(cachedResponse);
cachedResponse['query_time'] = Date.now() - t0;
cachedResponse['cached'] = true;
return cachedResponse;
}
}
const queryStruct = {
"bool": {
must: [],
must_not: [],
boost: 1.0
}
};
const {skip, limit} = getSkipLimit(query);
const sort_direction = getSortDir(query);
applyAccountFilters(query, queryStruct);
applyGenericFilters(query, queryStruct);
applyTimeFilter(query, queryStruct);
applyCodeActionFilters(query, queryStruct);
// allow precise counting of total hits
const trackTotalHits = getTrackTotalHits(query);
// Prepare query body
const query_body = {
"track_total_hits": trackTotalHits,
"query": queryStruct
};
// Include sorting
addSortedBy(query, query_body, sort_direction);
// console.log(prettyjson.render(queryStruct));
// Perform search
const pResults = await Promise.all([eosjs.rpc.get_info(), elastic['search']({
"index": process.env.CHAIN + '-action-*',
"from": skip || 0,
"size": (limit > maxActions ? maxActions : limit) || 10,
"body": query_body
})]);
const results = pResults[1]['body']['hits'];
const response = {
query_time: null,
cached: false,
lib: pResults[0].last_irreversible_block_num,
total: results['total']
};
if (query.simple) {
response['simple_actions'] = [];
} else {
response['actions'] = [];
}
if (results['hits'].length > 0) {
const actions = results['hits'];
for (let action of actions) {
action = action._source;
mergeActionMeta(action);
if (query.simple) {
response.simple_actions.push({
block: action['block_num'],
irreversible: action['block_num'] < pResults[0].last_irreversible_block_num,
timestamp: action['@timestamp'],
transaction_id: action['trx_id'],
actors: action['act']['authorization'].map(a => `${a.actor}@${a.permission}`).join(","),
notified: action['notified'].join(','),
contract: action['act']['account'],
action: action['act']['name'],
data: action['act']['data']
});
} else {
response.actions.push(action);
}
}
}
response['query_time'] = Date.now() - t0;
if (enable_caching) {
redis.set(hash, JSON.stringify(response), 'EX', cache_life);
}
return response;
}
module.exports = function (fastify, opts, next) {
fastify.get('/get_actions', {
schema: getActionsSchema.GET
}, async (request) => {
return await getActions(fastify, request);
});
next()
};
-49
View File
@@ -1,49 +0,0 @@
const {getBlocksSchema} = require("../../schemas");
const {blockStore} = require('../blockStore');
async function getBlocks(fastify, request, reply) {
const t0 = Date.now();
let from_block, to_block;
const {eosjs} = fastify;
if (request.query.from) {
from_block = parseInt(request.query.from);
if (request.query.to) {
to_block = parseInt(request.query.to);
} else {
to_block = from_block + 1;
}
} else {
const head = (await eosjs.rpc.get_info()).head_block_num;
from_block = head;
to_block = head + 1;
}
if (to_block > from_block) {
if (to_block - from_block > 20) {
reply.status(403).send('request over limit');
} else {
const response = {query_time: null, cached: false, blocks: []};
console.log('Local Block store size:', blockStore.localBlocks.size);
blockStore.requestRange(from_block, to_block, (block, cached) => {
if (cached) {
response.cached = true;
}
response.blocks.push(block);
}, () => {
response['query_time'] = Date.now() - t0;
reply.send(response);
});
}
} else {
reply.status(403).send('invalid block range');
}
}
module.exports = function (fastify, opts, next) {
fastify.get('/get_blocks', {
schema: getBlocksSchema.GET
}, async (request, reply) => {
await getBlocks(fastify, request, reply);
});
next();
};
-62
View File
@@ -1,62 +0,0 @@
const {getCreatedAccountsSchema} = require("../../schemas");
const {getCacheByHash} = require("../../helpers/functions");
const route = '/get_created_accounts';
async function getCreatedAccounts(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 results = await elastic['search']({
"index": process.env.CHAIN + '-action-*',
"body": {
"query": {
"bool": {
must: [
{term: {"act.authorization.actor": request.query.account.toLowerCase()}},
{term: {"act.name": "newaccount"}},
{term: {"act.account": "eosio"}}
]
}
},
sort: {
"global_sequence": "desc"
}
}
});
const response = {
"accounts": []
};
if (results['body']['hits']['hits'].length > 0) {
const actions = results['body']['hits']['hits'];
for (let action of actions) {
action = action._source;
const _tmp = {};
if (action['act']['data']['newact']) {
_tmp['name'] = action['act']['data']['newact'];
} else if (action['@newaccount']['newact']) {
_tmp['name'] = action['@newaccount']['newact'];
} else {
console.log(action);
}
_tmp['trx_id'] = action['trx_id'];
_tmp['timestamp'] = action['@timestamp'];
response.accounts.push(_tmp);
}
}
response['query_time'] = Date.now() - t0;
redis.set(hash, JSON.stringify(response), 'EX', 30);
return response;
}
module.exports = function (fastify, opts, next) {
fastify.get(route, {
schema: getCreatedAccountsSchema.GET
}, async (request) => {
return await getCreatedAccounts(fastify, request);
});
next()
};
-108
View File
@@ -1,108 +0,0 @@
const {getCreatorSchema} = require("../../schemas");
const {getCacheByHash} = require("../../helpers/functions");
const route = '/get_creator';
async function getActionByGS(client, gs) {
console.log(gs);
const results = await client['search']({
index: process.env.CHAIN + '-action-*',
body: {
query: {
bool: {
must: [
{term: {"global_sequence": gs}}
]
}
}
}
});
return results['body']['hits']['hits'][0]['_source'];
}
async function getCreator(fastify, request) {
const {redis, elastic, eosjs} = fastify;
const [cachedResponse, hash] = await getCacheByHash(redis, route + JSON.stringify(request.query) + 'v3');
if (cachedResponse) {
return cachedResponse;
}
const newact = request.query.account;
const response = {
account: newact,
creator: '',
timestamp: '',
block_num: 0,
trx_id: '',
};
let account_data = null;
try {
account_data = await eosjs.rpc.get_account(newact);
} catch (e) {
const err = new Error();
err.statusCode = 404;
err.message = 'account not found';
throw err;
}
response.timestamp = account_data['created'];
const queryBody = {
size: 1000,
query: {
bool: {
must: [
{term: {"act.name": "newaccount"}},
{term: {"act.account": "eosio"}},
{term: {"@timestamp": account_data['created']}}
]
}
}
};
const results = await elastic['search']({
"index": process.env.CHAIN + '-action-*',
"body": queryBody
});
for (const action of results['body']['hits']['hits']) {
const actData = action._source.act.data;
let valid = false;
response.block_num = action._source.block_num;
if (actData.newact === newact) {
response.creator = actData.creator;
response['trx_id'] = action._source['trx_id'];
valid = true;
} else {
if (action._source['@newaccount']) {
if (action._source['@newaccount']['newact'] === newact) {
response.creator = actData.creator;
response['trx_id'] = action._source['trx_id'];
valid = true;
}
}
}
// if (action._source.parent !== 0 && valid) {
//
// // Find indirect creator by global seq
// const creationAction = await getActionByGS(elastic, action._source.parent);
//
// if (creationAction.act.name === 'transfer') {
// response['indirect_creator'] = creationAction['@transfer']['from'];
// response['trx_id'] = creationAction['trx_id'];
// } else {
// response['indirect_creator'] = creationAction.act.authorization[0].actor;
// response['trx_id'] = creationAction['trx_id'];
// }
// }
}
redis.set(hash, JSON.stringify(response), 'EX', 600);
return response;
}
module.exports = function (fastify, opts, next) {
fastify.get(route, {
schema: getCreatorSchema.GET
}, async (request) => {
return await getCreator(fastify, request);
});
next()
};
-99
View File
@@ -1,99 +0,0 @@
const {getDeltasSchema} = require("../../schemas");
const {getCacheByHash} = require("../../helpers/functions");
const route = '/get_deltas';
const maxDeltas = 200;
async function getDeltas(fastify, request) {
const t0 = Date.now();
console.log(request.query);
const {redis, elastic} = fastify;
const [cachedResponse, hash] = await getCacheByHash(redis, route + JSON.stringify(request.query) + 'v2');
// if (cachedResponse) {
// return cachedResponse;
// }
let skip, limit;
let sort_direction = 'desc';
const mustArray = [];
for (const param in request.query) {
if (Object.prototype.hasOwnProperty.call(request.query, param)) {
console.log(param, request.query[param]);
const value = request.query[param];
switch (param) {
case 'limit': {
limit = parseInt(value, 10);
if (limit < 1) {
return 'invalid limit parameter';
}
break;
}
case 'skip': {
skip = parseInt(value, 10);
if (skip < 0) {
return 'invalid skip parameter';
}
break;
}
case 'sort': {
if (value === 'asc' || value === '1') {
sort_direction = 'asc';
} else if (value === 'desc' || value === '-1') {
sort_direction = 'desc'
} else {
return 'invalid sort direction';
}
break;
}
default: {
const values = request.query[param].split(",");
const terms = {};
terms[param] = values;
const shouldArray = {terms: terms};
const boolStruct = {bool: {should: [shouldArray]}};
mustArray.push(boolStruct);
break;
}
}
}
}
let prefix = process.env.CHAIN;
if (process.env.CHAIN === 'mainnet') {
prefix = 'eos';
}
const results = await elastic.search({
"index": prefix + '-delta-*',
"from": skip || 0,
"size": (limit > maxDeltas ? maxDeltas : limit) || 10,
"body": {
query: {bool: {must: mustArray}},
sort: {
"block_num": sort_direction
}
}
});
const response = {
query_time: null,
total: results['body']['hits']['total'],
deltas: results['body']['hits']['hits'].map((d) => {
delete d._source.present;
return d._source;
})
};
response['query_time'] = Date.now() - t0;
// redis.set(hash, JSON.stringify(response));
return response;
}
module.exports = function (fastify, opts, next) {
fastify.get(route, {
schema: getDeltasSchema.GET
}, async (request) => {
return await getDeltas(fastify, request);
});
next()
};
-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()
};
-55
View File
@@ -1,55 +0,0 @@
const {getTransactionSchema} = require("../../schemas");
const _ = require('lodash');
const {getCacheByHash} = require("../../helpers/functions");
async function getTransaction(fastify, request) {
const {redis, elastic, eosjs} = fastify;
const [cachedResponse, hash] = await getCacheByHash(redis, JSON.stringify(request.query));
if (cachedResponse) {
return cachedResponse;
}
const pResults = await Promise.all([eosjs.rpc.get_info(), elastic['search']({
"index": process.env.CHAIN + '-action-*',
"body": {
"query": {
"bool": {
must: [
{term: {"trx_id": request.query.id.toLowerCase()}}
]
}
},
"sort": {
"global_sequence": "asc"
}
}
})]);
const results = pResults[1];
const response = {
"trx_id": request.query.id,
"lib": pResults[0].last_irreversible_block_num,
"actions": []
};
const hits = results['body']['hits']['hits'];
if (hits.length > 0) {
for (let action of hits) {
action = action._source;
const name = action.act.name;
if (action['@' + name]) {
action['act']['data'] = _.merge(action['@' + name], action['act']['data']);
delete action['@' + name];
}
response.actions.push(action);
}
}
redis.set(hash, JSON.stringify(response), 'EX', 30);
return response;
}
module.exports = function (fastify, opts, next) {
fastify.get('/get_transaction', {
schema: getTransactionSchema.GET
}, async (request, reply) => {
reply.send(await getTransaction(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()
};
-65
View File
@@ -1,65 +0,0 @@
const {getAccountSchema} = require("../../schemas");
const {getCacheByHash} = require("../../helpers/functions");
const route = '/get_account';
const got = require('got');
const localApi = `http://${process.env.SERVER_ADDR}:${process.env.SERVER_PORT}/v2`;
const getTokensApi = localApi + '/state/get_tokens';
const getActionsApi = localApi + '/history/get_actions';
const enable_caching = process.env.ENABLE_CACHING === 'true';
let cache_life = 30;
if (process.env.CACHE_LIFE) {
cache_life = parseInt(process.env.CACHE_LIFE);
}
async function getAccount(fastify, request) {
const t0 = Date.now();
const {redis, elastic, eosjs} = fastify;
// let cachedResponse, hash;
// if(enable_caching) {
// [cachedResponse, hash] = await getCacheByHash(redis, route + JSON.stringify(request.query));
// if (cachedResponse) {
// cachedResponse = JSON.parse(cachedResponse);
// cachedResponse['query_time'] = Date.now() - t0;
// cachedResponse['cached'] = true;
// return cachedResponse;
// }
// }
const response = {
query_time: null,
cached: false,
account: null,
actions: null,
tokens: null,
links: null
};
const account = request.query.account;
const reqQueue = [];
reqQueue.push(eosjs.rpc.get_account(account));
// fetch recent actions
reqQueue.push(got.get(`${getActionsApi}?account=${account}&limit=10`));
// fetch account tokens
reqQueue.push(got.get(`${getTokensApi}?account=${account}`));
const results = await Promise.all(reqQueue);
response.account = results[0];
response.actions = JSON.parse(results[1].body).actions;
response.tokens = JSON.parse(results[2].body).tokens;
response['query_time'] = Date.now() - t0;
return response;
}
module.exports = function (fastify, opts, next) {
fastify.get(route, {
schema: getAccountSchema.GET
}, async (request) => {
return await getAccount(fastify, request);
});
next()
};
-97
View File
@@ -1,97 +0,0 @@
const {getKeyAccountsSchema} = require("../../schemas");
const {getCacheByHash} = require("../../helpers/functions");
const numeric = require('eosjs/dist/eosjs-numeric');
function invalidKey() {
const err = new Error();
err.statusCode = 400;
err.message = 'invalid public key';
throw err;
}
async function getKeyAccounts(fastify, public_Key) {
const {redis, elastic} = fastify;
let publicKey;
if (public_Key.startsWith("PUB_")) {
publicKey = public_Key;
} else if (public_Key.startsWith("EOS")) {
try {
publicKey = numeric.convertLegacyPublicKey(public_Key);
} catch (e) {
console.log(e);
invalidKey();
}
} else {
invalidKey();
}
const [cachedResponse, hash] = await getCacheByHash(redis, JSON.stringify(publicKey));
if (cachedResponse) {
return cachedResponse;
}
const _body = {
query: {
bool: {
should: [
{term: {"@updateauth.auth.keys.key.keyword": publicKey}},
{term: {"@newaccount.active.keys.key.keyword": publicKey}},
{term: {"@newaccount.owner.keys.key.keyword": publicKey}}
],
minimum_should_match: 1
}
},
sort: [{"global_sequence": {"order": "desc"}}]
};
const results = await elastic.search({
index: process.env.CHAIN + '-action-*',
body: _body
});
const response = {
account_names: []
};
if (results['body']['hits']['hits'].length > 0) {
response.account_names = results['body']['hits']['hits'].map((v) => {
if (v._source.act.name === 'newaccount') {
if (v._source['@newaccount'].newact) {
return v._source['@newaccount'].newact;
} else if (v._source.act.data.newact) {
return v._source.act.data.newact;
} else {
return null;
}
} else if (v._source.act.name === 'updateauth') {
return v._source.act.data.account;
} else {
return null;
}
});
}
if (response.account_names.length > 0) {
response.account_names = [...(new Set(response.account_names))];
redis.set(hash, JSON.stringify(response), 'EX', 30);
return response;
} else {
const err = new Error();
err.statusCode = 404;
err.message = 'no accounts associated with ' + public_Key;
throw err;
}
}
module.exports = function (fastify, opts, next) {
fastify.get('/get_key_accounts', {
schema: getKeyAccountsSchema.GET
}, async (request) => {
return getKeyAccounts(fastify, request.query.public_key);
});
fastify.post('/get_key_accounts', {
schema: getKeyAccountsSchema.POST
}, async (request) => {
return getKeyAccounts(fastify, request.body.public_key);
});
next();
};
-146
View File
@@ -1,146 +0,0 @@
const {getProposalsSchema} = require("../../schemas");
const {getCacheByHash} = require("../../helpers/functions");
const route = '/get_proposals';
const maxActions = 1000;
const enable_caching = process.env.ENABLE_CACHING === 'true';
let cache_life = 30;
if (process.env.CACHE_LIFE) {
cache_life = parseInt(process.env.CACHE_LIFE);
}
function getTrackOpt(request) {
let trackTotalHits = 10000;
if (request.query.track) {
if (request.query.track === 'true') {
trackTotalHits = true;
} else if (request.query.track === 'false') {
trackTotalHits = false;
} else {
trackTotalHits = parseInt(request.query.track, 10);
if (trackTotalHits !== trackTotalHits) {
throw new Error('failed to parse track param');
}
}
}
return trackTotalHits;
}
async function getProposals(fastify, request) {
const t0 = Date.now();
const {redis, elastic, eosjs} = fastify;
let cachedResponse, hash;
if (enable_caching) {
[cachedResponse, hash] = await getCacheByHash(redis, route + JSON.stringify(request.query));
if (cachedResponse) {
cachedResponse = JSON.parse(cachedResponse);
cachedResponse['query_time'] = Date.now() - t0;
cachedResponse['cached'] = true;
return cachedResponse;
}
}
// Pagination
let skip, limit;
skip = parseInt(request.query.skip, 10);
if (skip < 0) {
return 'invalid skip parameter';
}
limit = parseInt(request.query.limit, 10);
if (limit < 1) {
return 'invalid limit parameter';
}
let queryStruct = {
"bool": {
"must": []
}
};
// Filter by account
if (request.query.account) {
queryStruct.bool.must.push({
"bool": {
"should": [
{"term": {"requested_approvals.actor": request.query.account}},
{"term": {"provided_approvals.actor": request.query.account}}
]
}
});
}
// Filter by proposer account
if (request.query.proposer) {
queryStruct.bool.must.push({"term": {"proposer": request.query.proposer}});
}
// Filter by proposal name
if (request.query.proposal) {
queryStruct.bool.must.push({"term": {"proposal_name": request.query.proposal}});
}
// Filter by execution status
if (typeof request.query.executed !== 'undefined') {
queryStruct.bool.must.push({"term": {"executed": request.query.executed}});
}
// Filter by requested actors
if (request.query.requested) {
queryStruct.bool.must.push({"term": {"requested_approvals.actor": request.query.requested}});
}
// Filter by provided actors
if (request.query.provided) {
queryStruct.bool.must.push({"term": {"provided_approvals.actor": request.query.provided}});
}
// If no filter switch to full match
if (queryStruct.bool.must.length === 0) {
queryStruct = {
"match_all": {}
};
}
console.log(JSON.stringify(queryStruct));
const results = await elastic.search({
"index": process.env.CHAIN + '-table-proposals-*',
"from": skip || 0,
"size": (limit > maxActions ? maxActions : limit) || 10,
"body": {
"track_total_hits": getTrackOpt(request),
"query": queryStruct,
"sort": [{"block_num": "desc"}]
}
});
const response = {
query_time: null,
cached: false,
total: results['body']['hits']['total'],
proposals: []
};
const hits = results['body']['hits']['hits'];
for (const hit of hits) {
const prop = hit._source;
response.proposals.push(prop);
}
response['query_time'] = Date.now() - t0;
if (enable_caching) {
redis.set(hash, JSON.stringify(response), 'EX', cache_life);
}
return response;
}
module.exports = function (fastify, opts, next) {
fastify.get(route, {
schema: getProposalsSchema.GET
}, async (request) => {
return await getProposals(fastify, request);
});
next();
};
-94
View File
@@ -1,94 +0,0 @@
const {getTokensSchema} = require("../../schemas");
const {getCacheByHash} = require("../../helpers/functions");
const route = '/get_tokens';
const enable_caching = process.env.ENABLE_CACHING === 'true';
let cache_life = 30;
if(process.env.CACHE_LIFE) {
cache_life = parseInt(process.env.CACHE_LIFE);
}
async function getTokens(fastify, request) {
const t0 = Date.now();
const {redis, elastic, eosjs} = fastify;
let cachedResponse, hash;
if(enable_caching) {
[cachedResponse, hash] = await getCacheByHash(redis, route + JSON.stringify(request.query));
if (cachedResponse) {
cachedResponse = JSON.parse(cachedResponse);
cachedResponse['query_time'] = Date.now() - t0;
cachedResponse['cached'] = true;
return cachedResponse;
}
}
const response = {
query_time: null,
cached: false,
'account': request.query.account,
'tokens': []
};
const results = await elastic.search({
"index": process.env.CHAIN + '-action-*',
"body": {
size: 0,
query: {
bool: {
// must_not: {term: {"act.account": "eosio.token"}},
filter: [
{term: {"notified": request.query.account}},
{terms: {"act.name": ["transfer", "issue"]}}
]
}
},
aggs: {
tokens: {
terms: {
field: "act.account",
size: 1000
}
}
}
}
});
for (const bucket of results['body']['aggregations']['tokens']['buckets']) {
let token_data;
try {
token_data = await eosjs.rpc.get_currency_balance(bucket['key'], request.query.account);
} catch (e) {
console.log(`get_currency_balance error - contract:${bucket['key']} - account:${request.query.account}`);
continue;
}
for (const entry of token_data) {
let precision = 0;
const [amount, symbol] = entry.split(" ");
const amount_arr = amount.split(".");
if (amount_arr.length === 2) {
precision = amount_arr[1].length;
}
response.tokens.push({
symbol: symbol,
precision: precision,
amount: parseFloat(amount),
contract: bucket['key']
});
}
}
response['query_time'] = Date.now() - t0;
if(enable_caching) {
redis.set(hash, JSON.stringify(response), 'EX', cache_life);
}
return response;
}
module.exports = function (fastify, opts, next) {
fastify.get(route, {
schema: getTokensSchema.GET
}, async (request) => {
return await getTokens(fastify, request);
});
next()
};
-90
View File
@@ -1,90 +0,0 @@
const {getVotersSchema} = require("../../schemas");
const {getCacheByHash} = require("../../helpers/functions");
const route = '/get_voters';
const maxActions = 1000;
async function getVoters(fastify, request) {
const t0 = Date.now();
const {redis, elastic} = fastify;
const [cachedResponse, hash] = await getCacheByHash(redis, route + JSON.stringify(request.query));
if (cachedResponse) {
return cachedResponse;
}
let skip, limit;
skip = parseInt(request.query.skip, 10);
if (skip < 0) {
return 'invalid skip parameter';
}
limit = parseInt(request.query.limit, 10);
if (limit < 1) {
return 'invalid limit parameter';
}
const response = {
query_time: null,
voter_count: 0,
'voters': []
};
let queryStruct = {
"bool": {
"must": []
}
};
if (request.query.producer) {
for (const bp of request.query.producer.split(",")) {
queryStruct.bool.must.push({"term": {"producers": bp}});
}
}
if (request.query.proxy === 'true') {
queryStruct.bool.must.push({"term": {"is_proxy": true}});
}
if (queryStruct.bool.must.length === 0) {
queryStruct = {
"match_all": {}
};
}
let prefix = process.env.CHAIN;
if (process.env.CHAIN === 'mainnet') {
prefix = 'eos';
}
const results = await elastic.search({
"index": prefix + '-table-voters-*',
"from": skip || 0,
"size": (limit > maxActions ? maxActions : limit) || 10,
"body": {
"query": queryStruct,
"sort": [{"last_vote_weight": "desc"}]
}
});
const hits = results['body']['hits']['hits'];
for (const hit of hits) {
const voter = hit._source;
response.voters.push({
account: voter.voter,
weight: voter.last_vote_weight,
last_vote: voter.block_num
});
}
response.voter_count = results['body']['hits']['total']['value'];
response['query_time'] = Date.now() - t0;
redis.set(hash, JSON.stringify(response), 'EX', 30);
return response;
}
module.exports = function (fastify, opts, next) {
fastify.get(route, {
schema: getVotersSchema.GET
}, async (request) => {
return await getVoters(fastify, request);
});
next();
};
@@ -0,0 +1,17 @@
import {FastifyInstance} from "fastify";
import {addChainApiRoute, getRouteName} from "../../../helpers/functions";
export default function (fastify: FastifyInstance, opts: any, next) {
addChainApiRoute(
fastify,
getRouteName(__filename),
'Returns an object containing rows from the specified table.',
{
"code": {$ref: 'AccountName#'},
"action": {$ref: 'AccountName#'},
"binargs": {"type": "string"}
},
["code", "action", "binargs"]
);
next();
}
@@ -0,0 +1,20 @@
import {FastifyInstance} from "fastify";
import {addChainApiRoute, getRouteName} from "../../../helpers/functions";
export default function (fastify: FastifyInstance, opts: any, next) {
addChainApiRoute(
fastify,
getRouteName(__filename),
'Convert JSON object to binary',
{
"code": {$ref: 'AccountName#'},
"action": {$ref: 'AccountName#'},
"args": {
type: 'object',
additionalProperties: true
}
},
["code", "action", "args"]
);
next();
}
+13
View File
@@ -0,0 +1,13 @@
import {FastifyInstance} from "fastify";
import {addChainApiRoute, getRouteName} from "../../../helpers/functions";
export default function (fastify: FastifyInstance, opts: any, next) {
addChainApiRoute(
fastify,
getRouteName(__filename),
'Retrieves the ABI for a contract based on its account name',
{"account_name": {$ref: 'AccountName#'}},
["account_name"]
);
next();
}
+15
View File
@@ -0,0 +1,15 @@
import {FastifyInstance} from "fastify";
import {addChainApiRoute, getRouteName} from "../../../helpers/functions";
export default function (fastify: FastifyInstance, opts: any, next) {
addChainApiRoute(
fastify,
getRouteName(__filename),
'Returns an object containing various details about a specific account on the blockchain.',
{
"account_name": {$ref: 'AccountName#'}
},
["account_name"]
);
next();
}
@@ -0,0 +1,33 @@
import {FastifyInstance} from "fastify";
import {addChainApiRoute, getRouteName} from "../../../helpers/functions";
export default function (fastify: FastifyInstance, opts: any, next) {
addChainApiRoute(
fastify,
getRouteName(__filename),
'Retreives the activated protocol features for producer node',
{
"lower_bound": {
"type": "integer",
"description": "Lower bound"
},
"upper_bound": {
"type": "integer",
"description": "Upper bound"
},
"limit": {
"type": "integer",
"description": "The limit, default is 10"
},
"search_by_block_num": {
"type": "boolean",
"description": "Flag to indicate it is has to search by block number"
},
"reverse": {
"type": "boolean",
"description": "Flag to indicate it has to search in reverse"
}
}
);
next();
}
-134
View File
@@ -1,134 +0,0 @@
const schema = {
description: 'Returns an object containing various details about a specific block on the blockchain.',
summary: 'Returns an object containing various details about a specific block on the blockchain.',
tags: ['state'],
body: {
type: ['object', 'string'],
properties: {
"block_num_or_id": {
description: 'Provide a block number or a block id',
type: 'string'
},
},
required: ["block_num_or_id"]
}
};
const route = '/get_block';
const {blockStore} = require('../blockStore');
const {getCacheByHash} = require("../../helpers/functions");
let last_get_info = 0;
let chain_info;
async function getInfo(eosjs) {
if (Date.now() - last_get_info > 1000) {
chain_info = await eosjs.rpc.get_info();
last_get_info = Date.now();
}
}
async function processBlockData(block, eosjs) {
// Calculate total 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'];
});
block['total_cpu_usage_us'] = total_cpu;
block['total_net_usage_words'] = total_net;
// check irreversibility
await getInfo(eosjs);
block['last_irreversible_block_num'] = chain_info.last_irreversible_block_num;
block['irreversible'] = block['block_num'] <= block['last_irreversible_block_num'];
return block;
}
async function getBlock(fastify, request, reply) {
const {eosjs, redis} = fastify;
let block_num_or_id;
try {
if (typeof request.body === 'object') {
block_num_or_id = request.body['block_num_or_id'];
} else {
block_num_or_id = JSON.parse(request.body)['block_num_or_id'];
}
} catch (e) {
console.log(request.body);
console.log(e);
}
if (parseInt(block_num_or_id) === 0) {
await getInfo(eosjs);
block_num_or_id = chain_info.head_block_num;
}
if (block_num_or_id) {
let cachedResponse, hash;
const key = route + '_' + block_num_or_id;
[cachedResponse, hash] = await getCacheByHash(redis, key);
if (cachedResponse) {
cachedResponse = JSON.parse(cachedResponse);
if (cachedResponse['irreversible']) {
reply.send(cachedResponse);
return;
}
}
try {
let blockdata = await eosjs.rpc.get_block(block_num_or_id);
blockdata = await processBlockData(blockdata, eosjs);
const blockstring = JSON.stringify(blockdata);
if (blockdata['irreversible']) {
redis.set(hash, blockstring, 'EX', 86400);
} else {
redis.set(hash, blockstring, 'EX', 10);
}
reply.send(blockdata);
} catch (e) {
reply.code(400).send({
"code": 400,
"message": "Unknown Block",
"error": {
"code": 3100002,
"name": "unknown_block_exception",
"what": "Unknown block",
"details": [
{
"message": "Could not find block: " + block_num_or_id,
"file": "chain_plugin.cpp",
"line_number": 1852,
"method": "get_block"
}
]
}
});
}
} else {
reply.code(400).send({});
}
// TODO: fallback to state history
// let from_block;
// if (request.body['block_num_or_id']) {
// from_block = parseInt(request.body['block_num_or_id']);
// let response;
// console.log('Local Block store size:', blockStore.localBlocks.size);
// blockStore.requestRange(from_block, from_block + 1, (block) => {
// response = block;
// }, () => {
// reply.send(response);
// });
// }
}
module.exports = function (fastify, opts, next) {
fastify.post(route, {schema}, async (request, reply) => {
await getBlock(fastify, request, reply);
});
next();
};
+18
View File
@@ -0,0 +1,18 @@
import {FastifyInstance} from "fastify";
import {addChainApiRoute, getRouteName} from "../../../helpers/functions";
export default function (fastify: FastifyInstance, opts: any, next) {
addChainApiRoute(
fastify,
getRouteName(__filename),
'Returns an object containing various details about a specific block on the blockchain.',
{
"block_num_or_id": {
description: "Provide a `block number` or a `block id`",
type: 'string'
}
},
["block_num_or_id"]
);
next();
}
@@ -0,0 +1,18 @@
import {FastifyInstance} from "fastify";
import {addChainApiRoute, getRouteName} from "../../../helpers/functions";
export default function (fastify: FastifyInstance, opts: any, next) {
addChainApiRoute(
fastify,
getRouteName(__filename),
'Retrieves the block header state',
{
"block_num_or_id": {
"type": "string",
"description": "Provide a block_number or a block_id"
}
},
["block_num_or_id"]
);
next();
}
+20
View File
@@ -0,0 +1,20 @@
import {FastifyInstance} from "fastify";
import {addChainApiRoute, getRouteName} from "../../../helpers/functions";
export default function (fastify: FastifyInstance, opts: any, next) {
addChainApiRoute(
fastify,
getRouteName(__filename),
'Retrieves contract code',
{
"account_name": {$ref: 'AccountName#'},
"code_as_wasm": {
"type": "integer",
"default": 1,
"description": "This must be 1 (true)"
}
},
["account_name", "code_as_wasm"]
);
next();
}
@@ -0,0 +1,17 @@
import {FastifyInstance} from "fastify";
import {addChainApiRoute, getRouteName} from "../../../helpers/functions";
export default function (fastify: FastifyInstance, opts: any, next) {
addChainApiRoute(
fastify,
getRouteName(__filename),
'Retrieves the current balance',
{
"code": {$ref: 'AccountName#'},
"account": {$ref: 'AccountName#'},
"symbol": {$ref: 'Symbol#'}
},
["code", "account", "symbol"]
);
next();
}
@@ -0,0 +1,19 @@
import {FastifyInstance} from "fastify";
import {addChainApiRoute, getRouteName} from "../../../helpers/functions";
export default function (fastify: FastifyInstance, opts: any, next) {
addChainApiRoute(
fastify,
getRouteName(__filename),
'Retrieves currency stats',
{
"code": {$ref: 'AccountName#'},
"symbol": {
description: 'token symbol',
type: 'string',
}
},
["code", "symbol"]
);
next();
}
+11
View File
@@ -0,0 +1,11 @@
import {FastifyInstance} from "fastify";
import {addChainApiRoute, getRouteName} from "../../../helpers/functions";
export default function (fastify: FastifyInstance, opts: any, next) {
addChainApiRoute(
fastify,
getRouteName(__filename),
'Returns an object containing various details about the blockchain.'
);
next();
}
@@ -0,0 +1,25 @@
import {FastifyInstance} from "fastify";
import {addChainApiRoute, getRouteName} from "../../../helpers/functions";
export default function (fastify: FastifyInstance, opts: any, next) {
addChainApiRoute(
fastify,
getRouteName(__filename),
'Retrieves producers list',
{
"limit": {
"type": "string",
"description": "total number of producers to retrieve"
},
"lower_bound": {
"type": "string",
"description": "In conjunction with limit can be used to paginate through the results. For example, limit=10 and lower_bound=10 would be page 2"
},
"json": {
"type": "boolean",
"description": "return result in JSON format"
}
}
);
next();
}
+15
View File
@@ -0,0 +1,15 @@
import {FastifyInstance} from "fastify";
import {addChainApiRoute, getRouteName} from "../../../helpers/functions";
export default function (fastify: FastifyInstance, opts: any, next) {
addChainApiRoute(
fastify,
getRouteName(__filename),
'Retrieves raw ABI for a contract based on account name',
{
"account_name": {$ref: 'AccountName#'}
},
["account_name"]
);
next();
}
@@ -0,0 +1,15 @@
import {FastifyInstance} from "fastify";
import {addChainApiRoute, getRouteName} from "../../../helpers/functions";
export default function (fastify: FastifyInstance, opts: any, next) {
addChainApiRoute(
fastify,
getRouteName(__filename),
'Retrieves raw code and ABI for a contract based on account name',
{
"account_name": {$ref: 'AccountName#'}
},
["account_name"]
);
next();
}
@@ -0,0 +1,27 @@
import {FastifyInstance} from "fastify";
import {addChainApiRoute, getRouteName} from "../../../helpers/functions";
export default function (fastify: FastifyInstance, opts: any, next) {
addChainApiRoute(
fastify,
getRouteName(__filename),
'Retrieves the scheduled transaction',
{
"lower_bound": {
"type": "string",
"description": "Date/time string in the format YYYY-MM-DDTHH:MM:SS.sss",
"pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3}$",
"title": "DateTimeSeconds"
},
"limit": {
"description": "The maximum number of transactions to return",
"type": "integer"
},
"json": {
"description": "true/false whether the packed transaction is converted to json",
"type": "boolean"
}
}
);
next();
}
@@ -0,0 +1,38 @@
import {FastifyInstance} from "fastify";
import {addChainApiRoute, getRouteName} from "../../../helpers/functions";
export default function (fastify: FastifyInstance, opts: any, next) {
addChainApiRoute(
fastify,
getRouteName(__filename),
'Retrieves table scope',
{
"code": {
"type": "string",
"description": "`name` of the contract to return table data for"
},
"table": {
"type": "string",
"description": "Filter results by table"
},
"lower_bound": {
"type": "string",
"description": "Filters results to return the first element that is not less than provided value in set"
},
"upper_bound": {
"type": "string",
"description": "Filters results to return the first element that is greater than provided value in set"
},
"limit": {
"type": "integer",
"description": "Limit number of results returned."
},
"reverse": {
"type": "boolean",
"description": "Reverse the order of returned results"
}
},
["code"]
);
next();
}
@@ -0,0 +1,43 @@
import {FastifyInstance} from "fastify";
import {addChainApiRoute, getRouteName} from "../../../helpers/functions";
export default function (fastify: FastifyInstance, opts: any, next) {
addChainApiRoute(
fastify,
getRouteName(__filename),
'Returns an object containing rows from the specified table.',
{
"code": {
"type": "string",
"description": "The name of the smart contract that controls the provided table"
},
"table": {
"type": "string",
"description": "The name of the table to query"
},
"scope": {
"type": "string",
"description": "The account to which this data belongs"
},
"index_position": {
"type": "string",
"description": "Position of the index used, accepted parameters `primary`, `secondary`, `tertiary`, `fourth`, `fifth`, `sixth`, `seventh`, `eighth`, `ninth` , `tenth`"
},
"key_type": {
"type": "string",
"description": "Type of key specified by index_position (for example - `uint64_t` or `name`)"
},
"encode_type": {
"type": "string"
},
"upper_bound": {
"type": "string"
},
"lower_bound": {
"type": "string"
}
},
["code", "table", "scope"]
);
next();
}
@@ -0,0 +1,39 @@
import {FastifyInstance} from "fastify";
import {addApiRoute, chainApiHandler, getRouteName} from "../../../helpers/functions";
export default function (fastify: FastifyInstance, opts: any, next) {
addApiRoute(
fastify,
'POST',
getRouteName(__filename),
chainApiHandler,
{
description: "This method expects a transaction in JSON format and will attempt to apply it to the blockchain.",
summary: "This method expects a transaction in JSON format and will attempt to apply it to the blockchain.",
tags: ['chain'],
body: {
type: ['object', 'string'],
properties: {
signatures: {
"type": "array",
"description": "array of signatures required to authorize transaction",
"items": {$ref: 'Signature#'}
},
compression: {
"type": "boolean",
"description": "Compression used, usually false"
},
packed_context_free_data: {
"type": "string",
"description": "json to hex"
},
packed_trx: {
"type": "string",
"description": "Transaction object json to hex"
}
}
}
}
);
next();
}
@@ -0,0 +1,55 @@
import {FastifyInstance} from "fastify";
import {addApiRoute, chainApiHandler, getRouteName} from "../../../helpers/functions";
export default function (fastify: FastifyInstance, opts: any, next) {
addApiRoute(
fastify,
'POST',
getRouteName(__filename),
chainApiHandler,
{
description: "This method expects a transaction in JSON format and will attempt to apply it to the blockchain.",
summary: "This method expects a transaction in JSON format and will attempt to apply it to the blockchain.",
tags: ['chain'],
body: {
type: ['array', 'object', 'string'],
items: {
type: ['object', 'string'],
additionalProperties: false,
minProperties: 8,
required: [
"expiration",
"ref_block_num",
"ref_block_prefix",
"max_net_usage_words",
"max_cpu_usage_ms",
"delay_sec",
"context_free_actions",
"actions"
],
properties: {
"expiration": {$ref: 'Expiration#'},
"ref_block_num": {"type": "integer"},
"ref_block_prefix": {"type": "integer"},
"max_net_usage_words": {$ref: 'WholeNumber#'},
"max_cpu_usage_ms": {$ref: 'WholeNumber#'},
"delay_sec": {"type": "integer"},
"context_free_actions": {
"type": "array",
"items": {$ref: 'ActionItems#'}
},
"actions": {
"type": "array",
"items": {$ref: 'ActionItems#'}
},
"transaction_extensions": {
"type": "array",
"items": {$ref: 'BlockExtensions#'}
}
},
}
}
}
);
next();
}
@@ -0,0 +1,39 @@
import {FastifyInstance} from "fastify";
import {addApiRoute, chainApiHandler, getRouteName} from "../../../helpers/functions";
export default function (fastify: FastifyInstance, opts: any, next) {
addApiRoute(
fastify,
'POST',
getRouteName(__filename),
chainApiHandler,
{
description: "This method expects a transaction in JSON format and will attempt to apply it to the blockchain.",
summary: "This method expects a transaction in JSON format and will attempt to apply it to the blockchain.",
tags: ['chain'],
body: {
type: ['object', 'string'],
properties: {
signatures: {
"type": "array",
"description": "array of signatures required to authorize transaction",
"items": {$ref: 'Signature#'}
},
compression: {
"type": "boolean",
"description": "Compression used, usually false"
},
packed_context_free_data: {
"type": "string",
"description": "json to hex"
},
packed_trx: {
"type": "string",
"description": "Transaction object json to hex"
}
}
}
}
);
next();
}
+182 -11
View File
@@ -1,22 +1,166 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import {mergeActionMeta, timedQuery} from "../../../helpers/functions";
import {Serialize} from "eosjs";
import {hLog} from "../../../../helpers/common_functions";
import * as AbiEOS from "@eosrio/node-abieos";
import {ApiResponse} from "@elastic/elasticsearch";
import {TextDecoder, TextEncoder} from "util";
import {JsonRpc} from "eosjs/dist";
import {terms} from "../../v2-history/get_actions/definitions";
const abi_remapping = {
"_Bool": "bool"
};
const txEnc = new TextEncoder();
const txDec = new TextDecoder();
async function fetchAbiHexAtBlockElastic(client, chain, contract_name, last_block, get_json) {
try {
const _includes = ["actions", "tables", "block"];
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 client.search({
index: `${chain}-abi-*`,
body: {
size: 1, query,
sort: [{block: {order: "desc"}}],
_source: {includes: _includes}
}
});
const results = queryResult.body.hits.hits;
if (results.length > 0) {
const nextRefResponse: ApiResponse = await client.search({
index: `${chain}-abi-*`,
body: {
size: 1,
query: {
bool: {
must: [
{term: {account: contract_name}},
{range: {block: {gte: last_block}}}
]
}
},
sort: [{block: {order: "asc"}}],
_source: {includes: ["block"]}
}
});
const nextRef = nextRefResponse.body.hits.hits;
if (nextRef.length > 0) {
return {
valid_until: nextRef[0]._source.block,
...results[0]._source
};
}
return results[0]._source;
} else {
return null;
}
} catch (e) {
hLog(e);
return null;
}
}
async function getAbiFromHeadBlock(rpc: JsonRpc, code) {
let _abi;
try {
_abi = (await rpc.get_abi(code)).abi;
} catch (e) {
hLog(e);
}
return {abi: _abi, valid_until: null, valid_from: null};
}
async function getContractAtBlock(esClient, rpc, chain, accountName: string, block_num: number, check_action?: string) {
let savedAbi, abi;
savedAbi = await fetchAbiHexAtBlockElastic(esClient, chain, accountName, block_num, true);
if (savedAbi === null || (savedAbi.actions && !savedAbi.actions.includes(check_action))) {
savedAbi = await getAbiFromHeadBlock(rpc, accountName);
if (!savedAbi) return [null, null];
abi = savedAbi.abi;
} else {
try {
abi = JSON.parse(savedAbi.abi);
} catch (e) {
hLog(e);
return [null, null];
}
}
if (!abi) return [null, 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];
}
const terms = ["notified", "act.authorization.actor"];
const extendedActions = new Set(["transfer", "newaccount", "updateauth"]);
async function getActions(fastify: FastifyInstance, request: FastifyRequest) {
if (typeof request.body === 'string') {
request.body = JSON.parse(request.body);
}
const reqBody = request.body;
const reqBody = request.body as any;
const should_array = [];
for (const entry of terms) {
const tObj = {term: {}};
tObj.term[entry] = reqBody.account_name;
should_array.push(tObj);
}
let code, method, pos, offset, parent;
let code, method, pos, offset;
let sort_direction = 'desc';
let filterObj = [];
if (reqBody.filter) {
@@ -38,8 +182,8 @@ async function getActions(fastify: FastifyInstance, request: FastifyRequest) {
}
pos = parseInt(reqBody.pos || 0, 10);
offset = parseInt(reqBody.offset || 20, 10);
let from, size;
from = size = 0;
let from = 0;
let size = 0;
if (pos === -1) {
if (offset < 0) {
from = 0;
@@ -128,6 +272,7 @@ async function getActions(fastify: FastifyInstance, request: FastifyRequest) {
}
}
};
// console.log(JSON.stringify(esOpts, null, 2));
const pResults = await Promise.all([fastify.eosjs.rpc.get_info(), fastify.elastic['search'](esOpts)]);
const results = pResults[1];
const response = {
@@ -152,11 +297,13 @@ async function getActions(fastify: FastifyInstance, request: FastifyRequest) {
}
actions = actions.slice(index);
}
actions.forEach((action, index) => {
for (let i = 0; i < actions.length; i++) {
let action = actions[i];
action = action._source;
let act: any = {
"global_action_seq": action.global_sequence,
"account_action_seq": index,
"account_action_seq": i,
"block_num": action.block_num,
"block_time": action['@timestamp'],
"action_trace": {
@@ -172,7 +319,31 @@ async function getActions(fastify: FastifyInstance, request: FastifyRequest) {
};
mergeActionMeta(action);
act.action_trace.act = action.act;
act.action_trace.act.hex_data = Buffer.from(JSON.stringify(action.act.data)).toString('hex');
if (reqBody.hex_data) {
try {
const [contract, _] = await getContractAtBlock(
fastify.elastic,
fastify.eosjs.rpc,
fastify.manager.chain,
action.act.account,
action.block_num
);
action.act.hex_data = Serialize.serializeActionData(
contract,
action.act.account,
action.act.name,
action.act.data,
txEnc,
txDec
);
} catch (e: any) {
console.log(e);
}
}
// TODO: Optionally re-encode using the original ABI, will increase query time
// act.action_trace.act.hex_data = Buffer.from(flatstr(JSON.stringify(action.act.data))).toString('hex');
if (action.act.account_ram_deltas) {
act.action_trace.account_ram_deltas = action.account_ram_deltas
}
@@ -182,13 +353,13 @@ async function getActions(fastify: FastifyInstance, request: FastifyRequest) {
act.action_trace.receiver = receipt.receiver;
act.account_action_seq = receipt['recv_sequence'];
response.actions.push(act);
});
}
}
return response;
}
export function getActionsHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => {
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getActions, fastify, request, route));
}
}
+1 -1
View File
@@ -8,7 +8,7 @@ export default function (fastify: FastifyInstance, opts: any, next) {
addApiRoute(fastify, 'POST', getRouteName(__filename), getActionsHandler, {
description: 'legacy get actions query',
summary: 'get actions',
tags: ['actions', 'history'],
tags: ['history'],
body: {
type: ['object', 'string'],
properties: {
@@ -1,72 +1,72 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import {timedQuery} from "../../../helpers/functions";
import {ApiResponse} from "@elastic/elasticsearch";
async function getControlledAccounts(fastify: FastifyInstance, request: FastifyRequest) {
if (typeof request.body === 'string') {
request.body = JSON.parse(request.body)
}
let controlling_account = request.body.controlling_account;
const results: ApiResponse = await fastify.elastic.search({
index: fastify.manager.chain + '-action-*',
size: 100,
body: {
query: {
bool: {
should: [
{
term: {"@updateauth.auth.accounts.permission.actor": controlling_account}},
{
bool: {
must: [
{term: {"act.account": "eosio"}},
{term: {"act.name": "newaccount"}},
{term: {"act.authorization.actor": controlling_account}}
]
}
}
],
minimum_should_match: 1
}
},
sort: [
{"global_sequence": {"order": "desc"}}
]
}
});
if (typeof request.body === 'string') {
request.body = JSON.parse(request.body)
}
let controlling_account = request.body["controlling_account"];
const results: ApiResponse = await fastify.elastic.search({
index: fastify.manager.chain + '-action-*',
size: 100,
body: {
query: {
bool: {
should: [
{
term: {"@updateauth.auth.accounts.permission.actor": controlling_account}
},
{
bool: {
must: [
{term: {"act.account": "eosio"}},
{term: {"act.name": "newaccount"}},
{term: {"act.authorization.actor": controlling_account}}
]
}
}
],
minimum_should_match: 1
}
},
sort: [
{"global_sequence": {"order": "desc"}}
]
}
});
const response = {
controlled_accounts: []
};
const response = {
controlled_accounts: []
};
const hits = results.body.hits.hits;
const hits = results.body.hits.hits;
if (hits.length > 0) {
response.controlled_accounts = hits.map((v) => {
if (v._source.act.name === 'newaccount') {
if (v._source['@newaccount'].newact) {
return v._source['@newaccount'].newact;
} else if (v._source.act.data.newact) {
return v._source.act.data.newact;
} else {
return null;
}
} else if (v._source.act.name === 'updateauth') {
return v._source.act.data.account;
} else {
return null;
}
});
}
if (response.controlled_accounts.length > 0) {
response.controlled_accounts = [...(new Set(response.controlled_accounts))];
}
return response;
if (hits.length > 0) {
response.controlled_accounts = hits.map((v) => {
if (v._source.act.name === 'newaccount') {
if (v._source['@newaccount'].newact) {
return v._source['@newaccount'].newact;
} else if (v._source.act.data.newact) {
return v._source.act.data.newact;
} else {
return null;
}
} else if (v._source.act.name === 'updateauth') {
return v._source.act.data.account;
} else {
return null;
}
});
}
if (response.controlled_accounts.length > 0) {
response.controlled_accounts = [...(new Set(response.controlled_accounts))];
}
return response;
}
export function getControlledAccountsHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => {
reply.send(await timedQuery(getControlledAccounts, fastify, request, route));
}
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getControlledAccounts, fastify, request, route));
}
}
@@ -6,7 +6,7 @@ export default function (fastify: FastifyInstance, opts: any, next) {
addApiRoute(fastify, 'POST', getRouteName(__filename), getControlledAccountsHandler, {
description: 'get controlled accounts by controlling accounts',
summary: 'get controlled accounts by controlling accounts',
tags: ['state'],
tags: ['accounts'],
body: {
type: ['object', 'string'],
properties: {
@@ -8,9 +8,9 @@ export default function (fastify: FastifyInstance, opts: any, next) {
addApiRoute(fastify, 'POST', getRouteName(__filename), getKeyAccountsHandler, {
description: 'get accounts by public key',
summary: 'get accounts by public key',
tags: ['accounts', 'state'],
tags: ['accounts'],
body: {
type: 'object',
type: ['object', 'string'],
properties: {"public_key": {description: 'public key', type: 'string'}},
required: ["public_key"]
},
@@ -1,30 +1,25 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import {mergeActionMeta, timedQuery} from "../../../helpers/functions";
import {createHash} from "crypto";
import {GetInfoResult} from "eosjs/dist/eosjs-rpc-interfaces";
async function getTransaction(fastify: FastifyInstance, request: FastifyRequest) {
if (request.method === 'POST' && !request.body) {
throw new Error("missing POST body");
}
if (typeof request.body === 'string') {
request.body = JSON.parse(request.body)
}
const pResults = await Promise.all([fastify.eosjs.rpc.get_info(), fastify.elastic['search']({
"index": fastify.manager.chain + '-action-*',
"body": {
"query": {
"bool": {
must: [
{term: {"trx_id": request.body.id.toLowerCase()}}
]
}
},
"sort": {
"global_sequence": "asc"
}
}
})]);
const results = pResults[1];
const body: any = request.body;
const redis = fastify.redis;
const trxId = body.id.toLowerCase();
const conf = fastify.manager.config;
const cachedData = await redis.hgetall('trx_' + trxId);
const response: any = {
"id": request.body.id,
"id": body.id,
"trx": {
"receipt": {
"status": "executed",
@@ -41,11 +36,89 @@ async function getTransaction(fastify: FastifyInstance, request: FastifyRequest)
},
"block_num": 0,
"block_time": "",
"last_irreversible_block": pResults[0].last_irreversible_block_num,
"last_irreversible_block": undefined,
"traces": []
};
const hits = results['body']['hits']['hits'];
let hits;
// build get_info request with caching
const $getInfo = new Promise<GetInfoResult>(resolve => {
const key = `${fastify.manager.chain}_get_info`;
fastify.redis.get(key).then(value => {
if (value) {
resolve(JSON.parse(value));
} else {
fastify.eosjs.rpc.get_info().then(value1 => {
fastify.redis.set(key, JSON.stringify(value1), 'EX', 6);
resolve(value1);
}).catch((reason) => {
console.log(reason);
response.error = 'failed to get last_irreversible_block_num'
resolve(null);
});
}
});
});
// reconstruct hits from cached data
if (cachedData && Object.keys(cachedData).length > 0) {
const gsArr = [];
for (let cachedDataKey in cachedData) {
gsArr.push(cachedData[cachedDataKey]);
}
gsArr.sort((a, b) => {
return a.global_sequence - b.global_sequence;
});
hits = gsArr.map(value => {
return {
_source: JSON.parse(value)
};
});
const promiseResults = await Promise.all([
redis.ttl('trx_' + trxId),
$getInfo
]);
response.cache_expires_in = promiseResults[0];
response.last_irreversible_block = promiseResults[1].last_irreversible_block_num;
}
// search on ES if cache is not present
if (!hits) {
const _size = conf.api.limits.get_trx_actions || 100;
const blockHint = parseInt(body.block_num_hint, 10);
let indexPattern = '';
if (blockHint) {
const idxPart = Math.ceil(blockHint / conf.settings.index_partition_size).toString().padStart(6, '0');
indexPattern = fastify.manager.chain + `-action-${conf.settings.index_version}-${idxPart}`;
} else {
indexPattern = fastify.manager.chain + '-action-*';
}
let pResults;
try {
// build search request
const $search = fastify.elastic.search({
index: indexPattern,
size: _size,
body: {
query: {bool: {must: [{term: {trx_id: trxId}}]}},
sort: {global_sequence: "asc"}
}
});
// execute in parallel
pResults = await Promise.all([$getInfo, $search]);
} catch (e) {
console.log(e.message);
if (e.meta.statusCode === 404) {
return response;
}
}
hits = pResults[1]['body']['hits']['hits'];
response.last_irreversible_block = pResults[0].last_irreversible_block_num;
}
if (hits.length > 0) {
const actions = hits;
@@ -62,108 +135,33 @@ async function getTransaction(fastify: FastifyInstance, request: FastifyRequest)
"signatures": [],
"context_free_data": []
};
let traces = {};
let seqNum = 0;
for (let action of actions) {
action = action._source;
mergeActionMeta(action);
action.act['hex_data'] = Buffer.from(JSON.stringify(action.act.data)).toString('hex');
if (action.parent === 0) {
response.trx.trx.actions.push(action.act);
}
response.block_num = action.block_num;
response.block_time = action['@timestamp'];
seqNum += 1;
let trace = {
receipt: {
receiver: action.act.account,
global_sequence: String(action.global_sequence),
auth_sequence: [
action.act.authorization[0].actor,
seqNum
],
act_digest: '',
recv_sequence: seqNum,
code_sequence: 1,
abi_sequence: 1
},
act: action.act,
account_ram_deltas: action.account_ram_deltas || [],
context_free: false,
block_num: action.block_num,
block_time: action['@timestamp'],
console: "",
elapsed: 0,
except: null,
inline_traces: [],
producer_block_id: "",
trx_id: request.body.id,
notified: action.notified
};
let hash = createHash('sha256');
hash.update(JSON.stringify(action.act));
trace.receipt.act_digest = hash.digest('hex');
traces[action.global_sequence] = trace;
}
actions.forEach(action => {
action = action._source;
for (let i = 0; i < traces[action.global_sequence].notified.length; i++) {
if (traces[action.global_sequence].notified[i] === action.act.account) {
traces[action.global_sequence].notified.splice(i, 1);
break;
for (const receipt of action.receipts) {
if (action.act_digest) {
receipt.act_digest = action.act_digest.toLowerCase();
}
}
if (action.parent !== 0 && action.parent) {
if (traces[action.parent]) {
for (let i = 0; i < traces[action.parent].notified.length; i++) {
if (traces[action.parent].notified[i] === action.act.account) {
traces[action.parent].notified.splice(i, 1);
break;
}
}
traces[action.parent].inline_traces.push(traces[action.global_sequence]);
}
}
});
actions.forEach(action => {
action = action._source;
response.traces.push(traces[action.global_sequence]);
traces[action.global_sequence].notified.forEach((note, index) => {
seqNum += 1;
let trace = {
receipt: {
receiver: note,
global_sequence: String(action.global_sequence + index + 1),
auth_sequence: [
action.act.authorization[0].actor,
seqNum
],
act_digest: traces[action.global_sequence].receipt.act_digest,
recv_sequence: seqNum,
code_sequence: 1,
abi_sequence: 1
},
account_ram_deltas: action.account_ram_deltas || [],
response.traces.push({
receipt: receipt,
act: action.act,
account_ram_deltas: action.account_ram_deltas || [],
context_free: false,
block_num: action.block_num,
block_time: action['@timestamp'],
console: "",
context_free: false,
elapsed: 0,
except: null,
inline_traces: [],
producer_block_id: "",
trx_id: request.body.id,
};
traces[action.global_sequence].inline_traces.unshift(trace);
response.traces.push(trace);
});
delete traces[action.global_sequence].notified;
});
trx_id: body.id
});
}
}
} else {
const errmsg = "Transaction " + request.body.id.toLowerCase() + " not found in history and no block hint was given";
const errmsg = "Transaction " + body.id.toLowerCase() + " not found in history and no block hint was given";
return {
code: 500,
message: "Internal Service Error",
@@ -186,7 +184,7 @@ async function getTransaction(fastify: FastifyInstance, request: FastifyRequest)
}
export function getTransactionHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => {
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getTransaction, fastify, request, route));
}
}
@@ -6,10 +6,13 @@ export default function (fastify: FastifyInstance, opts: any, next) {
addApiRoute(fastify, 'POST', getRouteName(__filename), getTransactionHandler, {
description: 'get all actions belonging to the same transaction',
summary: 'get transaction by id',
tags: ['transactions', 'history'],
tags: ['history'],
body: {
type: ['object', 'string'],
properties: {id: {description: 'transaction id', type: 'string'}},
properties: {
id: {description: 'transaction id', type: 'string'},
block_num_hint: {description: 'block number hint', type: 'integer'},
},
required: ["id"]
}
});
+126
View File
@@ -0,0 +1,126 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {timedQuery} from "../../../helpers/functions";
import * as _ from "lodash";
interface getBlockTraceResponse {
id: string;
number: number;
previous_id: string;
status: string;
timestamp: string;
producer: string;
transactions: any[];
}
async function getBlockTrace(fastify: FastifyInstance, request: FastifyRequest) {
if (typeof request.body === 'string') {
request.body = JSON.parse(request.body);
}
const reqBody: any = request.body;
const targetBlock = parseInt(reqBody.block_num);
let searchBody;
if (reqBody.block_id) {
searchBody = {
query: {
bool: {
must: [
{term: {block_id: reqBody.block_id}}
]
}
}
};
} else {
if (targetBlock > 0) {
searchBody = {
query: {
bool: {
must: [
{term: {block_num: targetBlock}}
]
}
}
};
} else if (targetBlock < 0) {
searchBody = {
query: {match_all: {}},
sort: {block_num: "desc"}
}
}
}
const response = {transactions: []} as getBlockTraceResponse;
if (searchBody) {
const getBlockHeader = await fastify.elastic.search({
index: fastify.manager.chain + "-block-*",
size: 1,
body: searchBody
});
if (getBlockHeader.body.hits.hits.length === 1) {
const block = getBlockHeader.body.hits.hits[0]._source;
const info = await fastify.eosjs.rpc.get_info();
response.id = block.block_id;
response.number = block.block_num;
response.previous_id = block.prev_id;
response.status = info.last_irreversible_block_num > block.block_num ? "irreversible" : "pending"
response.timestamp = block['@timestamp'];
response.producer = block.producer;
// lookup all actions on block
const getActionsResponse = await fastify.elastic.search({
index: fastify.manager.chain + "-action-*",
size: fastify.manager.config.api.limits.get_actions || 1000,
body: {
query: {
bool: {
must: [
{term: {block_num: block.block_num}}
]
}
},
sort: {global_sequence: "asc"}
}
});
const hits = getActionsResponse.body.hits.hits;
if (hits.length > 0) {
_.forEach(_.groupBy(hits, (v) => v['_source']['trx_id']), (value, key) => {
const actArray = [];
for (const act of value) {
const action = act['_source']
for (const receipt of action.receipts) {
actArray.push({
receiver: receipt.receiver,
account: action.act.account,
action: action.act.name,
authorization: action.act.authorization.map(auth => {
return {account: auth.actor, permission: auth.permission};
}),
data: action.act.data
});
}
}
response.transactions.push({id: key, actions: actArray});
});
}
return response;
} else {
throw new Error('block not found!');
}
} else {
throw new Error("invalid block number or id");
}
}
export function getBlockTraceHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getBlockTrace, fastify, request, route));
}
}
+72
View File
@@ -0,0 +1,72 @@
import {FastifyInstance} from "fastify";
import {addApiRoute, extendResponseSchema, getRouteName} from "../../../helpers/functions";
import {getBlockTraceHandler} from "./get_block";
export default function (fastify: FastifyInstance, opts: any, next) {
const schema = {
description: 'get block traces',
summary: 'get block traces',
tags: ['history'],
body: {
type: ['object', 'string'],
properties: {
"block_num": {
description: 'block number',
type: 'integer'
},
"block_id": {
description: 'block id',
type: 'string'
}
}
},
response: extendResponseSchema({
"id": {type: "string"},
"number": {type: "integer"},
"previous_id": {type: "string"},
"status": {type: "string"},
"timestamp": {type: "string"},
"producer": {type: "string"},
"transactions": {
type: "array",
items: {
type: 'object',
properties: {
"id": {type: "string"},
"actions": {
type: "array",
items: {
type: 'object',
properties: {
"receiver": {type: "string"},
"account": {type: "string"},
"action": {type: "string"},
"authorization": {
type: "array", items: {
type: 'object',
properties: {
"account": {type: "string"},
"permission": {type: "string"},
}
}
},
"data": {
additionalProperties: true
}
}
}
}
}
}
}
})
};
addApiRoute(
fastify,
'POST',
getRouteName(__filename),
getBlockTraceHandler,
schema
);
next();
}
@@ -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();
}
@@ -0,0 +1,51 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {timedQuery} from "../../../helpers/functions";
async function getAbiSnapshot(fastify: FastifyInstance, request: FastifyRequest) {
const response = {
block_num: null
};
const query: any = request.query;
const code = query.contract;
const block = query.block;
const should_fetch = query.fetch;
const mustArray = [];
mustArray.push({"term": {"account": code}});
if (block) {
mustArray.push({"range": {"block": {"lte": parseInt(block)}}});
}
const results = await fastify.elastic.search({
index: fastify.manager.chain + '-abi-*',
size: 1,
body: {
query: {bool: {must: mustArray}},
sort: [{block: {order: "desc"}}]
}
});
if (results['body']['hits']['hits'].length > 0) {
if (should_fetch) {
response['abi'] = JSON.parse(results['body']['hits']['hits'][0]['_source']['abi']);
} else {
response['present'] = true;
}
response.block_num = results['body']['hits']['hits'][0]['_source']['block'];
} else {
response['present'] = false;
response['error'] = 'abi not found for ' + code + ' until block ' + block;
}
return response;
}
export function getAbiSnapshotHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getAbiSnapshot, fastify, request, route));
}
}
@@ -0,0 +1,40 @@
import {FastifyInstance} from "fastify";
import {addApiRoute, getRouteName} from "../../../helpers/functions";
import {getAbiSnapshotHandler} from "./get_abi_snapshot";
export default function (fastify: FastifyInstance, opts: any, next) {
const schema = {
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"]
}
};
addApiRoute(
fastify,
'GET',
getRouteName(__filename),
getAbiSnapshotHandler,
schema
);
next();
}
@@ -1,5 +1,6 @@
export const terms = [
"notified.keyword",
"notified",
"receipts.receiver",
"act.authorization.actor"
];
@@ -8,17 +9,26 @@ export const extendedActions = new Set([
"newaccount",
"updateauth",
"buyram",
"buyrambytes"
"buyrambytes",
"delegatebw",
"undelegatebw",
"voteproducer"
]);
export const primaryTerms = [
"notified",
"block_num",
"block_id",
"global_sequence",
"producer",
"@timestamp",
"creator_action_ordinal",
"action_ordinal",
"cpu_usage_us",
"net_usage_words"
"net_usage_words",
"trx_id",
"receipts.receiver",
"receipts.global_sequence",
"receipts.recv_sequence",
"receipts.auth_sequence.account",
"receipts.auth_sequence.sequence"
];
+92 -39
View File
@@ -1,4 +1,4 @@
import {extendedActions, primaryTerms, terms} from "./definitions";
import {primaryTerms, terms} from "./definitions";
export function addSortedBy(query, queryBody, sort_direction) {
if (query['sortedBy']) {
@@ -70,68 +70,119 @@ function addRangeQuery(queryStruct, prop, pkey, query) {
export function applyTimeFilter(query, queryStruct) {
if (query['after'] || query['before']) {
let _lte = "now";
let _gte = "0";
if (query['before']) {
_lte = query['before'];
if (!_lte.endsWith("Z")) {
_lte += "Z";
}
if (query['after']?.includes(' ')) {
query['after'] = query['after'].replace(' ', 'T');
}
if (query['after']) {
_gte = query['after'];
if (!_gte.endsWith("Z")) {
_gte += "Z";
}
if (query['before']?.includes(' ')) {
query['before'] = query['before'].replace(' ', 'T');
}
if (!queryStruct.bool['filter']) {
queryStruct.bool['filter'] = [];
}
queryStruct.bool['filter'].push({
range: {
"@timestamp": {
"gte": _gte,
"lte": _lte
if (query['after']?.includes('T') || query['before']?.includes('T')) {
let _lte = "now";
let _gte = "0";
if (query['before']) {
try {
_lte = new Date(query['before']).toISOString();
} catch (e) {
throw new Error(e.message + ' [before]');
}
}
});
if (query['after']) {
try {
_gte = new Date(query['after']).toISOString();
} catch (e) {
throw new Error(e.message + ' [after]');
}
}
if (!queryStruct.bool['filter']) {
queryStruct.bool['filter'] = [];
}
queryStruct.bool['filter'].push({
range: {
"@timestamp": {
"gte": _gte,
"lte": _lte
}
}
});
} else {
// search by block number
const rangeObj = {
range: {
block_num: {}
}
};
if (parseInt(query['after']) > 0) {
rangeObj.range.block_num['gte'] = query['after'];
}
if (parseInt(query['before']) > 0) {
rangeObj.range.block_num['lte'] = query['before'];
}
if (Object.keys(rangeObj.range.block_num).length > 0) {
if (!queryStruct.bool['filter']) {
queryStruct.bool['filter'] = [];
}
queryStruct.bool['filter'].push(rangeObj);
}
}
}
}
export function applyGenericFilters(query, queryStruct) {
export function applyGenericFilters(query, queryStruct, allowedExtraParams: Set<string>) {
for (const prop in query) {
if (Object.prototype.hasOwnProperty.call(query, prop)) {
const pair = prop.split(".");
if (pair.length > 1 || primaryTerms.includes(pair[0])) {
let pkey;
if (pair.length > 1) {
pkey = extendedActions.has(pair[0]) ? "@" + prop : prop;
if (pair.length > 1 && allowedExtraParams) {
pkey = allowedExtraParams.has(pair[0]) ? "@" + prop : prop;
} else {
pkey = prop;
}
if (query[prop].indexOf("-") !== -1) {
addRangeQuery(queryStruct, prop, pkey, query);
} else {
const _termQuery = {};
const _qObj = {};
const parts = query[prop].split(",");
if (parts.length > 1) {
processMultiVars(queryStruct, parts, prop);
} else if (parts.length === 1) {
const andParts = parts[0].split(" ");
if (andParts.length > 1) {
andParts.forEach(value => {
const _q = {};
console.log(value);
_q[pkey] = value;
queryStruct.bool.must.push({term: _q});
// @transfer.memo special case
if (pkey === '@transfer.memo') {
_qObj[pkey] = {
query: parts[0]
};
if (query.match_fuzziness) {
_qObj[pkey].fuzziness = query.match_fuzziness;
}
if (query.match_operator) {
_qObj[pkey].operator = query.match_operator;
}
queryStruct.bool.must.push({
match: _qObj
});
} else {
if (parts[0].startsWith("!")) {
_termQuery[pkey] = parts[0].replace("!", "");
queryStruct.bool.must_not.push({term: _termQuery});
const andParts = parts[0].split(" ");
if (andParts.length > 1) {
andParts.forEach(value => {
const _q = {};
_q[pkey] = value;
queryStruct.bool.must.push({term: _q});
});
} else {
_termQuery[pkey] = parts[0];
queryStruct.bool.must.push({term: _termQuery});
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});
}
}
}
}
@@ -179,7 +230,7 @@ export function applyCodeActionFilters(query, queryStruct) {
}
}
export function getSkipLimit(query) {
export function getSkipLimit(query, max?: number) {
let skip, limit;
skip = parseInt(query.skip, 10);
if (skip < 0) {
@@ -188,6 +239,8 @@ export function getSkipLimit(query) {
limit = parseInt(query.limit, 10);
if (limit < 1) {
throw new Error('invalid limit parameter');
} else if (limit > max) {
throw new Error(`limit too big, maximum: ${max}`);
}
return {skip, limit};
}
@@ -1,5 +1,4 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import {getTrackTotalHits, mergeActionMeta, timedQuery} from "../../../helpers/functions";
import {
addSortedBy,
@@ -12,7 +11,8 @@ import {
} from "./functions";
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 queryStruct = {
"bool": {
must: [],
@@ -21,12 +21,18 @@ async function getActions(fastify: FastifyInstance, request: FastifyRequest) {
}
};
const {skip, limit} = getSkipLimit(query);
const {skip, limit} = getSkipLimit(query, maxActions);
const sort_direction = getSortDir(query);
applyAccountFilters(query, queryStruct);
applyGenericFilters(query, queryStruct);
applyGenericFilters(query, queryStruct, fastify.allowedActionQueryParamSet);
applyTimeFilter(query, queryStruct);
applyCodeActionFilters(query, queryStruct);
// allow precise counting of total hits
const trackTotalHits = getTrackTotalHits(query);
@@ -40,24 +46,37 @@ async function getActions(fastify: FastifyInstance, request: FastifyRequest) {
addSortedBy(query, query_body, sort_direction);
// Perform search
const maxActions = fastify.manager.config.api.limits.get_actions;
const pResults = await Promise.all([
fastify.eosjs.rpc.get_info(),
fastify.elastic.search({
"index": fastify.manager.chain + '-action-*',
"from": skip || 0,
"size": (limit > maxActions ? maxActions : limit) || 10,
"body": query_body
})
]);
const results = pResults[1]['body']['hits'];
let indexPattern = fastify.manager.chain + '-action-*';
if (query.hot_only) {
indexPattern = fastify.manager.chain + '-action';
}
const esOpts = {
"index": indexPattern,
"from": skip || 0,
"size": (limit > maxActions ? maxActions : limit) || 10,
"body": query_body
};
// console.log(JSON.stringify(esOpts, null, 2));
const esResults = await fastify.elastic.search(esOpts);
const results = esResults['body']['hits'];
const response: any = {
cached: false,
lib: pResults[0].last_irreversible_block_num,
lib: 0,
total: results['total']
};
if (query.hot_only) {
response.hot_only = true;
}
if (query.checkLib) {
response.lib = (await fastify.eosjs.rpc.get_info()).last_irreversible_block_num;
}
if (query.simple) {
response['simple_actions'] = [];
} else {
@@ -66,21 +85,44 @@ async function getActions(fastify: FastifyInstance, request: FastifyRequest) {
if (results['hits'].length > 0) {
const actions = results['hits'];
for (let action of actions) {
action = action._source;
for (let action of actions.map(a => a._source)) {
try {
if (action.act.data) {
if (action.act.data.account && action.act.data.name && action.act.data.authorization) {
action.act.data = action.act.data.data;
}
}
} catch (e: any) {
console.log(e);
}
mergeActionMeta(action);
if (query.noBinary === true) {
for (const key in action['act']['data']) {
if (action['act']['data'].hasOwnProperty(key)) {
if (typeof action['act']['data'][key] === 'string' && action['act']['data'][key].length > 256) {
action['act']['data'][key] = action['act']['data'][key].slice(0, 32) + "...";
}
}
}
}
if (query.simple) {
let notified = new Set(action.receipts.map(r => r.receiver));
response.simple_actions.push({
block: action['block_num'],
irreversible: action['block_num'] < pResults[0].last_irreversible_block_num,
irreversible: response.lib !== 0 ? action['block_num'] < response.lib : undefined,
timestamp: action['@timestamp'],
transaction_id: action['trx_id'],
actors: action['act']['authorization'].map(a => `${a.actor}@${a.permission}`).join(","),
notified: action['notified'].join(','),
notified: [...notified].join(','),
contract: action['act']['account'],
action: action['act']['name'],
data: action['act']['data']
});
} else {
response.actions.push(action);
}
@@ -90,7 +132,7 @@ async function getActions(fastify: FastifyInstance, request: FastifyRequest) {
}
export function getActionsHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => {
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getActions, fastify, request, route));
}
}
+83 -40
View File
@@ -1,13 +1,79 @@
import {FastifyInstance, RouteSchema} from "fastify";
import {FastifyInstance, FastifySchema} from "fastify";
import {getActionsHandler} from "./get_actions";
import {addApiRoute, extendQueryStringSchema, extendResponseSchema, getRouteName} from "../../../helpers/functions";
export const getActionResponseSchema = {
"@timestamp": {type: "string"},
"timestamp": {type: "string"},
"block_num": {type: "number"},
"block_id": {type: "string"},
"trx_id": {type: "string"},
"act": {
type: 'object',
properties: {
"account": {type: "string"},
"name": {type: "string"},
"authorization": {
type: "array",
items: {
type: "object",
properties: {
"actor": {type: "string"},
"permission": {type: "string"},
}
}
}
},
additionalProperties: true
},
"receipts": {
type: "array",
items: {
type: "object",
properties: {
"receiver": {type: "string"},
"global_sequence": {type: "number"},
"recv_sequence": {type: "number"},
"auth_sequence": {
type: "array",
items: {
type: "object",
properties: {
"account": {type: "string"},
"sequence": {type: "number"},
}
}
}
}
}
},
"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"},
"producer": {type: "string"},
"parent": {type: "number"},
"action_ordinal": {type: 'number'},
"creator_action_ordinal": {type: 'number'},
"signatures": {type: "array", items: {type: 'string'}}
};
export default function (fastify: FastifyInstance, opts: any, next) {
const schema: RouteSchema = {
const schema: FastifySchema = {
description: 'get actions based on notified account. this endpoint also accepts generic filters based on indexed fields' +
' (e.g. act.authorization.actor=eosio or act.name=delegatebw), if included they will be combined with a AND operator',
summary: 'get root actions',
tags: ['actions', 'history'],
tags: ['history'],
querystring: extendQueryStringSchema({
"account": {
description: 'notified account',
@@ -40,7 +106,19 @@ export default function (fastify: FastifyInstance, opts: any, next) {
"simple": {
description: 'simplified output mode',
type: 'boolean'
}
},
"hot_only": {
description: 'search only the latest hot index',
type: 'boolean'
},
"noBinary": {
description: "exclude large binary data",
type: 'boolean'
},
"checkLib": {
description: "perform reversibility check",
type: 'boolean'
},
}),
response: extendResponseSchema({
"simple_actions": {
@@ -66,42 +144,7 @@ export default function (fastify: FastifyInstance, opts: any, next) {
type: "array",
items: {
type: 'object',
properties: {
"@timestamp": {type: "string"},
"timestamp": {type: "string"},
"block_num": {type: "number"},
"trx_id": {type: "string"},
"act": {
type: 'object',
properties: {
"account": {type: "string"},
"name": {type: "string"}
},
additionalProperties: true
},
"notified": {
type: "array", items: {type: "string"}
},
"cpu_usage_us": {type: "number"},
"net_usage_words": {type: "number"},
"account_ram_deltas": {
type: "array",
items: {
type: "object",
properties: {
"account": {type: "string"},
"delta": {type: "number"}
},
additionalProperties: true
}
},
"global_sequence": {type: "number"},
"receiver": {type: 'string'},
"producer": {type: "string"},
"parent": {type: "number"},
"action_ordinal": {type: 'number'},
"creator_action_ordinal": {type: 'number'}
}
properties: getActionResponseSchema
}
}
})
@@ -0,0 +1,58 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {timedQuery} from "../../../helpers/functions";
import {getSkipLimit} from "../get_actions/functions";
async function getCreatedAccounts(fastify: FastifyInstance, request: FastifyRequest) {
const query: any = request.query;
const {skip, limit} = getSkipLimit(query);
const maxActions = fastify.manager.config.api.limits.get_created_accounts;
const results = await fastify.elastic.search({
"index": fastify.manager.chain + '-action-*',
"from": skip || 0,
"size": (limit > maxActions ? maxActions : limit) || 100,
"body": {
"query": {
"bool": {
must: [
{term: {"act.authorization.actor": query.account.toLowerCase()}},
{term: {"act.name": "newaccount"}},
{term: {"act.account": "eosio"}}
]
}
},
sort: {
"global_sequence": "desc"
}
}
});
const response = {accounts: []};
if (results['body']['hits']['hits'].length > 0) {
const actions = results['body']['hits']['hits'];
for (let action of actions) {
action = action._source;
const _tmp = {};
if (action['act']['data']['newact']) {
_tmp['name'] = action['act']['data']['newact'];
} else if (action['@newaccount']['newact']) {
_tmp['name'] = action['@newaccount']['newact'];
} else {
console.log(action);
}
_tmp['trx_id'] = action['trx_id'];
_tmp['timestamp'] = action['@timestamp'];
response.accounts.push(_tmp);
}
}
return response;
}
export function getCreatedAccountsHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getCreatedAccounts, fastify, request, route));
}
}
@@ -0,0 +1,44 @@
import {FastifyInstance, FastifySchema} from "fastify";
import {getCreatedAccountsHandler} from "./get_created_accounts";
import {addApiRoute, extendQueryStringSchema, extendResponseSchema, getRouteName} from "../../../helpers/functions";
export default function (fastify: FastifyInstance, opts: any, next) {
const schema: FastifySchema = {
description: 'get all accounts created by one creator',
summary: 'get created accounts',
tags: ['accounts'],
querystring: extendQueryStringSchema({
"account": {
description: 'creator account',
type: 'string',
minLength: 1,
maxLength: 12
}
}, ["account"]),
response: extendResponseSchema({
"query_time": {
type: "number"
},
"total": {
type: "object",
properties: {
"value": {type: "number"},
"relation": {type: "string"}
}
},
"accounts": {
type: "array",
items: {
type: 'object',
properties: {
'name': {type: 'string'},
'timestamp': {type: 'string'},
'trx_id': {type: 'string'}
}
}
}
})
};
addApiRoute(fastify, 'GET', getRouteName(__filename), getCreatedAccountsHandler, schema);
next();
}
@@ -0,0 +1,106 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {timedQuery} from "../../../helpers/functions";
async function getCreator(fastify: FastifyInstance, request: FastifyRequest) {
const query: any = request.query;
const response = {
account: query.account,
creator: '',
timestamp: '',
block_num: 0,
trx_id: '',
};
if (query.account === fastify.manager.config.settings.eosio_alias) {
try {
const genesisBlock = await fastify.eosjs.rpc.get_block(1);
if (genesisBlock) {
response.timestamp = genesisBlock.timestamp;
}
} catch (e) {
console.log(e.message);
}
response.creator = '__self__';
response.block_num = 1;
response.trx_id = "";
return response;
}
const results = await fastify.elastic.search({
"index": fastify.manager.chain + '-action-*',
"body": {
size: 1,
query: {
bool: {
must: [{term: {"@newaccount.newact": query.account}}]
}
}
}
});
if (results['body']['hits']['hits'].length === 1) {
const result = results['body']['hits']['hits'][0]._source;
response.trx_id = result.trx_id;
response.block_num = result.block_num;
response.creator = result.act.data.creator;
response.timestamp = result['@timestamp'];
return response;
} else {
let accountInfo;
try {
accountInfo = await fastify.eosjs.rpc.get_account(query.account);
} catch (e) {
throw new Error("account not found");
}
if (accountInfo) {
try {
response.timestamp = accountInfo.created;
const blockHeader = await fastify.elastic.search({
"index": fastify.manager.chain + '-block-*',
"body": {
size: 1,
query: {
bool: {
must: [{term: {"@timestamp": response.timestamp}}]
}
}
}
});
const hits = blockHeader['body']['hits']['hits'];
if (hits.length > 0 && hits[0]._source) {
const blockId = blockHeader['body']['hits']['hits'][0]._source.block_id;
const blockData = await fastify.eosjs.rpc.get_block(blockId);
response.block_num = blockData.block_num;
for (const transaction of blockData["transactions"]) {
if (typeof transaction.trx !== 'string') {
const actions = transaction.trx.transaction.actions;
for (const act of actions) {
if (act.name === 'newaccount') {
if (act.data.name === query.account) {
response.creator = act.data.creator;
response.trx_id = transaction.id;
return response;
}
}
}
}
}
}
return response;
} catch (e) {
console.log(e.message);
throw new Error("account creation not found");
}
} else {
throw new Error("account not found");
}
}
}
export function getCreatorHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getCreator, fastify, request, route));
}
}
@@ -0,0 +1,45 @@
import {FastifyInstance, FastifySchema} from "fastify";
import {getCreatorHandler} from "./get_creator";
import {addApiRoute, extendResponseSchema, getRouteName} from "../../../helpers/functions";
export default function (fastify: FastifyInstance, opts: any, next) {
const schema: FastifySchema = {
description: 'get account creator',
summary: 'get account creator',
tags: ['accounts'],
querystring: {
type: 'object',
properties: {
"account": {
description: 'created account',
type: 'string',
minLength: 1,
maxLength: 12
}
},
required: ["account"]
},
response: extendResponseSchema({
"account": {
type: "string"
},
"creator": {
type: "string"
},
"timestamp": {
type: "string"
},
"block_num": {
type: "integer"
},
"trx_id": {
type: "string"
},
"indirect_creator": {
type: "string"
}
})
};
addApiRoute(fastify, 'GET', getRouteName(__filename), getCreatorHandler, schema);
next();
}
+35 -12
View File
@@ -1,14 +1,15 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import {mergeDeltaMeta, timedQuery} from "../../../helpers/functions";
import {applyTimeFilter} from "../get_actions/functions";
async function getDeltas(fastify: FastifyInstance, request: FastifyRequest) {
let skip, limit;
let sort_direction = 'desc';
const mustArray = [];
for (const param in request.query) {
if (Object.prototype.hasOwnProperty.call(request.query, param)) {
const value = request.query[param];
const query: any = request.query;
for (const param in query) {
if (Object.prototype.hasOwnProperty.call(query, param)) {
const value = query[param];
switch (param) {
case 'limit': {
limit = parseInt(value, 10);
@@ -34,13 +35,31 @@ async function getDeltas(fastify: FastifyInstance, request: FastifyRequest) {
}
break;
}
case 'before': {
break;
}
case 'after': {
break;
}
default: {
const values = request.query[param].split(",");
const terms = {};
terms[param] = values;
const shouldArray = {terms: terms};
const boolStruct = {bool: {should: [shouldArray]}};
mustArray.push(boolStruct);
if (typeof value === 'string') {
const values = query[param].split(",");
if (values.length > 1) {
const terms = {};
terms[param] = values;
mustArray.push({terms: terms});
} else {
const term = {};
term[param] = values[0];
mustArray.push({term: term});
}
} else {
if (typeof value === 'number') {
const term = {};
term[param] = value;
mustArray.push({term: term});
}
}
break;
}
}
@@ -48,12 +67,16 @@ async function getDeltas(fastify: FastifyInstance, request: FastifyRequest) {
}
const maxDeltas = fastify.manager.config.api.limits.get_deltas ?? 1000;
const queryStruct = {bool: {must: mustArray}};
applyTimeFilter(query, queryStruct);
const results = await fastify.elastic.search({
"index": fastify.manager.chain + '-delta-*',
"from": skip || 0,
"size": (limit > maxDeltas ? maxDeltas : limit) || 10,
"body": {
query: {bool: {must: mustArray}},
query: queryStruct,
sort: {
"block_num": sort_direction
}
@@ -70,7 +93,7 @@ async function getDeltas(fastify: FastifyInstance, request: FastifyRequest) {
}
export function getDeltasHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => {
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getDeltas, fastify, request, route));
}
}
+17 -4
View File
@@ -1,9 +1,9 @@
import {FastifyInstance, RouteSchema} from "fastify";
import {FastifyInstance, FastifySchema} from "fastify";
import {getDeltasHandler} from "./get_deltas";
import {addApiRoute, extendQueryStringSchema, extendResponseSchema, getRouteName} from "../../../helpers/functions";
export default function (fastify: FastifyInstance, opts: any, next) {
const schema: RouteSchema = {
const schema: FastifySchema = {
description: 'get state deltas',
summary: 'get state deltas',
tags: ['history'],
@@ -23,7 +23,19 @@ export default function (fastify: FastifyInstance, opts: any, next) {
"payer": {
description: 'payer account',
type: 'string'
}
},
"after": {
description: 'filter after specified date (ISO8601)',
type: 'string'
},
"before": {
description: 'filter before specified date (ISO8601)',
type: 'string'
},
"present": {
description: 'delta present flag',
type: 'number'
},
}),
response: extendResponseSchema({
"deltas": {
@@ -32,13 +44,14 @@ export default function (fastify: FastifyInstance, opts: any, next) {
type: 'object',
properties: {
"timestamp": {type: 'string'},
"present": {type: 'number'},
"code": {type: 'string'},
"scope": {type: 'string'},
"table": {type: 'string'},
"primary_key": {type: 'string'},
"payer": {type: 'string'},
"present": {type: 'boolean'},
"block_num": {type: 'number'},
"block_id": {type: 'string'},
"data": {
type: 'object',
additionalProperties: true
@@ -0,0 +1,105 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {timedQuery} from "../../../helpers/functions";
import {createHash} from "crypto";
import {base58ToBinary, binaryToBase58} from "eosjs/dist/eosjs-numeric";
import {Search} from "@elastic/elasticsearch/api/requestParams";
function convertToLegacyKey(block_signing_key: string) {
if (block_signing_key.startsWith("PUB_K1_")) {
const buf = base58ToBinary(37, block_signing_key.substr(7));
const data = buf.slice(0, buf.length - 4);
const merged = Buffer.concat([
data,
createHash('ripemd160')
.update(data)
.digest()
.slice(0, 4)
]);
return "EOS" + binaryToBase58(merged);
} else {
return block_signing_key;
}
}
async function getSchedule(fastify: FastifyInstance, request: FastifyRequest) {
const query: any = request.query;
const response: any = {
producers: []
};
if (query.mode === 'activated') {
const searchParams: Search<any> = {
track_total_hits: true,
index: fastify.manager.chain + "-block-*",
size: 1,
body: {
query: {bool: {must: []}},
sort: {block_num: query.sort || "desc"}
}
};
if (query.version) {
searchParams.body.query.bool.must.push({"term": {"new_producers.version": {"value": query.version}}});
} else {
searchParams.body.query.bool.must.push({"exists": {"field": "new_producers.version"}});
}
const apiResponse = await fastify.elastic.search(searchParams);
const results = apiResponse.body.hits.hits;
if (results && results.length > 0) {
response.timestamp = results[0]._source["@timestamp"];
response.block_num = results[0]._source.block_num;
response.version = results[0]._source.new_producers.version;
response.producers = results[0]._source.new_producers.producers.map(prod => {
return {
...prod,
legacy_key: convertToLegacyKey(prod.block_signing_key)
}
});
}
} else {
// default "proposed" mode
const searchParams: Search<any> = {
track_total_hits: true,
index: fastify.manager.chain + "-schedule-*",
size: 1,
body: {
query: {bool: {must: []}},
sort: {version: query.sort || "desc"}
}
};
if (query.version) {
searchParams.body.query.bool.must.push({"term": {"version": {"value": query.version}}});
} else {
searchParams.body.query.bool.must.push({"exists": {"field": "version"}});
}
if (query.producer) {
searchParams.body.query.bool.must.push({"term": {"producers.name": {"value": query.producer}}});
}
if (query.key) {
searchParams.body.query.bool.must.push({"term": {"producers.keys": {"value": query.key}}});
}
const apiResponse = await fastify.elastic.search(searchParams);
const results = apiResponse.body.hits.hits;
if (results && results.length > 0) {
response.timestamp = results[0]._source["@timestamp"];
response.proposal_block_num = results[0]._source.block_num;
response.version = results[0]._source.version;
response.producers = results[0]._source.producers;
// sort producers by name
// response.producers.sort((a, b) => {
// if (a.name < b.name) {
// return -1;
// }
// if (a.name > b.name) {
// return 1;
// }
// return 0;
// });
}
}
return response;
}
export function getScheduleHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getSchedule, fastify, request, route));
}
}
@@ -0,0 +1,68 @@
import {FastifyInstance} from "fastify";
import {addApiRoute, extendResponseSchema, getRouteName} from "../../../helpers/functions";
import {getScheduleHandler} from "./get_schedule";
export default function (fastify: FastifyInstance, opts: any, next) {
const schema = {
description: 'get producer schedule by version',
summary: 'get producer schedule by version',
tags: ['history'],
querystring: {
type: 'object',
properties: {
"producer": {
description: 'search by producer',
type: 'string'
},
"key": {
description: 'search by signing key',
type: 'string'
},
"after": {
description: 'filter after specified date (ISO8601)',
type: 'string'
},
"before": {
description: 'filter before specified date (ISO8601)',
type: 'string'
},
"mode": {
description: 'search mode (activated or proposed)',
type: 'string',
},
"version": {
description: 'schedule version',
type: 'integer',
minimum: 1
}
}
},
response: extendResponseSchema({
"timestamp": {type: "string"},
"block_num": {type: "number"},
"proposed_block_num": {type: "number"},
"version": {type: "number"},
"producers": {
type: "array",
items: {
type: "object",
properties: {
"producer_name": {type: "string"},
"name": {type: "string"},
"block_signing_key": {type: "string"},
"legacy_key": {type: "string"},
"keys": {type: "array", items: {type: "string"}}
}
}
},
})
};
addApiRoute(
fastify,
'GET',
getRouteName(__filename),
getScheduleHandler,
schema
);
next();
}
@@ -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,45 +1,128 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import {mergeActionMeta, timedQuery} from "../../../helpers/functions";
import {GetInfoResult} from "eosjs/dist/eosjs-rpc-interfaces";
async function getTransaction(fastify: FastifyInstance, request: FastifyRequest) {
const pResults = await Promise.all([
fastify.eosjs.rpc.get_info(),
fastify.elastic.search({
"index": fastify.manager.chain + '-action-*',
"body": {
"query": {
"bool": {
must: [
{term: {"trx_id": request.query.id.toLowerCase()}}
]
}
},
"sort": {
"global_sequence": "asc"
}
}
})
]);
const results = pResults[1];
const response = {
"trx_id": request.query.id,
"lib": pResults[0].last_irreversible_block_num,
"actions": []
};
const hits = results['body']['hits']['hits'];
if (hits.length > 0) {
for (let action of hits) {
action = action._source;
mergeActionMeta(action);
response.actions.push(action);
}
}
return response;
const redis = fastify.redis;
const query: any = request.query;
const trxId = query.id.toLowerCase();
const conf = fastify.manager.config;
const cachedData = await redis.hgetall('trx_' + trxId);
const response = {
query_time_ms: undefined,
executed: false,
cached: undefined,
cache_expires_in: undefined,
trx_id: query.id,
lib: undefined,
cached_lib: false,
actions: undefined,
generated: undefined,
error: undefined
};
let hits;
// build get_info request with caching
const $getInfo = new Promise<GetInfoResult>(resolve => {
const key = `${fastify.manager.chain}_get_info`;
fastify.redis.get(key).then(value => {
if (value) {
response.cached_lib = true;
resolve(JSON.parse(value));
} else {
fastify.eosjs.rpc.get_info().then(value1 => {
fastify.redis.set(key, JSON.stringify(value1), 'EX', 6);
response.cached_lib = false;
resolve(value1);
}).catch((reason) => {
console.log(reason);
response.error = 'failed to get last_irreversible_block_num'
resolve(null);
});
}
});
});
// reconstruct hits from cached data
if (cachedData && Object.keys(cachedData).length > 0) {
const gsArr = [];
for (let cachedDataKey in cachedData) {
gsArr.push(cachedData[cachedDataKey]);
}
gsArr.sort((a, b) => {
return a.global_sequence - b.global_sequence;
});
hits = gsArr.map(value => {
return {
_source: JSON.parse(value)
};
});
const promiseResults = await Promise.all([
redis.ttl('trx_' + trxId),
$getInfo
]);
response.cache_expires_in = promiseResults[0];
response.lib = promiseResults[1].last_irreversible_block_num;
}
// search on ES if cache is not present
if (!hits) {
const _size = conf.api.limits.get_trx_actions || 100;
const blockHint = parseInt(query.block_hint, 10);
let indexPattern = '';
if (blockHint) {
const idxPart = Math.ceil(blockHint / conf.settings.index_partition_size).toString().padStart(6, '0');
indexPattern = fastify.manager.chain + `-action-${conf.settings.index_version}-${idxPart}`;
} else {
indexPattern = fastify.manager.chain + '-action-*';
}
let pResults;
try {
// 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) {
response.error = 'no data near block_hint'
return response;
}
}
hits = pResults[1]['body']['hits']['hits'];
response.lib = pResults[0].last_irreversible_block_num;
}
if (hits.length > 0) {
let highestBlockNum = 0;
for (let action of hits) {
if (action._source.block_num > highestBlockNum) {
highestBlockNum = action._source.block_num;
}
}
response.actions = [];
for (let action of hits) {
if (action._source.block_num === highestBlockNum) {
mergeActionMeta(action._source);
response.actions.push(action._source);
}
}
response.executed = true;
}
return response;
}
export function getTransactionHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => {
reply.send(await timedQuery(getTransaction, fastify, request, route));
}
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getTransaction, fastify, request, route));
}
}
@@ -6,13 +6,17 @@ export default function (fastify: FastifyInstance, opts: any, next) {
const schema = {
description: 'get all actions belonging to the same transaction',
summary: 'get transaction by id',
tags: ['transactions', 'history'],
tags: ['history'],
querystring: {
type: 'object',
properties: {
"id": {
id: {
description: 'transaction id',
type: 'string'
},
block_hint: {
description: 'block hint to speed up tx recovery',
type: 'integer'
}
},
required: ["id"]
@@ -0,0 +1,128 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {mergeActionMeta, timedQuery} from "../../../helpers/functions";
async function getTransaction(fastify: FastifyInstance, request: FastifyRequest) {
const _size = fastify.manager.config.api.limits.get_trx_actions || 100;
const query: any = request.query;
let indexPattern = fastify.manager.chain + '-action-*';
if (query.hot_only) {
indexPattern = fastify.manager.chain + '-action';
}
const pResults = await Promise.all([
fastify.eosjs.rpc.get_info(),
fastify.elastic.search({
index: indexPattern,
size: _size,
body: {
query: {
bool: {
must: [
{term: {trx_id: query.id.toLowerCase()}}
]
}
},
sort: {
global_sequence: "asc"
}
}
}),
fastify.elastic.search({
index: fastify.manager.chain + '-gentrx-*',
size: _size,
body: {
query: {
bool: {
must: [
{term: {trx_id: query.id.toLowerCase()}}
]
}
}
}
})
]);
const results = pResults[1];
const genTrxRes = pResults[2];
const response = {
"executed": false,
"hot_only": false,
"trx_id": query.id,
"lib": pResults[0].last_irreversible_block_num,
"actions": [],
"generated": undefined
};
if (query.hot_only) {
response.hot_only = true;
}
const hits = results['body']['hits']['hits'];
if (hits.length > 0) {
// const producers = {};
// for (let hit of hits) {
// if (hit._source.producer) {
// if (producers[hit._source.producer]) {
// producers[hit._source.producer]++;
// } else {
// producers[hit._source.producer] = 1;
// }
// }
// }
// let useBlockNumber;
// if (Object.keys(producers).length > 1) {
// // multiple producers of the same tx id, forked actions are present, attempt to clean-up
// let trueProd = '';
// let highestActCount = 0;
// for (const prod in producers) {
// if (producers.hasOwnProperty(prod)) {
// if(producers[prod] === highestActCount) {
// useBlockNumber = true;
// } else if (producers[prod] > highestActCount) {
// highestActCount = producers[prod];
// trueProd = prod;
// }
// }
// }
// }
let highestBlockNum = 0;
for (let action of hits) {
if (action._source.block_num > highestBlockNum) {
highestBlockNum = action._source.block_num;
}
}
for (let action of hits) {
if (action._source.block_num === highestBlockNum) {
action = action._source;
mergeActionMeta(action);
response.actions.push(action);
}
}
response.executed = true;
}
const hits2 = genTrxRes['body']['hits']['hits'];
if (hits2 && hits2.length > 0) {
if (hits2[0]._source['@timestamp']) {
hits2[0]._source['timestamp'] = hits2[0]._source['@timestamp'];
delete hits2[0]._source['@timestamp'];
}
response.generated = hits2[0]._source;
}
return response;
}
export function getTransactionHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getTransaction, fastify, request, route));
}
}
@@ -0,0 +1,29 @@
import {FastifyInstance} from "fastify";
import {getTransactionHandler} from "./get_transaction_legacy";
import {addApiRoute, getRouteName} from "../../../helpers/functions";
export default function (fastify: FastifyInstance, opts: any, next) {
const schema = {
description: 'get all actions belonging to the same transaction',
summary: 'get transaction by id',
tags: ['history'],
querystring: {
type: 'object',
properties: {
"id": {
description: 'transaction id',
type: 'string'
}
},
required: ["id"]
}
};
addApiRoute(
fastify,
'GET',
getRouteName(__filename),
getTransactionHandler,
schema
);
next();
}
+22 -11
View File
@@ -1,42 +1,53 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import got from "got";
import {timedQuery} from "../../../helpers/functions";
async function getAccount(fastify: FastifyInstance, request: FastifyRequest) {
const query: any = request.query;
const response = {
query_time: null,
cached: false,
account: null,
actions: null,
total_actions: 0,
tokens: null,
links: null
};
const account = request.query.account;
const account = query.account;
const reqQueue = [];
reqQueue.push(fastify.eosjs.rpc.get_account(account));
try {
response.account = await fastify.eosjs.rpc.get_account(account);
} catch (e) {
throw new Error("Account not found!");
}
const localApi = `http://${fastify.manager.config.api.server_addr}:${fastify.manager.config.api.server_port}/v2`;
const getTokensApi = localApi + '/state/get_tokens';
const getActionsApi = localApi + '/history/get_actions';
const getLinksApi = localApi + '/state/get_links';
// fetch recent actions
reqQueue.push(got.get(`${getActionsApi}?account=${account}&limit=10`));
reqQueue.push(got.get(`${getActionsApi}?account=${account}&limit=20&noBinary=true&track=true`).json());
// fetch account tokens
reqQueue.push(got.get(`${getTokensApi}?account=${account}`));
reqQueue.push(got.get(`${getTokensApi}?account=${account}`).json());
// fetch account permission links
reqQueue.push(got.get(`${getLinksApi}?account=${account}`).json());
const results = await Promise.all(reqQueue);
response.account = results[0];
response.actions = JSON.parse(results[1].body).actions;
response.tokens = JSON.parse(results[2].body).tokens;
response.actions = results[0].actions;
response.total_actions = results[0].total.value;
response.tokens = results[1].tokens;
response.links = results[2].links;
return response;
}
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));
}
}
+47 -10
View File
@@ -1,21 +1,58 @@
import {FastifyInstance} from "fastify";
import {getAccountHandler} from "./get_account";
import {addApiRoute, getRouteName} from "../../../helpers/functions";
import {addApiRoute, extendQueryStringSchema, extendResponseSchema, getRouteName} from "../../../helpers/functions";
import {getActionResponseSchema} from "../../v2-history/get_actions";
export default function (fastify: FastifyInstance, opts: any, next) {
const schema = {
description: 'get account data',
summary: 'get account summary',
tags: ['accounts', 'state'],
querystring: {
type: 'object',
properties: {
"account": {
description: 'account name',
type: 'string'
}
tags: ['accounts'],
querystring: extendQueryStringSchema({
"account": {
description: 'account name',
type: 'string',
minLength: 1,
maxLength: 12
}
}
}, ["account"]),
response: extendResponseSchema({
"account": {
additionalProperties: true
},
"links": {
type: "array",
items: {
type: 'object',
properties: {
"timestamp": {type: "string"},
"permission": {type: "string"},
"code": {type: "string"},
"action": {type: "string"},
}
}
},
"tokens": {
type: "array",
items: {
type: 'object',
properties: {
"symbol": {type: "string"},
"precision": {type: "integer"},
"amount": {type: "number"},
"contract": {type: "string"},
}
}
},
"total_actions": {type: "number"},
"actions": {
type: "array",
items: {
type: 'object',
properties: getActionResponseSchema
}
},
})
};
addApiRoute(
fastify,
@@ -1,85 +1,139 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import {timedQuery} from "../../../helpers/functions";
import {Numeric} from "eosjs/dist";
import {getSkipLimit} from "../../v2-history/get_actions/functions";
function invalidKey() {
const err: any = new Error();
err.statusCode = 400;
err.message = 'invalid public key';
throw err;
const err: any = new Error();
err.statusCode = 400;
err.message = 'invalid public key';
throw err;
}
async function getKeyAccounts(fastify: FastifyInstance, request: FastifyRequest) {
let publicKey;
const public_Key = request.req.method === 'POST' ? request.body.public_key : request.query.public_key;
let publicKey;
if (typeof request.body === 'string' && request.method === 'POST') {
request.body = JSON.parse(request.body);
}
if (public_Key.startsWith("PUB_")) {
publicKey = public_Key;
} else if (public_Key.startsWith("EOS")) {
try {
publicKey = Numeric.convertLegacyPublicKey(public_Key);
} catch (e) {
console.log(e);
invalidKey();
}
} else {
invalidKey();
}
const body: any = request.body;
const query: any = request.query;
const _body = {
query: {
bool: {
should: [
{term: {"@updateauth.auth.keys.key.keyword": publicKey}},
{term: {"@newaccount.active.keys.key.keyword": publicKey}},
{term: {"@newaccount.owner.keys.key.keyword": publicKey}}
],
minimum_should_match: 1
}
},
sort: [{"global_sequence": {"order": "desc"}}]
};
const {skip, limit} = getSkipLimit(request.query);
const maxDocs = fastify.manager.config.api.limits.get_key_accounts ?? 1000;
const results = await fastify.elastic.search({
index: fastify.manager.chain + '-action-*',
body: _body
});
const public_Key = request.method === 'POST' ? body.public_key : query.public_key;
const response = {
account_names: []
};
if (results['body']['hits']['hits'].length > 0) {
response.account_names = results['body']['hits']['hits'].map((v) => {
if (v._source.act.name === 'newaccount') {
if (v._source['@newaccount'].newact) {
return v._source['@newaccount'].newact;
} else if (v._source.act.data.newact) {
return v._source.act.data.newact;
} else {
return null;
}
} else if (v._source.act.name === 'updateauth') {
return v._source.act.data.account;
} else {
return null;
}
});
}
if (response.account_names.length > 0) {
response.account_names = [...(new Set(response.account_names))];
return response;
} else {
const err: any = new Error();
err.statusCode = 404;
err.message = 'no accounts associated with ' + public_Key;
throw err;
}
if (public_Key.startsWith("PUB_")) {
publicKey = public_Key;
} else if (public_Key.startsWith("EOS")) {
try {
publicKey = Numeric.convertLegacyPublicKey(public_Key);
} catch (e) {
console.log(e.message);
invalidKey();
}
} else {
invalidKey();
}
const response = {
account_names: []
} as any;
if (request.method === 'GET' && query.details) {
response.permissions = [];
}
try {
const permTableResults = await fastify.elastic.search({
index: fastify.manager.chain + '-perm-*',
size: (limit > maxDocs ? maxDocs : limit) || 100,
from: skip || 0,
body: {
query: {
bool: {
must: [
{
term: {
"auth.keys.key.keyword": publicKey
}
}
],
}
}
}
});
if (permTableResults.body.hits.hits.length > 0) {
for (const perm of permTableResults.body.hits.hits) {
response.account_names.push(perm._source.owner);
if (request.method === 'GET' && query.details) {
response.permissions.push(perm._source);
}
}
}
} catch (e) {
console.log(e);
}
if (response.account_names.length > 0) {
response.account_names = [...(new Set(response.account_names))];
return response;
}
// Fallback to action search
const _body = {
query: {
bool: {
should: [
{term: {"@updateauth.auth.keys.key.keyword": publicKey}},
{term: {"@newaccount.active.keys.key.keyword": publicKey}},
{term: {"@newaccount.owner.keys.key.keyword": publicKey}}
],
minimum_should_match: 1
}
},
sort: [{"global_sequence": {"order": "desc"}}]
};
const results = await fastify.elastic.search({
index: fastify.manager.chain + '-action-*',
size: (limit > maxDocs ? maxDocs : limit) || 100,
from: skip || 0,
body: _body
});
if (results['body']['hits']['hits'].length > 0) {
response.account_names = results['body']['hits']['hits'].map((v) => {
if (v._source.act.name === 'newaccount') {
if (v._source['@newaccount'].newact) {
return v._source['@newaccount'].newact;
} else if (v._source.act.data.newact) {
return v._source.act.data.newact;
} else {
return null;
}
} else if (v._source.act.name === 'updateauth') {
return v._source.act.data.account;
} else {
return null;
}
});
}
if (response.account_names.length > 0) {
response.account_names = [...(new Set(response.account_names))];
}
return response;
}
export function getKeyAccountsHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => {
reply.send(await timedQuery(getKeyAccounts, fastify, request, route));
}
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getKeyAccounts, fastify, request, route));
}
}
+25 -12
View File
@@ -1,6 +1,6 @@
import {FastifyInstance} from "fastify";
import {getKeyAccountsHandler} from "./get_key_accounts";
import {addApiRoute, getRouteName} from "../../../helpers/functions";
import {addApiRoute, extendQueryStringSchema, getRouteName} from "../../../helpers/functions";
export default function (fastify: FastifyInstance, opts: any, next) {
@@ -8,25 +8,38 @@ export default function (fastify: FastifyInstance, opts: any, next) {
const getSchema = {
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'
},
tags: ['accounts'],
querystring: extendQueryStringSchema({
"public_key": {
description: 'public key',
type: 'string'
},
required: ["public_key"]
},
"details": {
description: 'include permission details',
type: 'boolean'
},
}, ["public_key"]),
response: {
200: {
type: 'object',
properties: {
"account_names": {
type: "array",
items: {type: "string"}
},
"permissions": {
type: "array",
items: {
type: "string"
type: "object",
properties: {
owner: {type: 'string'},
block_num: {type: 'integer'},
parent: {type: 'string'},
last_updated: {type: 'string'},
auth: {},
name: {type: 'string'},
present: {type: 'number'}
}
}
}
}
+54 -50
View File
@@ -1,67 +1,71 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import {getTrackTotalHits, timedQuery} from "../../../helpers/functions";
import {ApiResponse} from "@elastic/elasticsearch";
import {getSkipLimit} from "../../v2-history/get_actions/functions";
async function getLinks(fastify: FastifyInstance, request: FastifyRequest) {
const query = request.query;
const {account, code, action, permissions} = query;
const {skip, limit} = getSkipLimit(query);
const query: any = request.query;
const {account, code, action, permissions} = query;
const {skip, limit} = getSkipLimit(query);
const queryStruct = {
"bool": {
must: []
}
};
const queryStruct = {
"bool": {
must: [],
must_not: []
}
};
if (account) {
queryStruct.bool.must.push({'term': {'account': account}});
}
if (account) {
queryStruct.bool.must.push({'term': {'account': account}});
}
if (code) {
queryStruct.bool.must.push({'term': {'code': code}});
}
if (code) {
queryStruct.bool.must.push({'term': {'code': code}});
}
if (action) {
queryStruct.bool.must.push({'term': {'action': action}});
}
if (action) {
queryStruct.bool.must.push({'term': {'action': action}});
}
if (permissions) {
queryStruct.bool.must.push({'term': {'permissions': permissions}});
}
if (permissions) {
queryStruct.bool.must.push({'term': {'permissions': permissions}});
}
// Prepare query body
const query_body = {
track_total_hits: getTrackTotalHits(query),
query: queryStruct,
sort: {block_num: 'desc'}
};
// only present deltas
queryStruct.bool.must_not.push({'term': {'present': 0}});
const maxLinks = fastify.manager.config.api.limits.get_links;
const results: ApiResponse = await fastify.elastic.search({
index: fastify.manager.chain + '-link-*',
from: skip || 0,
size: (limit > maxLinks ? maxLinks : limit) || 10,
body: query_body
});
const hits = results.body.hits.hits;
const response: any = {
cached: false,
total: results.body.hits.total,
links: []
};
for (const hit of hits) {
const link = hit._source;
link.timestamp = link['@timestamp'];
delete link['@timestamp'];
response.links.push(link);
}
return response;
// Prepare query body
const query_body = {
track_total_hits: getTrackTotalHits(query),
query: queryStruct,
sort: {block_num: 'desc'}
};
const maxLinks = fastify.manager.config.api.limits.get_links;
const results: ApiResponse = await fastify.elastic.search({
index: fastify.manager.chain + '-link-*',
from: skip || 0,
size: (limit > maxLinks ? maxLinks : limit) || 50,
body: query_body
});
const hits = results.body.hits.hits;
const response: any = {
cached: false,
total: results.body.hits.total,
links: []
};
for (const hit of hits) {
const link = hit._source;
link.timestamp = link['@timestamp'];
delete link['@timestamp'];
response.links.push(link);
}
return response;
}
export function getLinksHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => {
reply.send(await timedQuery(getLinks, fastify, request, route));
}
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getLinks, fastify, request, route));
}
}
+1 -1
View File
@@ -6,7 +6,7 @@ export default function (fastify: FastifyInstance, opts: any, next) {
const schema = {
description: 'get permission links',
summary: 'get permission links',
tags: ['accounts', 'state'],
tags: ['accounts'],
querystring: {
type: 'object',
properties: {
@@ -1,16 +1,17 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import {getTrackTotalHits, timedQuery} from "../../../helpers/functions";
async function getProposals(fastify: FastifyInstance, request: FastifyRequest) {
const query: any = request.query;
// Pagination
let skip, limit;
skip = parseInt(request.query.skip, 10);
skip = parseInt(query.skip, 10);
if (skip < 0) {
return 'invalid skip parameter';
}
limit = parseInt(request.query.limit, 10);
limit = parseInt(query.limit, 10);
if (limit < 1) {
return 'invalid limit parameter';
}
@@ -21,41 +22,44 @@ async function getProposals(fastify: FastifyInstance, request: FastifyRequest) {
}
};
// Filter by account
if (request.query.account) {
queryStruct.bool.must.push({
"bool": {
"should": [
{"term": {"requested_approvals.actor": request.query.account}},
{"term": {"provided_approvals.actor": request.query.account}}
]
}
});
// Filter by accounts
if (query.account) {
const accounts = query.account.split(',');
for(const acc of accounts) {
queryStruct.bool.must.push({
"bool": {
"should": [
{"term": {"requested_approvals.actor": acc}},
{"term": {"provided_approvals.actor": acc}}
]
}
});
}
}
// Filter by proposer account
if (request.query.proposer) {
queryStruct.bool.must.push({"term": {"proposer": request.query.proposer}});
if (query.proposer) {
queryStruct.bool.must.push({"term": {"proposer": query.proposer}});
}
// Filter by proposal name
if (request.query.proposal) {
queryStruct.bool.must.push({"term": {"proposal_name": request.query.proposal}});
if (query.proposal) {
queryStruct.bool.must.push({"term": {"proposal_name": query.proposal}});
}
// Filter by execution status
if (typeof request.query.executed !== 'undefined') {
queryStruct.bool.must.push({"term": {"executed": request.query.executed}});
if (typeof query.executed !== 'undefined') {
queryStruct.bool.must.push({"term": {"executed": query.executed}});
}
// Filter by requested actors
if (request.query.requested) {
queryStruct.bool.must.push({"term": {"requested_approvals.actor": request.query.requested}});
if (query.requested) {
queryStruct.bool.must.push({"term": {"requested_approvals.actor": query.requested}});
}
// Filter by provided actors
if (request.query.provided) {
queryStruct.bool.must.push({"term": {"provided_approvals.actor": request.query.provided}});
if (query.provided) {
queryStruct.bool.must.push({"term": {"provided_approvals.actor": query.provided}});
}
// If no filter switch to full match
@@ -94,7 +98,7 @@ async function getProposals(fastify: FastifyInstance, request: FastifyRequest) {
}
export function getProposalsHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => {
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getProposals, fastify, request, route));
}
}
+1 -1
View File
@@ -6,7 +6,7 @@ export default function (fastify: FastifyInstance, opts: any, next) {
const schema = {
description: 'get proposals',
summary: 'get proposals',
tags: ['state'],
tags: ['system'],
querystring: {
type: 'object',
properties: {
+62 -40
View File
@@ -1,68 +1,90 @@
import {ServerResponse} from "http";
import {timedQuery} from "../../../helpers/functions";
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {getSkipLimit} from "../../v2-history/get_actions/functions";
async function getTokens(fastify: FastifyInstance, request: FastifyRequest) {
const query: any = request.query;
const response = {
query_time: null,
cached: false,
'account': request.query.account,
'tokens': []
account: query.account,
tokens: []
};
const results = await fastify.elastic.search({
"index": fastify.manager.chain + '-action-*',
const {skip, limit} = getSkipLimit(request.query);
const maxDocs = fastify.manager.config.api.limits.get_tokens ?? 100;
const stateResult = await fastify.elastic.search({
"index": fastify.manager.chain + '-table-accounts-*',
"size": (limit > maxDocs ? maxDocs : limit) || 50,
"from": skip || 0,
"body": {
size: 0,
query: {
bool: {
filter: [
{term: {"notified": request.query.account}},
{terms: {"act.name": ["transfer", "issue"]}}
]
}
},
aggs: {
tokens: {
terms: {
field: "act.account",
size: 1000
}
filter: [{term: {scope: query.account}}]
}
}
}
});
for (const bucket of results['body']['aggregations']['tokens']['buckets']) {
let token_data;
try {
token_data = await fastify.eosjs.rpc.get_currency_balance(bucket['key'], request.query.account);
} catch (e) {
console.log(`get_currency_balance error - contract:${bucket['key']} - account:${request.query.account}`);
const testSet = new Set();
for (const hit of stateResult.body.hits.hits) {
const data = hit._source;
if (typeof data.present !== "undefined" && data.present === 0) {
continue;
}
for (const entry of token_data) {
let precision = 0;
const [amount, symbol] = entry.split(" ");
const amount_arr = amount.split(".");
if (amount_arr.length === 2) {
precision = amount_arr[1].length;
}
response.tokens.push({
symbol: symbol,
precision: precision,
amount: parseFloat(amount),
contract: bucket['key']
});
let precision;
let token_data;
let errorMsg;
const key = `${data.code}_${data.symbol}`;
if (testSet.has(key)) {
continue;
}
testSet.add(key);
if (!fastify.tokenCache) {
fastify.tokenCache = new Map<string, any>();
}
if (fastify.tokenCache.has(key)) {
precision = fastify.tokenCache.get(key).precision;
} else {
try {
token_data = await fastify.eosjs.rpc.get_currency_balance(data.code, query.account, data.symbol);
if (token_data.length > 0) {
const [amount, symbol] = token_data[0].split(" ");
const amount_arr = amount.split(".");
if (amount_arr.length === 2) {
precision = amount_arr[1].length;
fastify.tokenCache.set(key, {precision});
}
}
} catch (e) {
errorMsg = e.message;
}
}
const resp: Record<string, any> = {
symbol: data.symbol,
precision: precision,
amount: parseFloat(data.amount),
contract: data.code,
};
if (errorMsg) {
resp.error = errorMsg;
}
response.tokens.push(resp);
}
return response;
}
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));
}
}
+11 -12
View File
@@ -1,21 +1,20 @@
import {FastifyInstance} from "fastify";
import {addApiRoute, getRouteName} from "../../../helpers/functions";
import {addApiRoute, extendQueryStringSchema, getRouteName} from "../../../helpers/functions";
import {getTokensHandler} from "./get_tokens";
export default function (fastify: FastifyInstance, opts: any, next) {
const schema = {
description: 'get account data',
summary: 'get account summary',
tags: ['accounts', 'state'],
querystring: {
type: 'object',
properties: {
"account": {
description: 'account name',
type: 'string'
}
description: 'get tokens from an account',
summary: 'get all tokens',
tags: ['accounts'],
querystring: extendQueryStringSchema({
"account": {
description: 'account name',
type: 'string',
minLength: 1,
maxLength: 12
}
}
}, ["account"])
};
addApiRoute(
fastify,
+49 -58
View File
@@ -1,72 +1,63 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {ServerResponse} from "http";
import {timedQuery} from "../../../helpers/functions";
import {getSkipLimit} from "../../v2-history/get_actions/functions";
async function getVoters(fastify: FastifyInstance, request: FastifyRequest) {
let skip, limit;
const query: any = request.query;
const {skip, limit} = getSkipLimit(request.query);
skip = parseInt(request.query.skip, 10);
if (skip < 0) {
return 'invalid skip parameter';
}
limit = parseInt(request.query.limit, 10);
if (limit < 1) {
return 'invalid limit parameter';
}
const response = {
voter_count: 0,
'voters': []
};
let queryStruct: any = {
"bool": {
"must": []
}
};
const response = {
query_time: null,
voter_count: 0,
'voters': []
};
let queryStruct: any = {
"bool": {
"must": []
}
};
if (query.producer) {
for (const bp of query.producer.split(",")) {
queryStruct.bool.must.push({"term": {"producers": bp}});
}
}
if (request.query.producer) {
for (const bp of request.query.producer.split(",")) {
queryStruct.bool.must.push({"term": {"producers": bp}});
}
}
if (query.proxy === 'true') {
queryStruct.bool.must.push({"term": {"is_proxy": true}});
}
if (request.query.proxy === 'true') {
queryStruct.bool.must.push({"term": {"is_proxy": true}});
}
if (queryStruct.bool.must.length === 0) {
queryStruct = {
"match_all": {}
};
}
if (queryStruct.bool.must.length === 0) {
queryStruct = {
"match_all": {}
};
}
const maxDocs = fastify.manager.config.api.limits.get_voters ?? 100;
const results = await fastify.elastic.search({
"index": fastify.manager.chain + '-table-voters-*',
"from": skip || 0,
"size": (limit > maxDocs ? maxDocs : limit) || 10,
"body": {
"query": queryStruct,
"sort": [{"last_vote_weight": "desc"}]
}
});
const hits = results['body']['hits']['hits'];
for (const hit of hits) {
const voter = hit._source;
response.voters.push({
account: voter.voter,
weight: voter.last_vote_weight,
last_vote: voter.block_num
});
}
response.voter_count = results['body']['hits']['total']['value'];
return response;
const maxDocs = fastify.manager.config.api.limits.get_voters ?? 100;
const results = await fastify.elastic.search({
"index": fastify.manager.chain + '-table-voters-*',
"from": skip || 0,
"size": (limit > maxDocs ? maxDocs : limit) || 10,
"body": {
"query": queryStruct,
"sort": [{"last_vote_weight": "desc"}]
}
});
const hits = results['body']['hits']['hits'];
for (const hit of hits) {
const voter = hit._source;
response.voters.push({
account: voter.voter,
weight: voter.last_vote_weight,
last_vote: voter.block_num
});
}
response.voter_count = results['body']['hits']['total']['value'];
return response;
}
export function getVotersHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply<ServerResponse>) => {
reply.send(await timedQuery(getVoters, fastify, request, route));
}
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getVoters, fastify, request, route));
}
}
+24 -19
View File
@@ -1,31 +1,36 @@
import {FastifyInstance} from "fastify";
import {getVotersHandler} from "./get_voters";
import {addApiRoute, getRouteName} from "../../../helpers/functions";
import {addApiRoute, extendQueryStringSchema, extendResponseSchema, getRouteName} from "../../../helpers/functions";
export default function (fastify: FastifyInstance, opts: any, next) {
const schema = {
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
tags: ['system'],
querystring: extendQueryStringSchema({
"producer": {
description: 'filter by voted producer (comma separated)',
type: 'string',
minLength: 1,
maxLength: 12
}
}),
response: extendResponseSchema({
"voters": {
type: "array",
items: {
type: "object",
properties: {
"account": {type: "string"},
"weight": {type: "number"},
"last_vote": {type: "number"},
"data": {
additionalProperties: true
}
}
}
}
}
})
};
addApiRoute(
fastify,
@@ -0,0 +1,120 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {timedQuery} from "../../../helpers/functions";
async function getLastSeq(fastify: FastifyInstance, date: string) {
const req = await fastify.elastic.search({
index: fastify.manager.chain + '-action-*',
body: {
"size": 1,
"query": {
"bool": {
"must": [
{"range": {"@timestamp": {"lt": date}}}
]
}
},
"_source": {
"includes": ["global_sequence"]
},
"sort": [{"global_sequence": "desc"}]
}
});
return req.body.hits.hits[0]._source.global_sequence;
}
async function getTxCount(fastify: FastifyInstance, dateFrom: string, dateTo: string) {
const req = await fastify.elastic.count({
index: fastify.manager.chain + '-action-*',
body: {
"query": {
"bool": {
"must": [
{"term": {"creator_action_ordinal": 0}},
{"range": {"@timestamp": {"gt": dateFrom, "lt": dateTo}}}
]
}
}
}
});
return req.body.count;
}
async function getUniqueActors(fastify: FastifyInstance, dateFrom: string, dateTo: string) {
const req = await fastify.elastic.search({
index: fastify.manager.chain + '-action-*',
size: 0,
body: {
"aggs": {
"unique_actors": {
"cardinality": {
"field": "act.authorization.actor"
}
}
},
"query": {
"bool": {
"filter": [{"range": {"@timestamp": {"gt": dateFrom, "lt": dateTo}}}]
}
}
}
});
return req.body.aggregations.unique_actors.value;
}
async function getActionUsage(fastify: FastifyInstance, request: FastifyRequest) {
const query: any = request.query;
let now = new Date();
let expiration = 86400;
if (query.end_date) {
now = new Date(query.end_date);
}
now.setMinutes(0, 0, 0);
let period;
if (query.period === '1h') {
period = 3600000;
} else if (query.period === '24h' || query.period === '1d') {
period = 86400000;
} else {
return {};
}
if (!query.end_date) {
expiration = Math.floor((period - (Date.now() - now.getTime())) / 1000);
}
const response = {} as any;
const periodStart = new Date(now.getTime() - period);
const pStart = periodStart.toISOString();
const pEnd = now.toISOString();
const cacheKey = `${fastify.manager.chain}_act_stats_${pStart}_${pEnd}_${query.unique_actors}`;
const cachedValue = await fastify.redis.get(cacheKey);
if (cachedValue) {
const cachedResponse = JSON.parse(cachedValue);
cachedResponse.cached = true;
cachedResponse.cache_exp = expiration;
return cachedResponse;
}
const firstReq = await getLastSeq(fastify, pStart);
const lastReq = await getLastSeq(fastify, pEnd);
response.action_count = lastReq - firstReq;
response.tx_count = await getTxCount(fastify, pStart, pEnd);
if (query.unique_actors) {
response.unique_actors = await getUniqueActors(fastify, pStart, pEnd);
}
response.period = query.period;
response.from = pStart;
response.to = pEnd;
await fastify.redis.set(cacheKey, JSON.stringify(response), 'EX', expiration);
return response;
}
export function getActionUsageHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getActionUsage, fastify, request, route));
}
}
@@ -0,0 +1,37 @@
import {FastifyInstance} from "fastify";
import {addApiRoute, getRouteName} from "../../../helpers/functions";
import {getActionUsageHandler} from "./get_action_usage";
export default function (fastify: FastifyInstance, opts: any, next) {
const schema = {
description: 'get action and transaction stats for a given period',
summary: 'get action and transaction stats for a given period',
tags: ['stats'],
querystring: {
type: 'object',
required: ["period"],
properties: {
"period": {
description: 'analysis period',
type: 'string'
},
"end_date": {
description: 'final date',
type: 'string'
},
"unique_actors": {
description: 'compute unique actors',
type: 'boolean'
}
}
}
};
addApiRoute(
fastify,
'GET',
getRouteName(__filename),
getActionUsageHandler,
schema
);
next();
}
@@ -0,0 +1,53 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {timedQuery} from "../../../helpers/functions";
import {parseInt} from "lodash";
async function getApiUsage(fastify: FastifyInstance, request: FastifyRequest) {
const response = {
total: {responses: {}},
buckets: []
} as any;
const data = await fastify.redis.keys(`stats:${fastify.manager.chain}:*`);
const buckets = [];
for (const key of data) {
const parts = key.split(':');
if (parts[2] === 'H') {
const bucket = {
timestamp: new Date(parseInt(parts[3])),
responses: {}
};
const sortedSet = await fastify.redis.zrange(key, 0, -1, 'WITHSCORES');
for (let i = 0; i < sortedSet.length; i += 2) {
const [status, path] = sortedSet[i].split(']');
if (status) {
const statusCode = status.slice(1);
if (!bucket.responses[statusCode]) {
bucket.responses[statusCode] = {};
}
if (statusCode) {
bucket.responses[statusCode][path] = parseInt(sortedSet[i + 1]);
}
}
}
response.buckets.push(bucket);
} else {
if (parts[2] === 'total' && parts[4]) {
const statusCode = parts[3];
if (!response.total.responses[statusCode]) {
response.total.responses[statusCode] = {};
}
const value = await fastify.redis.get(key);
response.total.responses[statusCode][parts[4]] = parseInt(value);
}
}
}
// reverse sorte by date
response.buckets.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
return response;
}
export function getApiUsageHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getApiUsage, fastify, request, route));
}
}
@@ -0,0 +1,41 @@
import {FastifyInstance} from "fastify";
import {addApiRoute, extendResponseSchema, getRouteName} from "../../../helpers/functions";
import {getApiUsageHandler} from "./get_api_usage";
export default function (fastify: FastifyInstance, opts: any, next) {
const schema = {
description: 'get hyperion api usage statistics',
summary: 'get hyperion api usage statistics',
tags: ['stats'],
response: extendResponseSchema({
total: {
type: 'object',
properties: {
responses: {
additionalProperties: true
}
}
},
buckets: {
type: 'array',
items: {
type: 'object',
properties: {
timestamp: {type: 'string'},
responses: {
additionalProperties: true
}
}
}
}
})
};
addApiRoute(
fastify,
'GET',
getRouteName(__filename),
getApiUsageHandler,
schema
);
next();
}
@@ -0,0 +1,64 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {timedQuery} from "../../../helpers/functions";
import {Search} from "@elastic/elasticsearch/api/requestParams";
import {applyTimeFilter} from "../../v2-history/get_actions/functions";
async function getMissedBlocks(fastify: FastifyInstance, request: FastifyRequest) {
const query: any = request.query;
const response = {
stats: {
by_producer: {}
},
events: []
};
const searchParams: Search<any> = {
track_total_hits: true,
index: fastify.manager.chain + "-logs-*",
size: 100,
body: {
query: {
bool: {
must: [
{term: {"type": "missed_blocks"}}
]
}
},
sort: {
"@timestamp": "desc"
}
}
};
let minBlocks = 0;
if (query.min_blocks) {
minBlocks = parseInt(query.min_blocks);
searchParams.body.query.bool.must.push({range: {"missed_blocks.size": {gte: minBlocks}}})
}
if (query.producer) {
searchParams.body.query.bool.must.push({term: {"missed_blocks.producer": query.producer}});
}
applyTimeFilter(query, searchParams.body.query);
const apiResponse = await fastify.elastic.search(searchParams);
apiResponse.body.hits.hits.forEach(v => {
const ev = v._source;
response.events.push({
"@timestamp": ev["@timestamp"],
...ev.missed_blocks
});
if (!response.stats.by_producer[ev.missed_blocks.producer]) {
response.stats.by_producer[ev.missed_blocks.producer] = ev.missed_blocks.size;
} else {
response.stats.by_producer[ev.missed_blocks.producer] += ev.missed_blocks.size;
}
});
return response;
}
export function getMissedBlocksHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getMissedBlocks, fastify, request, route));
}
}
@@ -0,0 +1,62 @@
import {FastifyInstance} from "fastify";
import {addApiRoute, extendResponseSchema, getRouteName} from "../../../helpers/functions";
import {getMissedBlocksHandler} from "./get_missed_blocks";
export default function (fastify: FastifyInstance, opts: any, next) {
const schema = {
description: 'get missed blocks',
summary: 'get missed blocks',
tags: ['stats'],
querystring: {
type: 'object',
properties: {
"producer": {
description: 'filter by producer',
type: 'string'
},
"after": {
description: 'filter after specified date (ISO8601)',
type: 'string'
},
"before": {
description: 'filter before specified date (ISO8601)',
type: 'string'
},
"min_blocks": {
description: 'min. blocks threshold',
type: 'integer',
minimum: 1
}
}
},
response: extendResponseSchema({
"stats": {
type: "object",
properties: {
"by_producer": {}
}
},
"events": {
type: "array",
items: {
type: "object",
properties: {
"@timestamp": {type: "string"},
"last_block": {type: "number"},
"schedule_version": {type: "number"},
"size": {type: "number"},
"producer": {type: "string"},
}
}
},
})
};
addApiRoute(
fastify,
'GET',
getRouteName(__filename),
getMissedBlocksHandler,
schema
);
next();
}
@@ -0,0 +1,75 @@
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import {timedQuery} from "../../../helpers/functions";
const percentiles = [1, 5, 25, 50, 75, 95, 99];
async function getResourceUsage(fastify: FastifyInstance, request: FastifyRequest) {
const query: any = request.query;
const searchBody: any = {
query: {
bool: {
must: [
{term: {"act.account": query.code}},
{term: {"act.name": query.action}},
{term: {"action_ordinal": {"value": 1}}},
{
range: {
"@timestamp": {
gte: "now-2d/d",
lt: "now/d"
}
}
}
]
}
},
track_total_hits: true,
size: 0,
aggs: {
cpu_extended_stats: {extended_stats: {field: "cpu_usage_us"}},
net_extended_stats: {extended_stats: {field: "net_usage_words"}},
cpu_percentiles: {
percentiles: {
field: "cpu_usage_us",
percents: percentiles
}
},
net_percentiles: {
percentiles: {
field: "net_usage_words",
percents: percentiles
}
}
}
};
if (request.query['@transfer.to']) {
searchBody.query.bool.must.push({term: {"@transfer.to": request.query['@transfer.to']}});
}
const results = await fastify.elastic.search({
index: fastify.manager.chain + "-action-*",
body: searchBody
});
if (results.body) {
const data = results.body.aggregations;
return {
cpu: {
stats: data.cpu_extended_stats,
percentiles: data.cpu_percentiles.values
},
net: {
stats: data.net_extended_stats,
percentiles: data.net_percentiles.values
}
};
} else {
return {};
}
}
export function getResourceUsageHandler(fastify: FastifyInstance, route: string) {
return async (request: FastifyRequest, reply: FastifyReply) => {
reply.send(await timedQuery(getResourceUsage, fastify, request, route));
}
}
@@ -0,0 +1,33 @@
import {FastifyInstance} from "fastify";
import {addApiRoute, getRouteName} from "../../../helpers/functions";
import {getResourceUsageHandler} from "./get_resource_usage";
export default function (fastify: FastifyInstance, opts: any, next) {
const schema = {
description: 'get resource usage stats for a specific action',
summary: 'get resource usage stats for a specific action',
tags: ['stats'],
querystring: {
type: 'object',
required: ["code", "action"],
properties: {
"code": {
description: 'contract',
type: 'string'
},
"action": {
description: 'action name',
type: 'string'
}
}
}
};
addApiRoute(
fastify,
'GET',
getRouteName(__filename),
getResourceUsageHandler,
schema
);
next();
}

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