源码剖析 EOS | chain_plugin 插件的实现细节

debugfuture · 2018年09月05日 · 542 次阅读

本文转自【引力区开发者社区】

上几篇文章我们已经对EOS所采用的DPOS+BFT共识算法进行了详细介绍,相信各位朋友已经有了一个详细的认识,接下来我们将对EOS项目源码中第一个关键模块——插件模块,进行详细的分析与介绍,这篇文章,我们将分两篇介绍节点运行时默认开启的四大插件中第一个关键插件——chain_plugin。(本篇主要罗列所有函数与数据结构,下篇选择关键函数重点介绍代码逻辑)

1、chain_plugin插件总览

我们已经介绍过nodeos节点运行时,会默认开启四个插件:chain_plugin、http_plugin、net_plugin和produce_plugin。这四个插件实现了EOS节点运行中关键功能,其中chain_plugin是处理和聚合EOSIO节点上的链数据所需的核心插件。首先对chain_plugin代码进行剖析,主要可分为三个部分:

  • 关键数据结构:定义了一些关键的链上数据结构,如权限、账户等;
  • read_only:只读函数,主要用于读取EOS区块链上信息,如账户详情、出块节点身份等;
  • read_write:读写函数,主要用于push交易信息上链;

下面详细展开介绍这四部分。

2、关键数据结构

首先我们介绍一下该插件中定义的关键数据结构:

【权限结构】:该结构保存返回某账户的权限结构,包括待查询账户名(perm_name)、父账户名(parent)、该账户权限等级(required_auth)。

【区块链基本信息结构】:该结构用于返回当前节点运行的区块链基本信息,包括版本号、链id、区块高度、最新不可逆区块高度与ID、区块id、区块时间戳、区块的生产者、区块CPU和NET消耗。

区块生产者信息结构】:该结构用于返回出块节点基本信息,其中name定义位于contracts/eoslib/types.hpp文件中。

【账户基本信息结构】:该结构用于返回某账户的基本信息,其中包括账户名、该节点上一个生产的区块高度以及出块时间、该节点上次部署合约时间、该账户ram、net和cpu余额、该账户权限分配情况、该账户余额值、该账户投票情况。

【智能合约信息结构】:该结构用于返回某智能合约的基本信息,包括账户名、该合约wast格式文件、该合约wasm格式文件、该合约hash值、该合约abi文件,以及一些指定格式的返回结果,如json格式、合约action详情。

【交易被签署的公钥结构】:该结构用于存储指定交易的签名公钥。

【数据表结构】:该结构用于存储某数据表中的基本信息,包括数据表名称、智能合约账户、表作用域、升降序情况等。

【账户余额结构】:该结构用于存储某代币的基本信息,包括某账户拥有的某代币余额、某种代币的发行总量、最大供应量等信息。

【出块节点基本结构】:该结构用于存储当前区块链中出块节点的基本信息,包括出块节点的得票情况、当前出块节点账户名等。

3、read_only只读函数

这部分函数主要用于读取区块链上数据,并要求不能对链上数据有任何修改,主要包括:

  • get_info():根据参数返回相关基本信息;
  • get_table_index_name():根据参数返回表数据,其中可以指定参数的key类型;
  • get_currency_balance():检索指定账户的代币数量;
  • get_currency_stats():检索指定代币的发行情况;
  • get_producers():检索指定出块节点基本信息,如投票情况等;
  • get_producer_schedule():检索当前网络出块节点列表;
  • get_block_header_state():返回指定区块状态;
  • get_scheduled_transactions():返回指定交易信息;
  • get_block():返回指定区块基本信息;

4、read_write读写函数

这部分函数主要用于对区块链上数据进行提交与修改,要求对链上数据有修改,主要包括:

  • push_block():向区块链网络提交区块,主要包括自生产的区块以及验证过的区块;
  • push_transaction():向区块链网络提交交易数据,主要包括自发起的交易信息以及验证过的交易信息;
  • push_recurse():递归的向区块链网络提交交易数据,重复执行push_transaction()函数

主要选择几个关键函数重点介绍代码逻辑。

1、read_only类关键函数

1.1、get_currency_balance函数

该函数的功能为获取某账户token余额,具体执行逻辑如下

