EOS行为核心:解析插件chain_plugin

EOS提供了大量的rpc接口,其中功能性最强,使用最频繁的一部分接口是EOS的行为核心,由chain_api_plugin提供,具体实现是在chain_plugin。 关键字:EOS,区块链,chain_plugin,chain_api_plugin,rpc,FC_REFLECT,反射,method模板,channel模板 一、接口列表chain_api_plugin rpc调用逻辑,chainbase数据库底层原理,nodeos启动流程,plugin生命周期在前文都有介绍。本节直接研究chain_plugin的内容,研究入口会从chain_api_plugin中暴漏的rpc接口切入,这些接口是非常熟悉的,因为之前演练cleos相关命令时调用的也是rpc。首先展示一下所有的接口内容: _http_plugin.add_api({ CHAIN_RO_CALL(get_info, 200l), CHAIN_RO_CALL(get_block, 200), CHAIN_RO_CALL(get_block_header_state, 200), CHAIN_RO_CALL(get_account, 200), CHAIN_RO_CALL(get_code, 200), CHAIN_RO_CALL(get_code_hash, 200), CHAIN_RO_CALL(get_abi, 200), CHAIN_RO_CALL(get_raw_code_and_abi, 200), CHAIN_RO_CALL(get_raw_abi, 200), CHAIN_RO_CALL(get_table_rows, 200), CHAIN_RO_CALL(get_table_by_scope, 200), CHAIN_RO_CALL(get_currency_balance, 200), CHAIN_RO_CALL(get_currency_stats, 200), CHAIN_RO_CALL(get_producers, 200), CHAIN_RO_CALL(get_producer_schedule, 200), CHAIN_RO_CALL(get_scheduled_transactions, 200), CHAIN_RO_CALL(abi_json_to_bin, 200), CHAIN_RO_CALL(abi_bin_to_json, 200), CHAIN_RO_CALL(get_required_keys, 200), CHAIN_RO_CALL(get_transaction_id, 200), CHAIN_RW_CALL_ASYNC(push_block, chain_apis::read_write::push_block_results, 202), CHAIN_RW_CALL_ASYNC(push_transaction, chain_apis::read_write::push_transaction_results, 202), CHAIN_RW_CALL_ASYNC(push_transactions, chain_apis::read_write::push_transactions_results, 202) }); 这些接口可以分为两类,一类是通过宏CHAIN_RO_CALL调用的,另一类是通过宏CHAIN_RW_CALL_ASYNC调用。 (1) CHAIN_RO_CALL #define CHAIN_RO_CALL(call_name, http_response_code) CALL(chain, ro_api, chain_apis::read_only, call_name, http_response_code) 采用同步只读的方式调用宏CALL。call_name是调用的函数名,http_response_code是响应码。下面进入宏CALL。 /** * @attention 目前调用CALL函数的只有read_only应用。 * @param api_name "chain' * @param api_handle app().get_plugin().get_read_only_api(); * @param api_namespace chain_apis::read_only * @param call_name -INHERIT * @param http_response_code -INHERIT */ #define CALL(api_name, api_handle, api_namespace, call_name, http_response_code) \ {std::string("/v1/" #api_name "/" #call_name), \ /*拼接接口url:http://ip:port/v1/chain/{call_name}*/ \ /* * @param body:http请求体 * @param cb:回调函数,用于返回处理结果 */ \ [api_handle](string, string body, url_response_callback cb) mutable { \ api_handle.validate(); \ try { \ if (body.empty()) body = "{}"; \ /* * api_handle为chain_plugin中read_only类的实例 * call_name为函数名,实现体找chain_plugin.cpp文件 * 函数参数1个:此处规定了一个命名规则,接口名加入后缀_param即为请求参数结构 */ \ auto result = api_handle.call_name(fc::json::from_string(body).as()); \ /*回调函数返回处理结果,此处也规定了一个命名规则,接口名加入后缀_result即为返回结构,转化为json的格式返回。*/ \ cb(http_response_code, fc::json::to_string(result)); \ } catch (...) { \ /*捕捉到异常,调用http_plugin的异常处理函数handle_exception*/ \ http_plugin::handle_exception(#api_name, #call_name, body, cb); \ } \ } \ } api_handle参数 同步只读的请求传入的api_handle参数值为ro_api变量,该变量是在chain_api_plugin插件启动chain_api_plugin::plugin_startup时(插件的生命周期前文已有介绍)初始化的, auto ro_api = app().get_plugin().get_read_only_api(); app()函数以及与application类相关的内容前文已经介绍过,通过get_plugin获取chain_plugin的实例,然后调用其成员函数get_read_only_api(), chain_apis::read_only get_read_only_api() const { return chain_apis::read_only(chain(), get_abi_serializer_max_time()); } //注意const修饰符,函数体内返回值是不可修改的。 返回的是chain_apis::read_only构造函数返回的read_only实例。类read_only中包含了所有基于只读机制的接口实现,与上面接口列表中声明的保持一致。 read_only(const controller& db, const fc::microseconds& abi_serializer_max_time) : db(db), abi_serializer_max_time(abi_serializer_max_time) {} 因此,最后传入CALL宏的api_handle参数值实际就是这个类read_only的实例。之后使用该实例去调用call_name,就是简单的实例调用自身成员函数(一般这个成员函数是声明和实现都有的)的逻辑了。 (2) CHAIN_RW_CALL_ASYNC #define CHAIN_RW_CALL_ASYNC(call_name, call_result, http_response_code) CALL_ASYNC(chain, rw_api, chain_apis::read_write, call_name, call_result, http_response_code) 采用异步读写的方式调用异步处理宏CALL_ASYNC。call_name是调用的函数名,call_result传入声明的结果接收体(例如chain_apis::read_write::push_transaction_results),http_response_code是响应码。下面进入宏CALL_ASYNC。 /** * @attention 目前调用CALL_ASYNC函数的只有read_write的应用。 * @param api_name "chain' * @param api_handle app().get_plugin().get_read_write_api(); * @param api_namespace chain_apis::read_write * @param call_name -INHERIT * @param call_result -INHERIT * @param http_response_code -INHERIT */ #define CALL_ASYNC(api_name, api_handle, api_namespace, call_name, call_result, http_response_code) \ {std::string("/v1/" #api_name "/" #call_name), \ /*同上,拼接接口url:http://ip:port/v1/chain/{call_name}*/ \ /* * http处理请求的函数结构不变,同上。 * @param body:http请求体 * @param cb:回调函数,用于返回处理结果 */ \ [api_handle](string, string body, url_response_callback cb) mutable { \ if (body.empty()) body = "{}"; \ api_handle.validate(); \ /* * api_handle为chain_plugin中read_only类的实例 * call_name为函数名,实现体找chain_plugin.cpp文件 * 函数参数2个: * @param 此处规定了一个命名规则,接口名加入后缀_param即为请求参数结构 * @param lambda表达式,将cb和body按值传递进内部函数,该内部函数整体作为异步操作的回调函数,注意与http的回调函数cb区分。 */ \ api_handle.call_name(fc::json::from_string(body).as(),\ [cb, body](const fc::static_variant& result){\ /*捕获异常,分发异常处理*/ \ if (result.contains()) {\ try {\ result.get()->dynamic_rethrow_exception();\ } catch (...) {\ http_plugin::handle_exception(#api_name, #call_name, body, cb);\ }\ } else {\ /* * 异步处理成功,通过http的回调函数cb返回结果。 */ \ cb(http_response_code, result.visit(async_result_visitor()));\ }\ });\ }\ } 其中最后处理结果的语句比较令人好奇result.visit(async_result_visitor()) result的类型是:const fc::static_variant& async_result_visitor()函数: struct async_result_visitor : public fc::visitor { template std::string operator()(const T& v) const { return fc::json::to_string(v); //与CALL处理返回结果相同的是,此处做的也是转换json的工作。 } }; 接着,进入fc库的static_variant.hpp文件中寻找类static_variant,它包含一个模板函数visit: template typename visitor::result_type visit(const visitor& v)const { return impl::storage_ops<0, Types...>::apply(_tag, storage, v); } 异步处理将处理结果转型放置在结果容器中。 api_handle参数 异步读写的请求传入的api_handle参数值为rw_api变量,该变量是在chain_api_plugin插件启动chain_api_plugin::plugin_startup时(插件的生命周期前文已有介绍)初始化的, auto rw_api = app().get_plugin().get_read_write_api(); app()函数以及与application类相关的内容前文已经介绍过,通过get_plugin获取chain_plugin的实例,然后调用其成员函数get_read_write_api(), chain_apis::read_write get_read_write_api() { return chain_apis::read_write(chain(), get_abi_serializer_max_time()); } 返回的是chain_apis::read_write构造函数返回的read_write实例。类read_write中包含了所有基于读写机制的接口实现,与上面接口列表中声明的保持一致。 read_write(controller& db, const fc::microseconds& abi_serializer_max_time) : db(db), abi_serializer_max_time(abi_serializer_max_time) {} 因此,最后传入CALL_ASYNC宏的api_handle参数值实际就是这个类read_write的实例。之后使用该实例去调用call_name,就是简单的实例调用自身成员函数(一般这个成员函数是声明和实现都有的)的逻辑了。 chain_api_plugin生命周期 set_program_options,空 plugin_initialize,空 plugin_startup,添加rpc接口,请求chain_plugin功能函数。 plugin_shutdown,空 二、结构体成员序列化FC_REFLECT FC_REFLECT为结构体提供序列化成员的能力。 FC_REFLECT是FC库中提供反射功能的宏。反射的意义在于了解一个未知的对象,反射是不限编程语言的,通过反射能够获取到对象的成员结构。宏#define FC_REFLECT( TYPE, MEMBERS )内部又调用了宏#define FC_REFLECT_DERIVED( TYPE, INHERITS, MEMBERS ),反射功能的具体实现就不深入探究了。下面来看其应用,举个例子: FC_REFLECT( eosio::chain_apis::read_only::get_required_keys_params, (transaction)(available_keys) ) FC_REFLECT( eosio::chain_apis::read_only::get_required_keys_result, (required_keys) ) 两行代码分别包含了关于get_required_keys的两个结构体, struct get_required_keys_params { fc::variant transaction; flat_set available_keys; }; struct get_required_keys_result { flat_set required_keys; }; get_required_keys是chain的RPC接口,结构体get_required_keys_params是该接口的请求参数的结构,而另一个get_required_keys_result是接口处理后返回的结构。 回过头继续看FC_REFLECT的两行代码,第一个参数传入的是结构体。第二个参数用圆括号包含,可以有多个,内容与结构体的成员一致。 FC_REFLECT实际上实现了面向对象编程中类成员的getter/setter方法。 三、chain_plugin生命周期 与基类定义的生命周期相同,也包含四个阶段。 chain_plugin::set_program_options 在nodeos程序调试部分有详细介绍。主要是添加chain_plugin相关的配置参数,一组是命令行的,另一组是来自配置文件的,其中命令行的配置项优先级更高。 chain_plugin::plugin_initialize 这个函数也是从nodeos程序入口而来,会传入配置项调用chain_plugin的初始化函数。初始胡函数获取到来自命令行和配置文件的中和配置参数以后,结合创世块配置,逐一处理相关参数逻辑。这些参数对应的处理逻辑如下表(对应controller的成员属性介绍)所示: param explanation detail action-blacklist 添加action黑名单 每一条数据是有账户和action名组成 key-blacklist 公钥黑名单 公钥集合 blocks-dir 设置数据目录 最终会处理为绝对路径保存到内存 checkpoint 检查点 缓存区块的检查点,用于快速扫描 wasm-runtime 虚拟机类型 可以指定运行时webassembly虚拟机类型 abi-serializer-max-time-ms abi序列化最大时间 要提高这个数值防止abi序列化失败 chain-state-db-size-mb 链状态库大小 基于chainbase的状态主库的大小 chain-state-db-guard-size-mb 链状态库守卫大小 也是controller中提到的未包含在公开属性中的 reversible-blocks-db-size-mb 链可逆区块库大小 链可逆区块库也是基于chainbase的状态数据库 reversible-blocks-db-guard-size-mb 链可逆区块库守卫大小 也是controller中提到的未包含在公开属性中的 force-all-checks 是否强制执行所有检查 默认为false disable-replay-opts 是否禁止重播参数 默认为false contracts-console 是否允许合约输出到控制台 一般为了调试合约使用,默认为false disable-ram-billing-notify-checks 是否允许内存账单通知 默认为false extract-genesis-json/print-genesis-json 输出创世块配置 以json格式输出 export-reversible-blocks 导出可逆区块到路径 将可逆区块目录reversible中数据导入到指定路径 delete-all-blocks 删除所有区块数据 重置区块链 truncate-at-blocks 区块截取点 所有生效的指令都要截止到本参数设置的区块号 hard-replay-blockchain 强制重播区块链 清空状态库,通过repair_log获得backup,搭配fix-reversible-blocks从backup中恢复可逆区块到区块目录。 replay-blockchain 重播区块链 清空状态库,搭配fix-reversible-blocks从原区块目录的可逆区块目录自我修复 fix-reversible-blocks 修复可逆区块 调用函数recover_reversible_blocks传入源路径和新路径,可逆缓存大小,以及是否有截取点truncate-at-blocks import-reversible-blocks 导入可逆区块路径(必须独立使用,没有其他参数命令) 清空可逆区块目录,调用import_reversible_blocks函数导入 snapshot 指定导入的快照路径 在controller的快照部分有详述 genesis-json 指定创世块配置文件 从文件中导出创世块的配置项到内存 genesis-timestamp 指定创世块的时间 同样将该时间配置到内存中对应的变量 read-mode 状态主库的读取模式 controller部分有详述 validation-mode 校验模式 controller部分有详述 chain_plugin参数处理完毕后,设置方法提供者(并没有找到该provider的应用)。接着转播信号到频道,为chain_plugin_impl的唯一指针my的connection属性赋值,创建信号槽。 pre_accepted_block_connection,连接信号pre_accepted_block,更新loaded_checkpoints区块检查点位置。 accepted_block_header_connection,连接信号accepted_block_header,承认区块头信号。 accepted_block_connection,连接信号accepted_block,承认区块信号。 irreversible_block_connection,连接信号irreversible_block,区块不可逆。 accepted_transaction_connection,连接信号accepted_transaction,承认事务。 applied_transaction_connection,连接信号applied_transaction,应用事务。 accepted_confirmation_connection,连接信号accepted_confirmation,承认确认。 chain_plugin的插件初始化工作完毕,主要是对chain_plugin的配置参数的处理,以及信号槽的实现。 chain_plugin::plugin_startup chain_plugin插件的启动,首先是快照的处理,这部分在快照的内容中有介绍,是根据nodeos过来的快照参数,判断是否要加入快照参数调用controller的startup。这期间如有捕捉到异常,则执行controller的reset重置操作。然后根据controller的属性输出链日志信息。 chain_plugin::plugin_shutdown 重置所有的信号槽,重置controller。 四、RPC接口实现 外部rpc调用通过chain_api_plugin插件包裹的接口服务,内部接口的实现是在chain_plugin中,对应关系是在chain_api_plugin的接口列表,通过函数名字匹配。 1. 获取基本信息 get_info // 返回值为read_only的实体成员get_info_results结构的实例。 read_only::get_info_results read_only::get_info(const read_only::get_info_params&) const { const auto& rm = db.get_resource_limits_manager(); return { // 以下字段都与get_info_results结构匹配,最终构造出get_info_results实例返回。 eosio::utilities::common::itoh(static_cast(app().version())), // server_version db.get_chain_id(), // chain_id db.fork_db_head_block_num(), // head_block_num db.last_irreversible_block_num(), // last_irreversible_block_num db.last_irreversible_block_id(), // last_irreversible_block_id db.fork_db_head_block_id(), // head_block_id db.fork_db_head_block_time(), // head_block_time db.fork_db_head_block_producer(), // head_block_producer rm.get_virtual_block_cpu_limit(), // virtual_block_cpu_limit rm.get_virtual_block_net_limit(), // virtual_block_net_limit rm.get_block_cpu_limit(), // block_cpu_limit rm.get_block_net_limit(), // block_net_limit //std::bitset<64>(db.get_dynamic_global_properties().recent_slots_filled).to_string(), // recent_slots //__builtin_popcountll(db.get_dynamic_global_properties().recent_slots_filled) / 64.0, // participation_rate app().version_string(), // server_version_string }; } 可以看到get_info_results的部分字段是通过read_only::db对象获取,还有一部分资源相关的内容是通过db的资源限制管理器获得,而关于版本方面的数据是从application实例获得。 2. 获取区块信息 get_block // 特殊的是,此处并没有创建一个get_block_result的结构体作为返回值的容器,是利用了variant语法将signed_block_ptr转换成可输出的状态。 fc::variant read_only::get_block(const read_only::get_block_params& params) const { signed_block_ptr block; // 如果参数block_num_or_id为空或者block_num_or_id的长度大于64,属于非法参数不处理,会报错。 EOS_ASSERT(!params.block_num_or_id.empty() && params.block_num_or_id.size() <= 64, chain::block_id_type_exception, "Invalid Block number or ID, must be greater than 0 and less than 64 characters" ); try { // 通过variant语法将参数block_num_or_id类型擦除然后通过as语法转化为block_id_type类型, block = db.fetch_block_by_id(fc::variant(params.block_num_or_id).as()); if (!block) {// 如果通过id的方法获得的block为空,则尝试使用区块号的方式获取。 block = db.fetch_block_by_number(fc::to_uint64(params.block_num_or_id));// 利用to_uint64将参数转型。 }// 如果获取失败,抛出异常,无效的参数block_num_or_id } EOS_RETHROW_EXCEPTIONS(chain::block_id_type_exception, "Invalid block ID: ${block_num_or_id}", ("block_num_or_id", params.block_num_or_id)) EOS_ASSERT( block, unknown_block_exception, "Could not find block: ${block}", ("block", params.block_num_or_id)); // 通过校验,开始返回对象。
50000+
5万行代码练就真实本领
17年
创办于2008年老牌培训机构
1000+
合作企业
98%
就业率

联系我们

电话咨询

0532-85025005

扫码添加微信