Skip to content

fibos-tracker 使用指南 #6

@WxSimon

Description

@WxSimon

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
          }
        ]
      }
    ]
  }
]

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions