EOS中plugin之producer_plugin

版权声明:本文为博主原创文章,转载请说明出处 https://blog.csdn.net/t46414704152abc/article/details/88867723

EOS中plugin之producer_plugin

EOS中的插件是非常重要的工具,其中大大小小总共有26个插件,其中比较重要的插件有chain_plugin、producer_plugin、http_plugin、net_plugin等四个插件。这四个插件在EOS服务器端启动后也开始启动进行工作。

abstract_plugin

EOS中所有插件继承于plugin类,而plugin类又继承于abstract_plugin类。
abstract_plugin中规定了每个插件的4个状态,这4个状态依次如下。

  • registered,表示插件已经注册, 每个插件初始化后就是注册的状态。
  • initialized,表示插件已经初始化,插件之后注册之后才能初始化。
  • started, 表示插件正在运行中。
  • stopped,表示插件停止运行。

下面是abstract_plugin的源代码,由于是个抽象类,abstract_plugin定义在appbase\appbase\plugin.hpp中。

class abstract_plugin {
    public:
        // 插件的4个状态。
        enum state {
        registered, ///< the plugin is constructed but doesn't do anything
        initialized, ///< the plugin has initialized any state required but is idle
        started, ///< the plugin is actively running
        stopped ///< the plugin is no longer running
        };

        virtual ~abstract_plugin(){}
        virtual state get_state()const = 0; // 查询插件当前的状态
        virtual const std::string& name()const  = 0;    // 获取插件名称
        virtual void set_program_options( options_description& cli, options_description& cfg ) = 0;                 // 设置插件参数
        virtual void initialize(const variables_map& options) = 0;//初始化插件
        virtual void handle_sighup() = 0;               // 这个干嘛用的不清楚
        virtual void startup() = 0;                     // 启动插件
        virtual void shutdown() = 0;                    // 关闭插件
};

plugin

plugin这个类继承了abstract_plugin,plugin的代码在appbase\apppbase\apllication.hpp中,这里比较奇怪的是,为什么plugin的定义不再plugin.hpp文件中,而plugin.hpp中定义了abstract_plugin类。

plugin是一个模板类,在plugin中,除去handle_sighup函数之外,已经实现了abstract_plugin中的全部纯虚函数。handle_sighup函数的含义,目前我不太清楚。

template<typename Impl>
   class plugin : public abstract_plugin {
      public:
         plugin():_name(boost::core::demangle(typeid(Impl).name())){}
         virtual ~plugin(){}

         virtual state get_state()const override         { return _state; }
         virtual const std::string& name()const override { return _name; }

         virtual void register_dependencies() {
            static_cast<Impl*>(this)->plugin_requires([&](auto& plug){});
         }

         virtual void initialize(const variables_map& options) override {
            if(_state == registered) {
               _state = initialized;
               static_cast<Impl*>(this)->plugin_requires([&](auto& plug){ plug.initialize(options); });
               static_cast<Impl*>(this)->plugin_initialize(options);
               //ilog( "initializing plugin ${name}", ("name",name()) );
               app().plugin_initialized(*this);
            }
            assert(_state == initialized); /// if initial state was not registered, final state cannot be initialized
         }

         virtual void handle_sighup() override {
         }
         // 插件启动函数
         virtual void startup() override {
            if(_state == initialized) {
               _state = started;
               static_cast<Impl*>(this)->plugin_requires([&](auto& plug){ plug.startup(); });
               static_cast<Impl*>(this)->plugin_startup();
               app().plugin_started(*this);     // 
            }
            assert(_state == started); // if initial state was not initialized, final state cannot be started
         }

         virtual void shutdown() override {
            if(_state == started) {
               _state = stopped;
               //ilog( "shutting down plugin ${name}", ("name",name()) );
               static_cast<Impl*>(this)->plugin_shutdown();
            }
         }

      protected:
         plugin(const string& name) : _name(name){}

      private:
         state _state = abstract_plugin::registered;
         std::string _name;
   };

