精通EOS:EOS 插件系统

EOS 中的节点程序 nodeos 从架构角度看,本质上是一个插件系统,系统提供的主要功能都依赖它的插件系统,通过不同的插件组合来提供各种服务功能,如区块链查询,交易验证执行,打包区块,P2P网络通信等服务。

插件的继承关系 系统的插件系统位于 eos/plugins 目录下,插件系统包含三个层次:
  1. 最顶层,这是一个位于 libraries/appbase/include/appbase/plugin.hpp 文件中的抽象类 abstract_plugin。这个抽象类提供了插件的基本接口,包括:
    • initialize初始化插件。
    • startup运行插件。
    • shutdown关闭插件。
    • set_program_options设置插件可以接收的选项。
    • get_state返回插件的状态。插件当前有 4 种状态,分别是:已注册、已初始化、运行中、已停止。其中已注册表示插件对象已经被实例化,但没有做任何事情;已初始化表示插件的参数已经被初始化,可以随时运行;运行中插件已经在运行状态;已停止表示插件已经停止运行。
    • name表示插件的名称,通过运行时类识别技术 RITT,结合 boost::core::demangle 来获取,获取到的名称包括了命名空间,比如:eosio::chain_api_plugin
  2. 中间层,这是一个位于 libraries/appbase/include/appbase/application.hpp 文件中的模板类,它实现了抽象插件中定义的方法,并且定义了一个虚函数来定义了插件的依赖的。
    • plugin构造函数。在方法中,通过 C++ 的 RTTI 运行时类型识别技术,获得具体的实例化插件的名称,并保存在 _name 属性中。这个名称包含了命名空间在内,比如:eosio::chain_api_plugin
    • get_state返回当前的状态对象 _state
    • name返回当前实例化类的名称,见构造函数解释。
    • register_dependencies通过 static_cast 方法,把当前插件转化为具体的实例化插件,然后调用插件的 plugin_requires 模板方法,加载插件依赖的其他插件。
    • initialize如果当前插件不是已注册状态,那么不做任务处理。对于处在已注册状态的插件,执行下面的处理:
      • 首先,设置插件状态为已初始化;
      • 其次,调用插件的 plugin_requires 模板方法,加载插件依赖的其他插件,并且通过 Lambda 表达式执行其它插件的 initialize 方法;
      • 然后,调用插件的 plugin_initialize 方法,进行初始化;
      • 最后,调用应用程序的 plugin_initialized 方法,把插件加入已初始化集合 initialized_plugins
      通过以上几个步骤,插件及其依赖都得到了初始化。
    • startup如果当前插件不是已初始化状态,那么不做任务处理。对于处在已初始化状态的插件,执行下面的处理:
      • 首先,设置插件状态为已启动;
      • 其次,调用插件的 plugin_requires 模板方法,加载插件依赖的其他插件,并且通过 Lambda 表达式执行其它插件的 startup 方法;
      • 然后,调用插件的 plugin_startup 方法,进行启动;
      • 最后,调用应用程序的 plugin_started 方法,把插件加入已运行集合 running_plugins
      通过以上几个步骤,插件及其依赖都是运行中的。
    • shutdown如果当前插件不是已初始化状态,那么不做任务处理。对于处在已初始化状态的插件,执行下面的处理:
      • 首先,设置插件状态为已停止;
      • 其次,调用插件的 plugin_shutdown 方法,进行停止。
    注意:插件的初始与启动方法分别是在 main 函数中通过调用应用对象的 application::iinitialize_implapplication::startup 中被调用的。
  3. 最下层的是承担各种具体功能的插件,这些插件都位于 eos/plugins,它们都定义了下面一些方法:
    • plugin_requires模板方法,加载插件的依赖,通过表达式依赖插件的对应方法。
    • set_program_options从抽象类类继承的虚函数
    • plugin_initialize实例化插件
    • plugin_startup启动插件
    • plugin_shutdown关闭插件
    在 EOS 提供的插件中 chain_pluginnet_pluginhttp_pluginproducer_plugin 这4个插件不需要用户手动注册,节点会自动注册。

    系统提供的插件及其功能如下所述:

    • chain_plugin这个插件承载了 nodeos 节点程序与区块链交互的基本功能,包括:
      • 读取本地不可逆区块链基本信息;
      • 设置本地链检查点;
      • 设置本地链参数;
      • 设置可逆区块数据库参数;
      • 设置账户黑/白名单;
      • 设置智能合约黑/白名单;
      • 重载区块链初始状态文件;
      • 删除、重写、替换本地区块链数据(包括开始、停止等动作);
    • net_plugin 这个插件定义了 P2P 网络插件,这个插件承载了 EOS 系统的 P2P 网络中 TCP/IP 层相关功能,包括 4 个基本功能:
      • 建立节点之间握手并互联;
      • 监听/发送/接收新交易请求;
      • 监听/发送/接收新区块请求;
      • 验证接收数据合法性;
    • http_plugin这个插件定义了网络 http 插件,这个插件承载了 EOS 系统的 P2 P网络中 http 层相关功能,包括 3 个基本功能:
      • 监听/发送/接收新交易请求;
      • 监听/发送/接收新区块请求;
      • 验证接收数据合法性;
    • producer_plugin定义了区块生产节点的功能插件,其中功能包括:
      • 生产/打包新区块数据;
      • 对新区块签名;
      • 对接收的区块进行验证,包括区块头合法性、签名合法性和交易合法性;
    • wallet_plugin这个插件定义了钱包插件,这个插件承载了 nodeos 节点程序与钱包交互的相关功能,包括3个基本功能:
      • 创建/读取钱包文件;
      • 设置解锁超时时间;
      • 密钥导入钱包;
    • account_history_plugin这个插件定义了历史记录查询插件,该插件承载了节点程序对本地链发起查询的相关功能,包括3个基本功能:
      • 指定区块查询;
      • 指定账户状态查询;
      • 指定交易查询;
    • bnet_plugin这个插件定义了 EOS 的 P2P 网络中不同节点之间同步各自本地链数据的算法,简单来讲,包括如下特征:
      • 查找本地链上的最后一个区块ID;
      • 如果本地产生新区块,则将该区块发送给其他节点;
      • 如果本地不产生新区块,则将接受到的未确认交易发送给其他节点;
    • faucet_testnet_plugin这个插件定义了在测试网上快速建立测试帐号的插件,该插件为测试提供便利。
    • history_plugin这个插件定义了查询指定账户中指定公钥的所有历史交易记录。
    • http_client_plugin这个插件定义了eos网络http层响应请求,并做相应的安全验证的客户端插件。
    • mongo_db_plugin这个插件定义了保存并管理本地不可逆转区块链数据的MongoDB数据库基本配置插件。
    • txn_test_gen_plugin这个插件定定义了一个每秒自动产生指定数量的交易信息的插件,该插件主要用作对eos网络的吞吐量(TPS)测试。
    • chain_api_plugin这个插件定义了一些用户可以调用的、区块链相关的 API。主要有:
      • /v1/chain/abi_bin_to_json
      • /v1/chain/abi_json_to_bin
      • /v1/chain/get_abi
      • /v1/chain/get_account
      • /v1/chain/get_block
      • /v1/chain/get_block_header_state
      • /v1/chain/get_code
      • /v1/chain/get_code_hash
      • /v1/chain/get_currency_balance
      • /v1/chain/get_currency_stats
      • /v1/chain/get_info
      • /v1/chain/get_producer_schedule
      • /v1/chain/get_producers
      • /v1/chain/get_raw_abi
      • /v1/chain/get_raw_code_and_abi
      • /v1/chain/get_required_keys
      • /v1/chain/get_scheduled_transactions
      • /v1/chain/get_table_by_scope
      • /v1/chain/get_table_rows
      • /v1/chain/get_transaction_id
      • /v1/chain/push_block
      • /v1/chain/push_transaction
      • /v1/chain/push_transactions
    • db_size_api_plugin这个插件定义了一些用户可以调用的关于存储相关的 API。主要有:
      • /v1/db_size/get
    • history_api_plugin这个插件定义了一些用户可以调用的、历史数据相关的 API。主要有:
      • /v1/history/get_actions
      • get_transaction
      • get_key_accounts
      • get_controlled_accounts
    • net_api_plugin这个插件定义了一些用户可以调用的、网络数据相关的 API。主要有:
      • /v1/net/connect
      • /v1/net/disconnect
      • /v1/net/status
      • /v1/net/connections
    • producer_api_plugin这个插件定义了一些用户可以调用的、生产区块相关的 API。主要有:
      • /v1/producer/pause
      • /v1/producer/resume
      • /v1/producer/paused
      • /v1/producer/get_runtime_options
      • /v1/producer/update_runtime_options
      • /v1/producer/add_greylist_accounts
      • /v1/producer/remove_greylist_accounts
      • /v1/producer/get_greylist
      • /v1/producer/get_whitelist_blacklist
      • /v1/producer/set_whitelist_blacklist
      • /v1/producer/get_integrity_hash
      • /v1/producer/create_snapshot
    • wallet_api_plugin这个插件定义了一些用户可以调用的、钱包相关的 API。主要有:
      • /v1/wallet/set_timeout
      • /v1/wallet/sign_transaction
      • /v1/wallet/sign_digest
      • /v1/wallet/create
      • /v1/wallet/open
      • /v1/wallet/lock_all
      • /v1/wallet/lock
      • /v1/wallet/unlock
      • /v1/wallet/import_key
      • /v1/wallet/remove_key
      • /v1/wallet/create_key
      • /v1/wallet/list_wallets
      • /v1/wallet/list_keys
      • /v1/wallet/get_public_keys