vector<asset> read_only::get_currency_balance( const read_only::get_currency_balance_params& p )const {

     //调用指定数据表操作接口,表拥有者为p.code
   const abi_def abi = eosio::chain_apis::get_abi( db, p.code );

   //按账户名返回表信息
   auto table_type = get_table_type( abi, "accounts" );

    //声明一个vector用以保存返回结果
   vector<asset> results;
   //调用walk_key_value_table模板,用以检索数据

   walk_key_value_table(p.code, p.account, N(accounts), [&](const key_value_object& obj){

      //判断表中数据合规性
      EOS_ASSERT( obj.value.size() >= sizeof(asset), chain::asset_type_exception, "Invalid data on table");
      //声明cursor资产,用作迭代结果返回值

      asset cursor;
      //初始化待检索数据

      fc::datastream<const char *> ds(obj.value.data(), obj.value.size());

      fc::raw::unpack(ds, cursor);



      EOS_ASSERT( cursor.get_symbol().valid(), chain::asset_type_exception, "Invalid asset");
      //判断返回结果是否为指定类型代币,若是则压入results中

      if( !p.symbol || boost::iequals(cursor.symbol_name(), *p.symbol) ) {

        results.emplace_back(cursor);

      }

      return !(p.symbol && boost::iequals(cursor.symbol_name(), *p.symbol));

   });



   return results;

}

1.2、get_currency_stats函数

该函数的功能用于返回某种代币的发行状态信息,具体执行逻辑如下:

fc::variant read_only::get_currency_stats( const read_only::get_currency_stats_params& p )const {

   fc::mutable_variant_object results;

   //调用指定数据表操作接口,表拥有者为p.code

   const abi_def abi = eosio::chain_apis::get_abi( db, p.code );

   auto table_type = get_table_type( abi, "stat" );
   //设置检索范围为表全局

   uint64_t scope = ( eosio::chain::string_to_symbol( 0, boost::algorithm::to_upper_copy(p.symbol).c_str() ) >> 8 );
   //重复上述流程

   walk_key_value_table(p.code, scope, N(stat), [&](const key_value_object& obj){

      EOS_ASSERT( obj.value.size() >= sizeof(read_only::get_currency_stats_result), chain::asset_type_exception, "Invalid data on table");



      fc::datastream<const char *> ds(obj.value.data(), obj.value.size());

      read_only::get_currency_stats_result result;



      fc::raw::unpack(ds, result.supply);

      fc::raw::unpack(ds, result.max_supply);

      fc::raw::unpack(ds, result.issuer);



      results[result.supply.symbol_name()] = result;

      return true;

   });



   return results;

}

1.3、get_producers函数

该函数用于返回当前出块节点信息,具体执行逻辑如下:


read_only::get_producers_result read_only::get_producers( const read_only::get_producers_params& p ) const {

   //获取EOSIO账户下的智能合约中拥有的数据表接口

   const abi_def abi = eosio::chain_apis::get_abi(db, N(eosio));

   const auto table_type = get_table_type(abi, N(producers));

   const abi_serializer abis{ abi, abi_serializer_max_time };

   //判断返回结果数据类型是否合规

   EOS_ASSERT(table_type == KEYi64, chain::contract_table_query_exception, "Invalid table type ${type} for table producers", ("type",table_type));

   //设置数据表指针

   const auto& d = db.db();

   const auto lower = name{p.lower_bound};



   static const uint8_t secondary_index_num = 0;

   //查找并返回保存producers数据的数据表指针

   const auto* const table_id = d.find<chain::table_id_object, chain::by_code_scope_table>(boost::make_tuple(N(eosio), N(eosio), N(producers)));

   const auto* const secondary_table_id = d.find<chain::table_id_object, chain::by_code_scope_table>(boost::make_tuple(N(eosio), N(eosio), N(producers) | secondary_index_num));

   EOS_ASSERT(table_id && secondary_table_id, chain::contract_table_query_exception, "Missing producers table");

   //初始化查找指针

   const auto& kv_index = d.get_index<key_value_index, by_scope_primary>();

   const auto& secondary_index = d.get_index<index_double_index>().indices();

   const auto& secondary_index_by_primary = secondary_index.get<by_primary>();

   const auto& secondary_index_by_secondary = secondary_index.get<by_secondary>();

   //声明get_producers_result类对象,用于存储返回值

   read_only::get_producers_result result;

   const auto stopTime = fc::time_point::now() + fc::microseconds(1000 * 10); // 10ms

   vector<char> data;

   //声明一个匿名函数,调用lower_bound检索方法

   auto it = [&]{

      if(lower.value == 0)

         return secondary_index_by_secondary.lower_bound(

            boost::make_tuple(secondary_table_id->id, to_softfloat64(std::numeric_limits<double>::lowest()), 0));

      else

         return secondary_index.project<by_secondary>(

            secondary_index_by_primary.lower_bound(

               boost::make_tuple(secondary_table_id->id, lower.value)));

   }();

   //逐个压入检索结果

   for( ; it != secondary_index_by_secondary.end() && it->t_id == secondary_table_id->id; ++it ) {

      if (result.rows.size() >= p.limit || fc::time_point::now() > stopTime) {

         result.more = name{it->primary_key}.to_string();

         break;

      }

      copy_inline_row(*kv_index.find(boost::make_tuple(table_id->id, it->primary_key)), data);

      if (p.json)

         result.rows.emplace_back(abis.binary_to_variant(abis.get_table_type(N(producers)), data, abi_serializer_max_time));

      else

         result.rows.emplace_back(fc::variant(data));

   }

   //最后压入总投票权重值

   result.total_producer_vote_weight = get_global_row(d, abi, abis, abi_serializer_max_time)["total_producer_vote_weight"].as_double();

   return result;

}

选择几个read_write类关键函数重点介绍代码逻辑。

2、read_write类关键函数

2.1、push_block函数

该函数定义了一个RPC接口,它的功能为向其他节点发送待选区块,具体执行逻辑如下

void read_write::push_block(const read_write::push_block_params& params, next_function<read_write::push_block_results> next) {

   //尝试向其他节点发送已经经过本地确认的已签名区块,并返回执行结果
   try {
   //加载block_sync函数,并将signed_block的指针作为参数传入

      app().get_method<incoming::methods::block_sync>()(std::make_shared<signed_block>(params));

      next(read_write::push_block_results{});

   } catch ( boost::interprocess::bad_alloc& ) {

      raise(SIGUSR1);

   } CATCH_AND_CALL(next);

}

其中get_method函数的作用时获取对传入类型声明的方法的引用,这将在第一次访问时构造方法。这允许插件之间的松散和延迟绑定。接下来看一下block_sync的定义,incoming::methods::block_sync的定义位于eos/plugins/chain_interface/include/eosio/chain/plugin_interface.hpp函数中:

namespace incoming {

      namespace channels {

         using block                 = channel_decl<struct block_tag, signed_block_ptr>;

         using transaction           = channel_decl<struct transaction_tag, packed_transaction_ptr>;

      }



      namespace methods {

         // 利用method_decl结构模板,声明一个将待同步的签名区块推送出去的功能结构

         using block_sync            = method_decl<chain_plugin_interface, void(const signed_block_ptr&), first_provider_policy>;

         using transaction_async     = method_decl<chain_plugin_interface, void(const packed_transaction_ptr&, bool, next_function<transaction_trace_ptr>), first_provider_policy>;

      }

   }

我们继续往上追溯,看看method_decl模板的定义,该定义位于eos\libraries\appbase\include\appbase\method.hpp中:

  /**

   *

   * @模板参数 Tag - API特定鉴别器,用于区分其他方面相同的函数签名

   * @模板参数 FunctionSig - 函数的签名

   * @模板参数 DispatchPolicy - 指示如何访问函数的提供者的调度策略默认为@ref first_success_policy

*/

 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模板的定义,method是一个松散链接的应用程序级函数。调用者可以获取方法并调用它,提供者可以获取方法并自行注册,这样就无需在应用程序中紧密耦合不同的插件。