首先可以看到plugin的初始化函数,由于plugin是一个模板类,在初始化过程中,传入的模板类的名称赋值给了plugin的protected变量_name,里面唯一不太理解的是,为什么这个_name是protected的类型而不是private类型。typeid在头文件typeindex中,主要是获得某个类的名称。

此外,plugin中实现了插件的startup()、initialized()和shutdown()三个功能。在initialize()函数中,首先判断当前状态是否是registered状态,随后更改状态为initialized状态,然后查询当前插件依赖的其他插件并且将其他插件初始化,随后初始化插件自身。注意到插件启动完成后有如下一句代码:

app().plugin_initialized(*this);

app()函数返回客户端的引用,随后客户端将当前初始化的插件变量放入到当前客户端中已初始化插件的vector中。

在startup()之前,可以看到,插件启动之前,其状态必须是已经初始化的状态,这表明插件启动之前必须初始化。随后设置状态为启动状态。然后查询当前插件启动需要的其他插件并启动这些插件,随后再启动插件自身,随后客户端将当前已启动的插件变量放入到当前客户端中已启动插件的vector中。

在shutdown()函数中,特别厉害的一个设计就是关闭插件的调用,因为不同的插件,可能关闭方式不一样,但是在plugin中仍然也实现了这个方法,实在是太巧妙了。最精髓的就是这句代码:

static_cast<Impl*>(this)->plugin_shutdown();

将当前指针转换成模板类初始化时传进的类,然后调用这个类的plugin_shutdown()函数。好像很牛批,可是我仔细一想,最后不还是调用每个类自己的plugin_shutdown()函数了吗,每个类中自己实现这个功能好像也可以,这样设计就是看起来很牛批,但是没什么卵用,奇技淫巧而已~

现在介绍下EOS具体的插件,producer_plugin,这个插件主要负责超级节点的区块生产、同步以及新区快的校验工作,是4个插件里面最重要的一个插件。

producer_plugin

(1). producer_plugin.hpp

producer_plugin插件的实现位于plugins\producer_plugin下。producer_plugin类的定义,就很头铁,定义方式如下。

class producer_plugin : public appbase::plugin<producer_plugin>{
    ...
};

在定义的过程中就传入了producer_plugin的类名,这个名称会传给plugin中的_name变量。
为什么会有appbase这个玩意儿呢,因为这是一个命名空间,plugin就定义在appbase命名空间中。

接下来我们看看producer_plugin的头文件,由于头文件比较长,口味略重,非战斗人员请迅速撤离。

class producer_plugin : public appbase::plugin<producer_plugin> {
public:
   APPBASE_PLUGIN_REQUIRES((chain_plugin)(http_client_plugin))

   struct runtime_options {
      fc::optional<int32_t> max_transaction_time;
      fc::optional<int32_t> max_irreversible_block_age;
      fc::optional<int32_t> produce_time_offset_us;
      fc::optional<int32_t> last_block_time_offset_us;
      fc::optional<int32_t> max_scheduled_transaction_time_per_block_ms;
      fc::optional<int32_t> subjective_cpu_leeway_us;
      fc::optional<double>  incoming_defer_ratio;
   };

   struct whitelist_blacklist {
      fc::optional< flat_set<account_name> > actor_whitelist;
      fc::optional< flat_set<account_name> > actor_blacklist;
      fc::optional< flat_set<account_name> > contract_whitelist;
      fc::optional< flat_set<account_name> > contract_blacklist;
      fc::optional< flat_set< std::pair<account_name, action_name> > > action_blacklist;
      fc::optional< flat_set<public_key_type> > key_blacklist;
   };

   struct greylist_params {
      std::vector<account_name> accounts;
   };

   struct integrity_hash_information {
      chain::block_id_type head_block_id;
      chain::digest_type   integrity_hash;
   };

   struct snapshot_information {
      chain::block_id_type head_block_id;
      std::string          snapshot_name;
   };

   producer_plugin();
   virtual ~producer_plugin();

   virtual void set_program_options(
      boost::program_options::options_description &command_line_options,
      boost::program_options::options_description &config_file_options
      ) override;


