-
Notifications
You must be signed in to change notification settings - Fork 12
Description
fibos-tracker 使用指南
什么是 fibos-tracker
- fibos-tracker 是一个 FIBOS 区块链数据 API 服务框架,基于 fib-app 框架实现
- 提供对 FIBOS 区块数据的 emitter 监听事件
- 提供 http 服务,支持 GraphQL 调用
- 支持使用 ORM 模型 定制自己的数据模型 model,自定义数据表以及自定义 hook 监听数据
为什么使用 fibos-tracker
在 FIBOS 中,进行链下数据存储的方式有两种:
1 配合 history 插件进行链上查询。
2 使用 mongodb 插件将链上数据存储到 mongodb 中。
但是这两个插件在日常使用中都存在少许问题,比如 history 插件需要配合高配置的服务器使用,而 mongodb 的数据不能够定制化存储,数据查询起来也非常费时。而 fibos-tracker 的出现就是为了解决 history 插件和 mongodb 插件的链下数据存储问题。
fibos-tracker 配合 fibos 独有的 emitter 插件,能够将链上的数据实时推送到客户端。由于 emitter 插件只做链上数据推送功能,开启后并不会增大节点的性能消耗,同时在 fibos-tracker 得到链上推送的数据后可以根据业务的需求,自定义数据存储引擎(Mysql、SQLite)、自定义数据存储。
如何使用 fibos-tracker
1 环境支持
fibos 版本: v1.3.1.7+
- 快速安装:
curl -s https://fibos.io/download/installer.sh | sh
存储支持:Mysql、SQLite
2 安装 fibos-tracker
fibos --init
fibos --install fibos-tracker
3 全局配置
| 配置名 | 描述 | 默认值 |
|---|---|---|
| DBconnString | 数据存储引擎配置 | 使用 SQLite 存储引擎 |
| isFilterNullBlock | 是否过滤空块 | true |
| isSyncSystemBlock | 是否存储默认数据 | false |
实例:
const Tracker = require("fibos-tracker");
Tracker.Config.DBconnString = "mysql://root:123456@127.0.0.1/fibos_chain"; //使用Mysql数据存储引擎
Tracker.Config.isFilterNullBlock = false; //存储空块
Tracker.Config.isSyncSystemBlock = true; //存储默认数据
一个小栗子
学习了解 fibos-trakcer 之后,让我们开始动手编写一个可以定制同步 FIBOS TESTNET 网络区块数据的应用。
-
代码目录结构如下:
├── genesis.json //genesis 文件 ├── hooks.js //自定义存储数据文件 ├── index.js //节点配置文件 ├── server.js //http 服务文件 ├── node_modules └── package.json实例代码地址: fibos-tracker-demo
-
index.js配置测试网启动节点代码如下:const fibos = require("fibos"); const Tracker = require("fibos-tracker"); const CONFIG = { node_dir: "./node", DBconnString: "mysql://root:123456@127.0.0.1/chain_data" } fibos.config_dir = CONFIG.node_dir; fibos.data_dir = CONFIG.node_dir; fibos.load("http", { "http-server-address": "0.0.0.0:8871", "access-control-allow-origin": "*", "http-validate-host": false, "verbose-http-errors": true }); fibos.load("net", { 'p2p-listen-endpoint': "0.0.0.0:9876", "p2p-peer-address": ["p2p.testnet.fo:80"] }); fibos.load("producer"); fibos.load("chain", { "delete-all-blocks": true, "genesis-json": "./genesis.json" }); fibos.load("chain_api"); fibos.load("emitter"); Tracker.Config.DBconnString = CONFIG.DBconnString; const tracker = new Tracker(); tracker.use(require("./hooks.js")); tracker.emitter(fibos); fibos.start();CONFIG 内的存在两个配置: node_dir: 区块数据存储位置,DBconnString: 数据存储连接字符串。
-
genesis.json文件:{ "initial_timestamp": "2018-08-01T00:00:00.000", "initial_key": "FO6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV", "initial_configuration": { "max_block_net_usage": 1048576, "target_block_net_usage_pct": 1000, "max_transaction_net_usage": 524288, "base_per_transaction_net_usage": 12, "net_usage_leeway": 500, "context_free_discount_net_usage_num": 20, "context_free_discount_net_usage_den": 100, "max_block_cpu_usage": 200000, "target_block_cpu_usage_pct": 1000, "max_transaction_cpu_usage": 150000, "min_transaction_cpu_usage": 100, "max_transaction_lifetime": 3600, "deferred_trx_expiration_window": 600, "max_transaction_delay": 3888000, "max_inline_action_size": 4096, "max_inline_action_depth": 4, "max_authority_depth": 6 }, "initial_chain_id": "68cee14f598d88d340b50940b6ddfba28c444b46cd5f33201ace82c78896793a" } -
hooks.js文件:let defines = [db => { }]; let hooks = {}; module.exports = { defines: defines, hooks: hooks }
如何 hook 链上数据
假设我们存在这么一个业务场景:
我需要记录和一个账号相关的所有资金流水,即和这个账号之间的所有转账操作。那么基于这个业务需求,我们可以在 hooks.js 中定制自己需要存储的数据表结构和 hook 数据的方式。
- 根据业务定义表结构
let defines = [db => {
return db.define('transfer', {
from: {
required: true,
type: "text",
size: 12
},
to: {
required: true,
type: "text",
size: 12
},
quantity: {
required: true,
type: "text",
size: 256
},
memo: {
type: "text",
size: 256
}
}, {});
}];
可以看到,定义了一个名为 transfer 的表。表中的字段如下:
| 字段 | 类型 | 备注 | 实例 |
|---|---|---|---|
| from | String | 转账发起方 | fiboscouncil |
| to | String | 转账接收方 | fibos |
| quantity | String | 转账数量 | 1.0000 FO |
| memo | String | 转账附言 | hello world |
| 默认字段 | - | - | - |
| id | Number | 自增长id | 1 |
| createdAt | Date | 记录创建时间 | |
| updatedAt | Date | 记录更新时间 |
- 根据业务自定义hook数据
从上面的业务需求出发,我们需要将所有和我转账相关的链上记录都存储下来,所以我们需要监听链上所有 transfer 动作记录,同时在 action 的data中,需要判断 from、to 为我的动作记录。代码如下:
let account = "fibos";
let hooks = {
"eosio.token/transfer": (db, messages) => {
let Transfer = db.models.transfer;
try {
db.trans(() => {
messages.forEach((m) => {
let data = m.act.data;
if (data.from !== account || data.to !== account) return;
Transfer.createSync(data);
});
});
} catch (e) {
console.error("eosio.token/transfer Error:", e);
}
}
}
从上面代码中可以看到,我们存储数据的条件为:转账的 from 为 "fibos" 账号或者转账的 to 为 "fibos" 账号。当条件满足时,直接将 data 内的数据存储到 Transfer 表中。
经过上面两个步骤,我们已经使用 fibos-tracker 完成了一个简单的区块链数据定制存储,下面就让程序跑起来吧。
在当前目录下执行: fibos index.js。执行完成后,Tracker 节点开始同步测试网的区块数据,过程中所有和 "fibos" 这个账号的转账记录都将保存到 Transfer 表中。
如何查询存储的数据
完成上面两个步骤后,我们的需求数据都已经保存到了Mysql中。但是我们往往需要将数据提供给前端展示,所以需要提供出一个 HTTP server。这个时候我们可以直接使用 fib-app 的特性,使用 graphql 来进行查询。
- 编写HTTP server
新建文件 server.js,我们主要使用 fib-app 提供HTTP服务供前端使用 graphql 来进行数据查询,实例代码如下:
const http = require("http");
const Tracker = require("fibos-tracker");
Tracker.Config.DBconnString = "mysql://root:123456@127.0.0.1/chain_data"
const tracker = new Tracker();
tracker.use(require("./hooks.js"));
let httpServer = new http.Server("", 8080, [
(req) => {
req.session = {};
}, {
'/app': tracker.app,
"*": [function(req) {}]
},
function(req) {}
]);
httpServer.crossDomain = true;
httpServer.run(() => { });
console.notice("http server run port: 8080");
保存完毕后,执行 fibos server.js 便启动了服务,其中服务的端口为:8080,此时终端打出 http server run port: 8080 证明服务启动正常。
- 数据查询
1 使用 FIBOS GraphQL 客户端查询
const http = require("http");
let graphql = function(body) {
return http.post(`http://127.0.0.1:8080/1.0/app/`, {
headers: {
'Content-Type': 'application/graphql'
},
body: body
});
}
2 Web GraphQL 查询
let graphql = function(body) {
$.ajax({
type: "POST",
url: "http://127.0.0.1:8080/1.0/app",
data: body,
headers: {
"Content-Type": "application/graphql"
},
success: (res) => {
console.log("success");
},
error: (res) => {
console.log("error");
}
});
}
请求体:「获取所有记录」
{
find_transfer(
where:{}
) {
id
from
to
quantity
memo
}
}`
框架说明
fibos-tracker 默认 DB 说明
为了完善区块数据存储,框架会默认存储 blocks、transactions 以及 actions 的基础数据,三张表的数据结构如下:
blocks 表数据
| 字段 | 类型 | 备注 |
|---|---|---|
| id | Number | 自增长id |
| block_num | Number | 区块高度 |
| block_time | Date | 区块时间 |
| producer_block_id | String | 区块hash |
| producer | String | 区块 producer |
| status | String | 可逆状态 |
| createdAt | Date | 记录创建时间 |
| updatedAt | Date | 记录更新时间 |
transactions 表数据
| 字段 | 类型 | 备注 |
|---|---|---|
| id | Number | 自增长id |
| trx_id | String | 交易hash |
| rawData | JSON | 原始数据 |
| block_id | String | 区块高度(关联blocks) |
| createdAt | Date | 记录创建时间 |
| updatedAt | Date | 记录更新时间 |
actions 表数据
| 字段 | 类型 | 备注 |
|---|---|---|
| id | Number | 自增长id |
| contract_name | String | 合约名称 |
| action | String | 动作名称 |
| authorization | Array | 授权用户 |
| data | JSON | 交易data |
| transaction_id | Number | 交易事务id(关联 transactions表) |
| parent_id | Number | 上级action id(关联 actions 表) |
| createdAt | Date | 记录创建时间 |
| updatedAt | Date | 记录更新时间 |
fibos-tracker 内部逻辑
fibos-tracker 的数据存储是配合 FIBOS 的插件 emitter 来进行实现的。当开启 emitter 插件的节点进行同步时,FIBOS 会将 transaction、block 、irreversible_block 数据通过 emitter 插件推送到客户端,而 fibos-tracker 需要做的就是将插件推送过来的数据拼装后存储。
tracker.use 介绍
tracker.use 属于 fibos-tracker 中重要的api,与 fib-app 的支持,可以实现自定义 hook 数据监听,使用 ORM 模型自定义 DB 数据存储,实例如下:
tracker.use({
defines: [(db) => {
// ORM DB Define
}, (db) => {
// ORM DB Define
}],
hooks: {
"eosio.token/transfer": (db, messages) => {
// hook Tracker messages
},
"eosio/newaccount": (db, messages) => {
// hook Tracker messages
}
}
});
可以看到在 tracker.use 中接受的对象数据类型为:
{
defines: [],
hooks: {}
}
其中 defines 中定义的为 ORM 模型数据表,这就意味着我们可以定义很多数据表结构满足我们的业务需求,而 hooks 主要实现的为数据的过滤,其中过滤规则如下:
hooks 过滤规则:
- 过滤某个合约:如:
eosio - 过滤某个合约的某个动作:如:
eosio/newaccount
hooks 的 messages 数据说明:
为了方便 hooks 业务研发,传递 messages 时做了优化:
- 满足过滤规则的所有 action 合并成数组传递
- 数组内每一个满足过滤规则的 action 包含 本层 action 以下所有 inline_action,并且如果存在上层 action,将携带 parent 属性,标识上层 parent 的 action 数据。
注:每层 parent_id 是该层 action 上级的 DB 自增长 id。
举一个返回示例结构:
[
{
"inline_traces": [
{
"parent": ... parent_id => 1
"inline_traces": [
{
"parent": ... parent_id => 2
"parent": ... parent_id => 1
},
{
"parent": ... parent_id => 2
"parent": ... parent_id => 1
}
]
}
]
}
]