BitShares(比特股)是一种支持包括虚拟货币、法币、贵金属等有价值实物的开源分布式交易系统。关于BitShares,网上的资料有很多,这里不作具体赘述,本文主要对BitShares的插件的开发进行入门介绍。

在进行BitShares的插件开发前,需要具备以下技能:

  • 本地编译并搭建过BitShares节点/重钱包(必须)
  • 能看懂简单的C++代码(必须)
  • 使用过命令行版的钱包,或自已构造过json消息和节点进行简单的交互(可选)

推荐一下这个链接:BitShares (BTS) 中文技术文档,对于BitShares技术学习非常有帮助。

1. 目的

通过插件,可以直接获取到BitShares区块链上的各种数据,对BitShares进行扩展开发,实现定制化的功能。本文通过插件的方式来监控区块链上的转账操作,对插件的开发进行简单的介绍。

2. 目录结构

BitShares的插件源代码存放在libraries/plugins目录中,以我们要创建的monitor插件为例,新建的插件目录结构如下:

libraries/plugins/
├── monitor
│   ├── CMakeLists.txt (+)
│   ├── include
│   │   └── graphene
│   │       └── monitor
│   │           └── monitor.hpp (+)
│   └── monitor.cpp (+)
└── CMakeLists.txt (*)

其中,(+)表示新增的文件,(*)表示需要修改的文件。

3. 插件接口

插件需要实现的接口如下示例代码。

#include <graphene/app/plugin.hpp>

namespace graphene { namespace monitor_plugin {
    // 插件必须继承自graphene::app::plugin类
    class monitor_plugin : public graphene::app::plugin {
        public:
            // 插件名称
            std::string plugin_name()const override;

            // 插件描述信息
            std::string plugin_description()const override;

            // 插件配置解析接口
            virtual void plugin_set_program_options(
                boost::program_options::options_description &command_line_options,
                boost::program_options::options_description &config_file_options
            ) override;

            // 插件初始化
            virtual void plugin_initialize(
                const boost::program_options::variables_map& options
            ) override;

            // 启动插件
            virtual void plugin_startup() override;

            // 停止插件
            virtual void plugin_shutdown() override;
    };
} } // graphene::monitor_plugin

4. 接口实现示例

4.1 插件名称

即启动节点时指定的插件名称,示例中插件名称是monitor,启动节点的时候如果希望加载该插件,则需要带上--plugins "monitor"的参数。

std::string monitor_plugin::plugin_name()const
{
    return "monitor";
}
4.2 插件描述信息

描述信息在查看帮助信息的时候会显示出来。

std::string monitor_plugin::plugin_description()const
{
    return "Monitor transaction status.";
}
4.3 插件配置解析接口

注册插件的参数选项,这里使用了boost库,包括参数名称、参数类型、参数描述。参数支持两种配置方式,一种是命令行参数,另一种是配置文件,两种方式需要分别注册,代码示例如下。

#define MONITOR_OPT_ACTION_TYPE "monitor-action-type"

void monitor_plugin::plugin_set_program_options(
   boost::program_options::options_description& command_line_options,
   boost::program_options::options_description& config_file_options)
{
   command_line_options.add_options()
         (MONITOR_OPT_ACTION_TYPE, boost::program_options::value<uint32_t>(),
          "The type of operation monitored");
   config_file_options.add(command_line_options);
}
4.4 插件初始化

插件初始化接口中,一般用来读取参数配置,或者需要全局初始化不释放的资源。

#define MONITOR_OPT_ACTION_TYPE "monitor-action-type"
#define MONITOR_ACTION_TYPE_ALL (0xFFFFFFFF)

void monitor_plugin::plugin_initialize(const boost::program_options::variables_map& options) {
    try {
        ilog("monitor plugin: plugin_initialize() begin");

        // 读取参数配置
        if (options.count(MONITOR_OPT_ACTION_TYPE)) {
            monitor_action_type = options[MONITOR_OPT_ACTION_TYPE].as<uint32_t>();
        } else {
            monitor_action_type = MONITOR_ACTION_TYPE_ALL;
        }

        ilog("monitor plugin: plugin_initialize() end");
    } FC_LOG_AND_RETHROW()

    return;
}
4.5 启动插件