   // 判断一个公钥是否是当前区块生产者的公钥
   bool                   is_producer_key(const chain::public_key_type& key) const; 
   // 应该是对摘要进行签名,这里面的key应该是私钥。        
   chain::signature_type  sign_compact(const chain::public_key_type& key, const fc::sha256& digest) const;

   // 对插件进行初始化
   virtual void plugin_initialize(const boost::program_options::variables_map& options);
   // 插件启动函数
   virtual void plugin_startup();
   // 插件关闭函数
   virtual void plugin_shutdown();
   // 暂停运行和继续运行
   void pause();
   void resume();
   // 返回插件是否被暂停的状态
   bool paused() const;

   // 更新运行的相关参数
   void update_runtime_options(const runtime_options& options);

   // 获取运行时的相关参数
   runtime_options get_runtime_options() const;

   // 增加grey_list
   void add_greylist_accounts(const greylist_params& params);
   // 删除grey_list
   void remove_greylist_accounts(const greylist_params& params);
   // 获取greylist
   greylist_params get_greylist() const;

   // 获取whitelist和设置whitelist
   whitelist_blacklist get_whitelist_blacklist() const;
   void set_whitelist_blacklist(const whitelist_blacklist& params);

   // 获取integrity_hash_information
   integrity_hash_information get_integrity_hash() const;
   
   // 创建一个快照,snapshot中文意思是快照
   snapshot_information create_snapshot() const;

   // 定义一个返回值为空,传入变量为const chain::producer_confirmation&的signal
   // 这个信号用于区块的确认。
   signal<void(const chain::producer_confirmation&)> confirmed_block;
private:
    // producer_plugin_impl的指针,负责所有任务的执行
    // producer_plugin_impl类的定义在producer_plugin.cpp中
   std::shared_ptr<class producer_plugin_impl> my; 
};

producer_plugin定义完毕之后第一个public函数就很恶心咯,这是什么鬼!!!仔细看看这些大写字母的定义,转换成小写字母,即appbase_plugin_requires,还记得前面说每个插件初始化或者启动的时候都要初始化或者启动它依赖的插件这个过程吗?这感觉应该是说producer_plugin类启动的时候依赖chain_plugin和http_client_plugin两个插件。

APPBASE_PLUGIN_REQUIRES((chain_plugin)(http_client_plugin))

大写的显示,应该是说明这一个宏定义,果然,我们翻了一下,宏定义在plugin.hpp文件中,具体定义如下:

#define APPBASE_PLUGIN_REQUIRES( PLUGINS )                               \
   template<typename Lambda>                                           \
   void plugin_requires( Lambda&& l ) {                                \
      BOOST_PP_SEQ_FOR_EACH( APPBASE_PLUGIN_REQUIRES_VISIT, l, PLUGINS ) \
   }

#define APPBASE_PLUGIN_REQUIRES_VISIT( r, visitor, elem ) \
  visitor( appbase::app().register_plugin<elem>() ); 

哇~~~,越来越恶心了,这个宏定义,指向一个模板函数,这个模板函数的参数是一个函数,里面用到了BOOST_PP_SEQ_FOR_EACH,这个一般如下用法:

  • BOOST_PP_SEQ_FOR_EACH(macro, r, data)
  • macro,一个以格式macro(r, data, elem)定义的三元宏。该宏被BOOST_PP_SEQ_FOR_EACH按照seq中每个元素进行展开。展开该宏,需要用到下一个BOOST_PP_FOR的重复项、备用数据data和当前元素。
  • data,备用数据,用于传给macro。
  • seq,用于供macro按照哪个序列进行展开。

具体的细节尚未弄清楚,但是明确的是,这个hpp文件可以看出producer_plugin插件依赖两个插件,即chain_plugin和http_client_plugin。