template<typename FunctionSig, typename DispatchPolicy>

   class method final : public impl::method_caller<FunctionSig,DispatchPolicy> {

      public:

         //表示允许通过RAII拥有所有权的方法的注册提供者的类型,以及明确的未注册操作

         class handle {

            public:

               ~handle() {

                  unregister();

               }



               //显式取消注册此对象的此通道的提供程序将过期

               void unregister() {

                  if (_handle.connected()) {

                     _handle.disconnect();

                  }

               }



               // 可以构造和移动该处理器

               handle() = default;

               handle(handle&&) = default;

               handle& operator= (handle&& rhs) = default;



               // 不允许复制,因为这可以保护资源

               handle(const handle& ) = delete;

               handle& operator= (const handle& ) = delete;



            private:

               using handle_type = boost::signals2::connection;

               handle_type _handle;

               handle(handle_type&& _handle)

               :_handle(std::move(_handle))

               {}



               friend class method;

         };



         //注册此方法的提供者

         template<typename T>

         handle register_provider(T provider, int priority = 0) {

            return handle(this->_signal.connect(priority, provider));

         }



      protected:

         method() = default;

         virtual ~method() = default;



         //适用于类型擦除方法的删除器

         static void deleter(void* erased_method_ptr) {

            auto ptr = reinterpret_cast<method*>(erased_method_ptr);

            delete ptr;

         }



         //从指针中获取擦除方法

         static method* get_method(erased_method_ptr& ptr) {

            return reinterpret_cast<method*>(ptr.get());

         }



         //为类型擦除方法poiner构造unique_ptr

         static erased_method_ptr make_unique() {

            return erased_method_ptr(new method(), &deleter);

         }



         friend class appbase::application;

   };

至此,我们知道了push_block函数主要的功能就是定义了一个松耦合模式下发送已签名区块的带执行权限的区块。

2.2、 push_transaction函数

大致思路如上,该函数的功能用于发送一个已签名交易,具体执行逻辑如下:

void read_write::push_transaction(const read_write::push_transaction_params& params, next_function<read_write::push_transaction_results> next) {

   try {
      //在动态内存中为packed_transaction分配一个对象并初始化它,返回指向此对象的智能指针pretty_input

      auto pretty_input = std::make_shared<packed_transaction>();

      //分配一个该函数解析器
      auto resolver = make_resolver(this, abi_serializer_max_time);

      //尝试序列化该abi
      try {

         abi_serializer::from_variant(params, *pretty_input, resolver, abi_serializer_max_time);

      } EOS_RETHROW_EXCEPTIONS(chain::packed_transaction_type_exception, "Invalid packed transaction")



      //定义这个函数,并根据执行结果返回相应指针
      app().get_method<incoming::methods::transaction_async>()(pretty_input, true, [this, next](const fc::static_variant<fc::exception_ptr, transaction_trace_ptr>& result) -> void{

         if (result.contains<fc::exception_ptr>()) {

            next(result.get<fc::exception_ptr>());

         } else {

            auto trx_trace_ptr = result.get<transaction_trace_ptr>();



            try {

               fc::variant pretty_output;

               pretty_output = db.to_variant_with_abi(*trx_trace_ptr, abi_serializer_max_time);



               chain::transaction_id_type id = trx_trace_ptr->id;

               next(read_write::push_transaction_results{id, pretty_output});

            } CATCH_AND_CALL(next);

         }

      });
   } catch ( boost::interprocess::bad_alloc& ) {

      raise(SIGUSR1);

   } CATCH_AND_CALL(next);

}

2.3、push_transactions函数

该函数用于批量发送交易信息,具体执行逻辑如下:

void read_write::push_transactions(const read_write::push_transactions_params& params, next_function<read_write::push_transactions_results> next) {

   try {
      //首先判断交易数量,不能超过1000,否则报错

      EOS_ASSERT( params.size() <= 1000, too_many_tx_at_once, "Attempt to push too many transactions at once" );

      //在动态内存中为param中所有交易信息分配一个对象并初始化它,返回指向此对象的智能指针params_copy 
      auto params_copy = std::make_shared<read_write::push_transactions_params>(params.begin(), params.end());

      //在动态内存中为执行结果分配一个对象并初始化它,返回指向此对象的智能指针result       auto result = std::make_shared<read_write::push_transactions_results>();

      result->reserve(params.size());

      push_recurse(this, 0, params_copy, result, next);



   } CATCH_AND_CALL(next);

}
暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册