插件生命周期 EOS 的每个插件,在生命期内都会经历以下阶段
  1. 插件注册
  2. 插件初始化
  3. 插件启动
无论是在 main 方法通过模板函数 initialize 自动启动的,还是用户通过 --plugin 参数或配置文件指定的插件都要经过上面说的 3 个生成生命周期。

1、插件注册 在 C++ 的 main 函数执行前,系统会提前初始化静态变量,这其中就包括了初始化插件相关的变量,这些插件都是通过调用 app().register_plugin<xxx_plugin>() 方法时行注册。我们简单看下这个方法:
  1. 调用模板函数 find_plugin ,查找插件是否存在,如果存在则直接返回,否则,进入下一步;
  2. 实例一个模板插件的具体实体化,并放入 plugins 集合;
  3. 最后,调用插件的 register_dependencies 的方法来注册插件的依赖。
系统中有如下一些插件采用了静态注册:
  • bnet_plugin
  • chain_api_plugin
  • db_size_api_plugin
  • faucet_testnet_plugin
  • history_api_plugin
  • history_plugin
  • http_plugin
  • login_plugin
  • mongo_db_plugin
  • net_api_plugin
  • net_plugin
  • producer_api_plugin
  • producer_plugin
  • template_plugin
  • test_control_api_plugin
  • test_control_plugin
  • txn_test_gen_plugin
  • wallet_api_plugin
  • wallet_plugin
系统通过这种静态注册方法和插件间的依赖关系,间接地在系统启动前把所有插件都注册到系统中。

如果我们新开发了一个插件,因这个插件没有被任何现在的插件依赖,所以一定要采用静态注册方法,在系统启动之前就要进行注册,然后才可以在配置文件或参数中进行指定,这样插件才会进行初始化与启动。

2、插件初始化 无论是自动启动的插件,还是用户通过 --plugin 参数或配置文件指定的插件,所有的这此二插件都是在 main 函数中通过调用应用对象的 application::iinitialize_impl 方法完成初始化的。

3、插件启动 在插件初始化之后,已经完成初始化的插件在 main 函数中通过调用应用对象的 application::startup 方法完成启动的。

后记 由于本人水平所限,文中错误在所难免,欢迎您踊跃指出错误,在下感激不尽。我的微信联系方式:joepeak。

原创不易,尤其寒冬,欢迎赞助我一杯咖啡,本人比特币地址如下:3C1gyc2tsVudvVNQCZfrwHviDQeUpPeT95

版权声明:自由转载-非商用-非衍生-保持署名(创意共享4.0许可证)

本文来源: BTCsos 速援财经 · 为快不破 文章作者: 佚名
    下一篇