随后再producer_plugin中定义了一系列结构体。这些结构体都用到了一个optional的类。简单地来说,这个类类似于boost::optional的概念,主要用于实现未初始化的概念。函数并不能总是返回有意义的结果,有时候函数可能返回“无意义”的值,一般来说我们通常使用一个不再正常解空间的一个哨兵来表示无意义的概念,如NULL,-1,end()或者EOF.然后对于不同的应用场合,这些哨兵并不是通用的,而且有时候可能不存在这种解空间之外的哨兵。optional很像一个仅能存放一个元素的容器,它实现了"未初始化"的概念:如果元素未初始化,那么容器就是空的,否则,容器内就是有效的,已经初始化的值。optional的真实接口很复杂,因为它要能包装任何的类型。

定义的这些结构体,我想应该是producer_plugin运行时配置的一些参数,大致知道这个概念即可。随后是producer_plugin的构造函数、析构函数以及设置参数的函数。更加详细的内容,我已经写在注释中,可以参看,至于producer_plugin中一些方法的具体实现,暂时不予理会,知道其作用就可以了。

(2). plugin_startup

producer_plugin.cpp中插件的plugin_shutdown, pause, resume等函数不是很重要,因此这里不再介绍。但是中最重要的一个函数是plugin_startup函数,nodeos节点启动之后,启动producer_plugin插件的接口就是这个函数,这里需对这个函数进行详细的解析,但是我们仍然会跳过一些细枝末节的东西,重点看其主要脉络。由于源码中这个函数内容比较多,这里不再展示源码,直接从主要流程开始。

在函数里面首先获取一个变量:chain

chain::controller &chain = my->chain->chain_plug->chain()

my是一个produer_plugin_impl类的智能指针,这个指针具体负责交易的打包、区块的生产和检查等工作。通过chain可以访问到区块链上的一些信息。

随后chain连接两个信号量,分别执行的函数是on_block()和on_irreversible_block。

my->_accepted_block_connection.emplace(chain.accepted_block.connect([this](const auto &bsp) { my->on_block(bsp); }));
      my->_irreversible_block_connection.emplace(chain.irreversible_block.connect([this](const auto &bsp) { my->on_irreversible_block(bsp->block); }));

在boost中,进行connect之后返回一个connection的对象,my将返回的两个对象放入自己的connection容器中。

随后获得了区块链上最近的一个不可逆区块的块号和其指针

const auto lib_num = chain.last_irreversible_block_num();// 返回块号
const auto lib = chain.fetch_block_by_number(lib_num); // 返回指针

如果当前区块指针不为空,则进入my->on_irreversible_block(lib)函数。my->on_irreversible_block函数就是将不可逆区块的时间设置为lib指向的区块的时间。
如果lib指针为空,设置不可逆区块的时间为当前时间的最大值,即设置为0xffffffff秒,时间大致为139年,那时候BM应该早就跪了~

if (lib){
   my->on_irreversible_block(lib);
}
else{
   // maximum 大致是136年
   my->_irreversible_block_time = fc::time_point::maximum();
}

接下来,判断my中的区块生产者列表是否为空,若当前节点可以生产区块,如果是第一次接入的超节点,则展示new_chain_banner标志。随后开始进入区块生产的大循环中,区块生产的函是由my控制的。看起来my这个插件无法避免的需要介绍了。

if (my->_production_enabled){
   if (chain.head_block_num() == 0)
   { // 如果是创世块,展示新区块的标语
      new_chain_banner(chain);
   }
   //_production_skip_flags |= eosio::chain::skip_undo_history_check;
}
   // 开始进入区块生产循环中,持续不断的开始生产区块
   my->schedule_production_loop();

至此,plugin_startup函数的主要情况已经介绍完毕,my->schedule_produetion_loop函数的介绍,将在produer_plugin_impl类的介绍中进行。

producer_plugin_impl

producer_plugin_impl类中的变量

produer_plugin_impl类的定义,上来我就懵逼了,这是什么!!!enable_shared_from_this也是一个类,但是这种继承的形式到底是怎么肥四?

class producer_plugin_impl : public std::enable_shared_from_this<producer_plugin_impl>

查了一下网上的解释,enable_shared_from_this是c++ 11的新特性,使用这个特性就可以获得一个对象的多个shared_ptr指针,但是又不会造成对象的多次释放问题。具体的解释可以参看下面两个链接:

stack overflow

cpp reference

其初始化函数只有1个,里面_timer完成io操作,_transaction_ack_channel主要接收交易,具体形式如下:

 public:
   producer_plugin_impl(boost::asio::io_service &io)
       : _timer(io), _transaction_ack_channel(app().get_channel<compat::channels::transaction_ack>()){
   }

老实说,produer_plugin_impl中定义的变量很多,这些变量都是public属性。因为这个类具体负责区块生产,因此涉及到的事务非常繁杂,所以变量比较多也算正常。


optional<fc::time_point> calculate_next_block_time(const account_name &producer_name, const block_timestamp_type &current_block_time) const;
void schedule_production_loop();
void produce_block();
bool maybe_produce_block();

boost::program_options::variables_map _options;  // 程序启动的一些选项
bool _production_enabled = false;   //能够生产区块
bool _pause_production = false;     // 是否暂停生产
uint32_t _production_skip_flags = 0; //eosio::chain::skip_nothing;

// 这是啥玩意,我不明白
using signature_provider_type = std::function<chain::signature_type(chain::digest_type)>; 
// 映射公钥和签名
std::map<chain::public_key_type, signature_provider_type> _signature_providers;
std::set<chain::account_name> _producers; // BP名单
boost::asio::deadline_timer _timer;       // 负责io的_timer
std::map<chain::account_name, uint32_t> _producer_watermarks;  // BP的水印
pending_block_mode _pending_block_mode;                        // 添加区块的模式,producing和speculate两个模式
transaction_id_with_expiry_index _persistent_transactions;     // 过期的交易
fc::optional<boost::asio::thread_pool> _thread_pool;           // 线程池

int32_t _max_transaction_time_ms;                              // 交易的延迟时间
fc::microseconds _max_irreversible_block_age_us;
// 非最后一个区块产生时间的偏移量,按微秒计算。负值会导致块更早出去,正值会导致块更晚出去。
int32_t _produce_time_offset_us = 0;
// 最后一个区块产生时间的偏移量,按微秒计算。负值会导致块更早出去,正值会导致块更晚出去。
int32_t _last_block_time_offset_us = 0;
int32_t _max_scheduled_transaction_time_per_block_ms;          // 一个区块全部交易打包完毕的最大时间耗费

fc::time_point _irreversible_block_time;                       // 不可逆区块的时间
fc::microseconds _keosd_provider_timeout_us;                   // 钱包客户端超时时间 us为单位

time_point _last_signed_block_time;                            // 最近一个签名区块的时间
time_point _start_time = fc::time_point::now();                // 启动时间,当前时间
uint32_t _last_signed_block_num = 0;                           // 最近一个签名区块的块号

producer_plugin *_self = nullptr;                              // 指向producer_plugin的指针
chain_plugin *chain_plug = nullptr;                            // 指向chain_plugin的指针

incoming::channels::block::channel_type::handle _incoming_block_subscription; // 订阅收到的区块?
incoming::channels::transaction::channel_type::handle _incoming_transaction_subscription; // 订阅收到交易?

compat::channels::transaction_ack::channel_type &_transaction_ack_channel;    // 接收交易的通道

incoming::methods::block_sync::method_type::handle _incoming_block_sync_provider;   // 同步区块的数据提供方
incoming::methods::transaction_async::method_type::handle _incoming_transaction_async_provider; // 同步交易的数据提供方

transaction_id_with_expiry_index _blacklisted_transactions;       // 超时交易的id

fc::optional<scoped_connection> _accepted_block_connection;       // 存储已经接收区块的connection
fc::optional<scoped_connection> _irreversible_block_connection;   // 存储不可逆区块的connection

/*
      * HACK ALERT
      * Boost timers can be in a state where a handler has not yet executed but is not abortable.
      * As this method needs to mutate state handlers depend on for proper functioning to maintain
      * invariants for other code (namely accepting incoming transactions in a nearly full block)
      * the handlers capture a corelation ID at the time they are set.  When they are executed
      * they must check that correlation_id against the global ordinal.  If it does not match that
      * implies that this method has been called with the handler in the state where it should be
      * cancelled but wasn't able to be.
      */