本例中,启动插件时将回调函数monitor_singed_block注册到了生成区块的事件中applied_block,为后续监控打包区块数据做准备。

void monitor_plugin::plugin_startup() {
    // 注册事件回调函数
    monitor_signed_block_handler = database().applied_block.connect(
        [&]( const graphene::chain::signed_block& blk ) {
            monitor_signed_block(blk);
        }
    );

    return;
}
4.6 停止插件

和启动相对应,将前面注册的事件取消掉,停止监控。

void monitor_plugin::plugin_shutdown() {
    // 卸载事件回调函数
    database().applied_block.disconnect(monitor_signed_block_handler);

    return;
}
4.7 业务处理函数

这里业务处理函数是作为回调函数注册到生成区块的事件上的,所实现的功能是遍历新生成区块上的所有操作,并将转帐类型的操作打印出来,具体代码如下。

void monitor_plugin::monitor_signed_block(const graphene::chain::signed_block& blk) {
    for (uint32_t i = 0; i < blk.transactions.size(); ++i) {
        processed_transaction trans = blk.transactions[i];

        for (uint32_t j = 0; j < trans.operations.size(); ++j) {
            operation op = trans.operations[j];

            if (operation::tag<transfer_operation>::value == op.which()) {
                transfer_operation& op_trans = op.get<transfer_operation>();
                const account_object& from_account = op_trans.from(database());
                const account_object& to_account = op_trans.to(database());
                const asset_object& asset_type = op_trans.amount.asset_id(database());

                cout<<">>> action transfer: "<< from_account.name
                    << " -> " << to_account.name
                    << " " << asset_type.amount_to_pretty_string(op_trans.amount)
                    << endl;
            }
        }
    }
    return;
}

5. 插件注册

插件写完了,还需要注册到系统中才行,在main函数中可以找到类似如下代码:

int main(int argc, char** argv) {
    ...

    auto witness_plug = node->register_plugin<witness_plugin::witness_plugin>();
    auto debug_witness_plug = node->register_plugin<debug_witness_plugin::debug_witness_plugin>();
    auto history_plug = node->register_plugin<account_history::account_history_plugin>();
    auto elasticsearch_plug = node->register_plugin<elasticsearch::elasticsearch_plugin>();
    auto market_history_plug = node->register_plugin<market_history::market_history_plugin>();
    auto delayed_plug = node->register_plugin<delayed_node::delayed_node_plugin>();
    auto snapshot_plug = node->register_plugin<snapshot_plugin::snapshot_plugin>();
    auto es_objects_plug = node->register_plugin<es_objects::es_objects_plugin>();
    auto grouped_orders_plug = node->register_plugin<grouped_orders::grouped_orders_plugin>();

    ...
}

这里我们将monitor_plugin插件加上去,增加下面代码完成插件的注册:

int main(int argc, char** argv) {
    ...

    auto monitor_plug = node->register_plugin<monitor_plugin::monitor_plugin>();

    ...
}

6. 编译

BitShares源代码使用cmake编译,需要在插件目录下新增一个CMakeLists.txt文件,内容如下。

file(GLOB HEADERS "include/graphene/monitor/*.hpp")

add_library( graphene_monitor
             monitor.cpp
           )

target_link_libraries( graphene_monitor graphene_chain graphene_app )
target_include_directories( graphene_monitor
                            PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" )

install( TARGETS
    graphene_monitor

    RUNTIME DESTINATION bin
    LIBRARY DESTINATION lib
    ARCHIVE DESTINATION lib
)

另外,还需要将上级目录中的CMakeLists.txt中追加新插件目录:

...
add_subdirectory( monitor )

完成插件后,在BitShares目录中依次执行cmakemake,重新编译witness_node程序。

cmake
make

7. 运行

启动witness_node时通过命令行参数加载monitor插件,执行如下命令:

./witness_node --plugins "monitor"

成功运行后,当区块链上发生转帐操作时,可以看到命令行中会打印追踪到的交易信息。

BitShares Plugin Monitor Running

8. 完整的源代码

本地下载:monitor.hppmonitor.cpp

Github:点击这里,可以直接查看完整的示例代码。


Comments