接着设置异常回调,如果发生异常则执行回调函数,回归正常计划的出块循环节奏:
auto ensure = fc::make_scoped_exit([this](){ schedule_production_loop(); // 正常计划的出块循环节奏。 });接下来,向链推送目标区块chain.push_block(block);。异常处理,相关标志位处理,日志输出结果。继续回到push_block函数。
push_block函数首先要判断是否是pending状态,推送区块前要保证没有pending区块。接着校验是否为空区块,区块状态是否为incomplete。通过校验后,发射预承认区块信号,携带区块对象。
emit( self.pre_accepted_block, b ); // 预承认区块信号接着,如果节点未特殊配置强制检查以及区块状态为不可逆irreversible或者检验过validated,则将区块构建为可信block_state对象加入到fork_db。经历一系列校验,执行auto inserted = my->index.insert(n)添加区块到分叉库创建的多索引库fork_multi_index_type中,返回状态区块对象。回到push_block,检查区块生产者是否在可信生产者列表中,如果在,则将可信的生产者执行轻量级校验的标志位置为true。然后发射承认区块头信号,并携带区块状态数据。
emit( self.accepted_block_header, new_header_state ); // 承认区块头信号接着判断如果当前数据库读取模式不是IRREVERSIBLE不可逆,则需要调用maybe_switch_forks处理分叉合并的事务。最后判断如果区块状态为irreversible,则发出第三个信号,不可逆区块信号,并携带区块数据。
emit( self.irreversible_block, new_header_state ); // 不可逆区块信号push_block函数执行完毕,共发射了三个信号,对应的是前文提到的controller维护的信号,通过信号槽机制,找到connection,并执行对应函数操作即可,信号槽机制曾多次分析阐述,此处不展开。
push_block接口是推送本地的区块处理,并未涉及到区块链网络节点的广播。
该接口的函数实现方式以及采用语法特性与push_block相似,本段不重复介绍。该接口的入参类型是一个变体对象variant_object,也就是说它没有像其他接口那样特别声明参数结构,而是在函数实现中,加入了对象的构造过程,参数对象最终通过abi_serializer::from_variant被构造成packed_transaction打包事务类型。返回值结构是有定义的:
struct push_transaction_results { chain::transaction_id_type transaction_id; // 事务id fc::variant processed; // 加工过的事务对象 };回到函数体,同样是基于method模板的功能,在producer_plugin中找到transaction_async注册的函数,传入了处理好的打包事务对象,是否存留标志位,用来接收返回值的next函数。实际调用了producer_plugin_impl::on_incoming_transaction_async函数。
on_incoming_transaction_async函数该函数内容较多。首先,仍及是获取节点当前链环境:
chain::controller& chain = app().get_plugin<chain_plugin>().chain();接着,判断当前链若是不存在pending块,则增加到pending块。接着推送区块是通过channel模板的机制,这是与method模板想类似的机制。首先来看函数中该机制首次出现的位置:
_transaction_ack_channel.publish(std::pair<fc::exception_ptr, packed_transaction_ptr>(response.get<fc::exception_ptr>(), trx)); // 传入事务对象trx_transaction_ack_channel是当前实例的成员,找到当前实例的构造函数,发现该成员的初始化信息:
_transaction_ack_channel(app().get_channel<compat::channels::transaction_ack>())app().get_channel<xxx>的结构与上面介绍method的机制非常相似,查看transaction_ack的声明:
namespace compat { namespace channels { using transaction_ack = channel_decl<struct accepted_transaction_tag, std::pair<fc::exception_ptr, packed_transaction_ptr>>; } }该声明与上面method相关key的声明在同一个文件中,说明设计者的思路是有将他们归为一类的:都属于解耦调用的桥梁。接着查看channel_decl:
/** * @tparam Tag - API鉴定器,用于区分相同数据类型 * @tparam Data - channel携带的数据类型 * @tparam DispatchPolicy - 当前channel的分发策略。默认是drop_exceptions */ template< typename Tag, typename Data, typename DispatchPolicy = drop_exceptions > struct channel_decl { using channel_type = channel<Data, DispatchPolicy>; using tag_type = Tag; };与method_decl非常相似了。具体channel机制的分析如下图所示。
可以看得出,channel的订阅与发布的模式,对应的是method的注册和调用,主要区别在于主体的角色转换。