uint32_t _timer_corelation_id = 0;                        

// keep a expected ratio between defer txn and incoming txn
double _incoming_trx_weight = 0.0;
double _incoming_defer_ratio = 1.0; // 1:1

// path to write the snapshots to
bfs::path _snapshots_dir;

可能会奇怪,为什么my指针中的变量,在其初始化函数中都没有看到初始化的工作,例如其中的chain_plugin和producer_plugin指针。实际上,my中这些变量的初始化工作已经在producer_plugin的initialized函数中初始化完毕了。

schedule_production_loop 函数

producer_plugin的startup函数中就是调用了producer_plugin_impl中的shcedule_production_loop函数开始生产区块。因此,我们首先从这个函数入手,开始分析producer_plugin_impl是如何生产区块的。

函数首先获得了chain的引用和指向producer_plugin_impl的一个weak_ptr指针,关闭了_timer的io操作。

   chain::controller &chain = chain_plug->chain();
   _timer.cancel(); // _timer 是boost库中asio的一个定时器,关闭所有异步等待
   std::weak_ptr<producer_plugin_impl> weak_this = shared_from_this();
   auto result = start_block()

随后尝试生产区块,调用了start_block函数,start_block函数返回结果有succeed,failed,waiting,exhausted4种,针对4中情况分别执行不用的流程。

  1. failed, 获取各种调度信息异常,则重新获取数据进行调度;

  2. waitting,其它节点正在出块,则进行等待;

  3. producing,轮到本节点出块,则进行出块操作;

  4. succeed,生产区块成功,计算下一个生产者出块的时间。

当start_block()返回结果为failed的时,进入异步等待状态,稍后再尝试生产区块。

       if (result == start_block_result::failed)
   {
      elog("Failed to start a pending block, will try again later");
     
      _timer.expires_from_now(boost::posix_time::microseconds(config::block_interval_us / 10)); // 0.05秒后定时器失效

      // 稍后继续尝试schedule_production_loop()函数
      _timer.async_wait([weak_this, cid = ++_timer_corelation_id](const boost::system::error_code &ec) {
         // 获得this的shared_ptr指针以判断是否被销毁,销毁返回空的shared_ptr
         auto self = weak_this.lock();
         if (self && ec != boost::asio::error::operation_aborted && cid == self->_timer_corelation_id)
         {
            self->schedule_production_loop();
         }
      });
   }

如果返回结果是waiting,表示本地的区块链还在从eos网络中下载区块已进行信息同步过程中,这个过程中还无法生产区块。随后查看当前是否还能继续生产区块,如果可以,则待会儿生产区块,即调用函数schedule_delayed_production_loop,这个函数这里不再进行仔细分析,主要就是过一段时间后这个函数中还会调用schedule_production_loop函数进行区块生产。
返回结果时waiting的情况下,对应代码如下:

   else if (result == start_block_result::waiting)
   { // 当前还在同步区块信息,等待中...
       // waiting的状态有两种情况,要么生产区块的BP名单为空,或者是生产区块受外部命令而停止
      if (!_producers.empty() && !production_disabled_by_policy())
      {
         fc_dlog(_log, "Waiting till another block is received and scheduling Speculative/Production Change");
         // 过会儿再进行区块生产
         schedule_delayed_production_loop(weak_this, calculate_pending_block_time());
      }
      else
      {
         fc_dlog(_log, "Waiting till another block is received");
         // 其他区块还没有同步完毕,因此急需等待同步完成才能生产区块
      }
   }

如果返回结果是speculateing模式,并且还可以继续进行区块生产,但是生产者不确定这个区块是否合法,所以小心翼翼的过一会儿继续生产区块,过程如下:

else if (_pending_block_mode == pending_block_mode::speculating && !_producers.empty() && !production_disabled_by_policy())
{
   fc_dlog(_log, "Specualtive Block Created; Scheduling Speculative/Production Change");
   EOS_ASSERT(chain.pending_block_state(), missing_pending_block_state, "speculating without pending_block_state");
   const auto &pbs = chain.pending_block_state();
   schedule_delayed_production_loop(weak_this, pbs->header.timestamp);
}

