入参为chain::signed_block类型:
struct signed_block : public signed_block_header { using signed_block_header::signed_block_header; // 签名区块头 signed_block() = default; // 默认构造器 signed_block( const signed_block_header& h ):signed_block_header(h){} // 构造器,传入签名区块头 vector<transaction_receipt> transactions; // 包含收到事务的集合 extensions_type block_extensions; // 区块扩展 };该接口的返回值push_block_results为空,没有返回值。接口的函数实现:
void read_write::push_block(const read_write::push_block_params& params, next_function<read_write::push_block_results> next) { try { app().get_method<incoming::methods::block_sync>()(std::make_shared<signed_block>(params));// 命名空间incoming::methods下的成员block_sync next(read_write::push_block_results{});// 调用next写入结果,实际上结果为空。 } catch ( boost::interprocess::bad_alloc& ) { chain_plugin::handle_db_exhaustion(); } CATCH_AND_CALL(next); }查看incoming::methods命名空间下的成员block_sync:
namespace incoming { namespace methods { // 推送block到一个独立的provider using block_sync = method_decl<chain_plugin_interface, void(const signed_block_ptr&), first_provider_policy>; } }继续看method_decl的定义:
/** * @tparam Tag - API鉴定器,用于区分相同方法的不同签名 * @tparam FunctionSig - 方法签名 * @tparam DispatchPolicy - 分发策略,规定了provider是如何被访问的 */ template< typename Tag, typename FunctionSig, template <typename> class DispatchPolicy = first_success_policy> struct method_decl { using method_type = method<FunctionSig, DispatchPolicy<FunctionSig>>; using tag_type = Tag; };method_decl中调用了method模板,该特性是由appbase/method提供,它是一个松散的链接应用程序层级的函数。调用者Caller可以抓取一个方法并且调用它,而提供者Providers能够抓取一个方法然后注册它。method模板消除了应用程序中不同插件之间的耦合度,可以在不同插件之间完成松散地函数调用。
method模板的使用方式如下图:
实体A注册了一个函数到method里,使用FunctionSig作为key。实体B传入FunctionSig在method中寻找method并调用。同样的,实体C、实体D都可以来调用,实体A并不关心谁来调用,它不会与调用者发生强关系。
回到:push_block,这一行代码:
app().get_method
block_sync就是key,通过该键能够找到对应的method: app().get_method<incoming::methods::block_sync>()。获取到method以后,可以直接调用,传入参数,通过make_shared将rpc参数转成signed_block对象的(共享)指针: std::make_shared<signed_block>(params)。下面去找到key为block_sync的method的位置,查找其相关的register语句:
my->_incoming_block_sync_provider = app().get_method<incoming::methods::block_sync>().register_provider([this](const signed_block_ptr& block){ my->on_incoming_block(block); });在producer_plugin中找到了method的注册位置,真实调用的函数为生产插件中的on_incoming_block函数,参数在外部处理传入符合signed_block指针类型。
on_incoming_block函数下面来看on_incoming_block函数。首先打印日志,提醒告知接收到区块的区块号。然后区块时间与本地节点时间对时,超过本地7秒开外的就终止程序,日志提示。接着,获取节点当前链环境:
chain::controller& chain = app().get_plugin<chain_plugin>().chain();接下来,判断本地节点是否已包含该区块,
signed_block_ptr controller::fetch_block_by_id( block_id_type id )const {// 传入区块id auto state = my->fork_db.get_block(id);// 在本地fork_db库中查找,是否之前已接收到分叉库了。 if( state && state->block ) return state->block; // 如果找到了,则返回区块。 auto bptr = fetch_block_by_number( block_header::num_from_id(id) ); //将id转为区块号,尝试以区块号来查找。 if( bptr && bptr->id() == id ) return bptr; // 以区块号来查找并且找到了,则直接返回区块。 return signed_block_ptr(); // 返回为空的signed_block对象。 }如果判断存在,则终止程序。不存在可以继续处理。处理接收新区块时,仍旧要丢弃掉pending状态的区块。
pending状态区块的优先级有时候很低,前面讲到在写入快照时,此处又提到接收新区块时,都要将pending区块先丢弃再进行。
总结所有需要先丢弃pending区块的操作还有:
producer_plugin_impl::maybe_produce_block
producer_plugin_impl::start_block,
producer_plugin::get_integrity_hash,获取完整hash
producer_plugin::update_runtime_options,更新环境参数
producer_plugin::resume
producer_plugin::create_snapshot
producer_plugin_impl::on_incoming_block