如果返回结果是producing模式,表示已经成功的生产了一个区块,但是还需要其他一些验证工作。这里有个问题就是,为什么speculating模式下没有验证,非得在producing模式下验证生产的区块呢?

首先验证区块截止时间是否大于当前时间,如果大于当前时间,表明生产区块成功在截止日期内,是合法的区块,然后记录日志。如果截止日期超过现在,表明生产的区块已经过期了,此时将生产区块的时间减小0.5s,看是否超时并记录日志,这一步完成之后进行区块的同步操作。
代码如下:

else if (_pending_block_mode == pending_block_mode::producing)
{
   // 成功打包了一个区块,但是得看看打包的区块是否超时
   static const boost::posix_time::ptime epoch(boost::gregorian::date(1970, 1, 1));
   // pending_block_time 返回最后一个区块的时间戳(是刚刚生产的区块的时间戳还是之前有效区块的时间戳,暂时还不清楚)
   auto deadline = calculate_block_deadline(chain.pending_block_time());

   if (deadline > fc::time_point::now())
   {
      // ship this block off no later than its deadline
      EOS_ASSERT(chain.pending_block_state(), missing_pending_block_state, "producing without pending_block_state, start_block succeeded");
      _timer.expires_at(epoch + boost::posix_time::microseconds(deadline.time_since_epoch().count()));
      fc_dlog(_log, "Scheduling Block Production on Normal Block #${num} for ${time}", ("num", chain.pending_block_state()->block_num)("time", deadline));
   }
   else
   {
      EOS_ASSERT(chain.pending_block_state(), missing_pending_block_state, "producing without pending_block_state");
      auto expect_time = chain.pending_block_time() - fc::microseconds(config::block_interval_us);
      // ship this block off up to 1 block time earlier or immediately
      if (fc::time_point::now() >= expect_time)
      {
         _timer.expires_from_now(boost::posix_time::microseconds(0));
         fc_dlog(_log, "Scheduling Block Production on Exhausted Block #${num} immediately", ("num", chain.pending_block_state()->block_num));
      }
      else
      {
         _timer.expires_at(epoch + boost::posix_time::microseconds(expect_time.time_since_epoch().count()));
         fc_dlog(_log, "Scheduling Block Production on Exhausted Block #${num} at ${time}", ("num", chain.pending_block_state()->block_num)("time", expect_time));
      }
   }

   _timer.async_wait([&chain, weak_this, cid = ++_timer_corelation_id](const boost::system::error_code &ec) {
      auto self = weak_this.lock();
      if (self && ec != boost::asio::error::operation_aborted && cid == self->_timer_corelation_id)
      {
         // pending_block_state expected, but can't assert inside async_wait
         auto block_num = chain.pending_block_state() ? chain.pending_block_state()->block_num : 0;
         auto res = self->maybe_produce_block();
         fc_dlog(_log, "Producing Block #${num} returned: ${res}", ("num", block_num)("res", res));
      }
   });
}

如果对其他一些细节的代码不太明白,也不必在意,因为我也不太清楚。但是我们注意到同步操作中的一段代码,如下:

_timer.async_wait(
   [&chain, weak_this, cid = ++_timer_corelation_id](const boost::system::error_code &ec) {
   auto self = weak_this.lock();
   if (self && ec != boost::asio::error::operation_aborted && cid == self->_timer_corelation_id)
   {
      // pending_block_state expected, but can't assert inside async_wait
      auto block_num = chain.pending_block_state() ? chain.pending_block_state()->block_num : 0;
      auto res = self->maybe_produce_block();
      fc_dlog(_log, "Producing Block #${num} returned: ${res}", ("num", block_num)("res", res));
   }
});

_timer.async_wait应该是将生产出来的在群里面广播。这个函数接受一个lambda函数,表示同步的方法,同步过程中有一个maybe_produce_block()函数,我想应该是某个block procuder(以后简称BP)生产了一个区块,但是还没有得到其他BP的确认,这是个进行同步请求确认的过程。因此,我们再仔细看看进行同步的细节(不过貌似很多细节我也看不懂哎…囧…)。

mayb_produce_block函数的主要内容如下:

bool producer_plugin_impl::maybe_produce_block()
{
   auto reschedule = fc::make_scoped_exit([this] {
      schedule_production_loop();
   });

   try
   {
      try
      {
         produce_block();
         return true;
      }
      catch (const guard_exception &e)
      {
         chain_plug->handle_guard_exception(e);
         return false;
      }
      FC_LOG_AND_DROP();
   }
   catch (boost::interprocess::bad_alloc &)
   {
      raise(SIGUSR1);
      return false;
   }

   fc_dlog(_log, "Aborting block due to produce_block error");
   chain::controller &chain = chain_plug->chain();
   chain.abort_block();
   return false;
}

上来就是一句我不懂的函数:

auto reschedule = fc::make_scoped_exit(
   [this]{
   schedule_production_loop();
}
);

也就是说,往里面传了一个lambda函数,函数就是重复生产区块的函数。但是fc::make_scope_exit又是什么意思呢?这里面大概介绍一下,make_scoped_exit函数中传入一个lambda函数,may_produe_block函数结束时reschedule函数只是定义了一下,reschedule隶属于scoped_exit类,这个类的析构函数中调用了schedule_production_loop();表明函数结束之后继续生产区块。
随后may_produce_block中有一个重要的函数,即produce_block()函数,我们继续追踪,这个函数代码如下:

void producer_plugin_impl::produce_block()
{
   //ilog("produce_block ${t}", ("t", fc::time_point::now())); // for testing _produce_time_offset_us
   EOS_ASSERT(_pending_block_mode == pending_block_mode::producing, producer_exception, "called produce_block while not actually producing");
   
   chain::controller &chain = chain_plug->chain();
   // 获取当前打包好的区块的指针
   const auto &pbs = chain.pending_block_state();
   // 获取打包的区块的区块头
   const auto &hbs = chain.head_block_state();
   EOS_ASSERT(pbs, missing_pending_block_state, "pending_block_state does not exist but it should, another plugin may have corrupted it");
   // 寻找BP的私钥
   auto signature_provider_itr = _signature_providers.find(pbs->block_signing_key);

   EOS_ASSERT(signature_provider_itr != _signature_providers.end(), producer_priv_key_not_found, "Attempting to produce a block for which we don't have the private key");

   //idump( (fc::time_point::now() - chain.pending_block_time()) );
   // 将区块内容写入数据库中,确定区块头中的merkel_root等内容
   chain.finalize_block();

   // 对区块进行签名
   chain.sign_block([&](const digest_type &d) {
      auto debug_logger = maybe_make_debug_time_logger();
      return signature_provider_itr->second(d);
   });
   // 往本地的分叉的区块链数据库中提交区块,因为还没有确认
   chain.commit_block();
   // 获取区块时间戳
   auto hbt = chain.head_block_time();
   //idump((fc::time_point::now() - hbt));

   // 获取刚刚添加的最新的区块头状态
   block_state_ptr new_bs = chain.head_block_state();
   // 记录最新的区块生产者和其生产的区块号
   _producer_watermarks[new_bs->header.producer] = chain.head_block_num();

   ilog("Produced block ${id}... #${n} @ ${t} signed by ${p} [trxs: ${count}, lib: ${lib}, confirmed: ${confs}]",
        ("p", new_bs->header.producer)("id", fc::variant(new_bs->id).as_string().substr(0, 16))("n", new_bs->block_num)("t", new_bs->header.timestamp)("count", new_bs->block->transactions.size())("lib", chain.last_irreversible_block_num())("confs", new_bs->header.confirmed));
}

至此,大致对producer_plugin这个插件的内容以及主要功能有一个大致的了解。
这个插件主要负责区块的接收、检验、打包和本地写入功能。

猜你喜欢

转载自blog.csdn.net/t46414704152abc/article/details/88867723