EOS行为核心:解析插件chain_plugin

2021-11-23 10:42:02 浏览数 (1)

以前曾动过写一写chain_plugin插件的念头,因为某些事情耽搁了,最近发现文彬先生写的一篇关于chain_plugin的介绍,真可谓面面俱到,高山仰止景行行止,遂经原作者同意转载至此,和大家一起分享学习。本文原文链接如下:

https://www.cnblogs.com/Evsward/p/chainPlugin.html

原作者还写了一系列的文,可以说我是看着原作者的文长大的(

),大家有兴趣的话也可以去博客园围观下。由于公众号正文内容不能超过5000字,因此本文只摘选前半部分,后面的rpc接口可以去cleos的main函数对应着去chain_plugin找到。

EOS提供了大量的rpc接口,其中功能性最强,使用最频繁的一部分接口是EOS的行为核心,由chain_api_plugin提供,具体实现是在chain_plugin。

一、接口列表chain_api_plugin

rpc调用逻辑,chainbase数据库底层原理,nodeos启动流程,plugin生命周期在前文都有介绍。本节直接研究chain_plugin的内容,研究入口会从chain_api_plugin中暴漏的rpc接口切入,这些接口是非常熟悉的,因为之前演练cleos相关命令时调用的也是rpc。首先展示一下所有的接口内容:

代码语言:javascript复制
_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

代码语言:javascript复制
#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。

代码语言:javascript复制
/**
 *  @attention 目前调用CALL函数的只有read_only应用。
 *  @param api_name "chain'
 *  @param api_handle app().get_plugin<chain_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<api_namespace::call_name ## _params>()); 
          /*回调函数返回处理结果,此处也规定了一个命名规则,接口名加入后缀_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时(插件的生命周期前文已有介绍)初始化的,

代码语言:javascript复制
auto ro_api = app().get_plugin<chain_plugin>().get_read_only_api();

app()函数以及与application类相关的内容前文已经介绍过,通过get_plugin<chain_plugin>获取chain_plugin的实例,然后调用其成员函数get_read_only_api(),

代码语言:javascript复制
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中包含了所有基于只读机制的接口实现,与上面接口列表中声明的保持一致。

代码语言:javascript复制
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

代码语言:javascript复制
#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。

代码语言:javascript复制
/**
 *  @attention 目前调用CALL_ASYNC函数的只有read_write的应用。
 *  @param api_name "chain'
 *  @param api_handle app().get_plugin<chain_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<api_namespace::call_name ## _params>(),
         [cb, body](const fc::static_variant<fc::exception_ptr, call_result>& result){
            /*捕获异常,分发异常处理*/ 
            if (result.contains<fc::exception_ptr>()) {
               try {
                  result.get<fc::exception_ptr>()->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<fc::exception_ptr, call_result>& async_result_visitor()函数:

代码语言:javascript复制
struct async_result_visitor : public fc::visitor<std::string> {
   template<typename T>
   std::string operator()(const T& v) const {
      return fc::json::to_string(v); //与CALL处理返回结果相同的是,此处做的也是转换json的工作。
   }
};

接着,进入fc库的static_variant.hpp文件中寻找类static_variant,它包含一个模板函数visit:

代码语言:javascript复制
template<typename visitor>
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时(插件的生命周期前文已有介绍)初始化的,

代码语言:javascript复制
auto rw_api = app().get_plugin<chain_plugin>().get_read_write_api();

app()函数以及与application类相关的内容前文已经介绍过,通过get_plugin<chain_plugin>获取chain_plugin的实例,然后调用其成员函数get_read_write_api(),

代码语言:javascript复制
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中包含了所有基于读写机制的接口实现,与上面接口列表中声明的保持一致。

代码语言:javascript复制
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 ),反射功能的具体实现就不深入探究了。下面来看其应用,举个例子:

代码语言:javascript复制
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的两个结构体,

代码语言:javascript复制
struct get_required_keys_params {
 fc::variant transaction;
 flat_set<public_key_type> available_keys;
};struct get_required_keys_result {
 flat_set<public_key_type> 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

代码语言:javascript复制
// 返回值为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<uint32_t>(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

代码语言:javascript复制
// 特殊的是,此处并没有创建一个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<block_id_type>());
      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));
   // 通过校验,开始返回对象。
   fc::variant pretty_output;
   // i将结果block的数据通过resolver解析到pretty_output
   abi_serializer::to_variant(*block, pretty_output, make_resolver(this, abi_serializer_max_time), abi_serializer_max_time);
   // 引用区块的前缀设置
   uint32_t ref_block_prefix = block->id()._hash[1];

   return fc::mutable_variant_object(pretty_output.get_object())
           ("id", block->id())
           ("block_num",block->block_num())
           ("ref_block_prefix", ref_block_prefix);
}

进一步研究区块的id是如何生成的,以及如何通过id获得区块号。是在block_heade.cpp中定义:

代码语言:javascript复制
namespace eosio { namespace chain {
   digest_type block_header::digest()const
   {
      return digest_type::hash(*this);// hash算法为sha256,然后使用fc::raw::pack打包获取结果
   }

   uint32_t block_header::num_from_id(const block_id_type& id)
   {
      return fc::endian_reverse_u32(id._hash[0]);// 实际上是对区块id并入区块号的算法的逆向工程,获得区块号。
   }

   // id的类型为block_id_type
   block_id_type block_header::id()const
   {
      // id不包括签名区块头属性,尤其是生产者签名除外。
      block_id_type result = digest();//digest_type::hash(*this),this是id()的调用者。
      result._hash[0] &= 0xffffffff00000000;//对结果进行位操作,并入一个十六进制头。
      result._hash[0]  = fc::endian_reverse_u32(block_num()); // 通过上一个区块id找到其区块号然后自增获得当前区块号。并入id数据成为其一部分。
      return result;
   }
} }

get_block拼接好id、block_num、ref_block_prefix最后三个字段以后,返回的数据结构如下图所示:

3. 获取区块头状态 get_block_header_state

注意与上面的get_block的实现区分,get_block_header_state是通过fetch_block_state_by_numberfetch_block_state_by_id函数获取到的是状态库中的区块对象,也就是说是可逆区块数据,而不是get_block通过fetch_block_by_numberfetch_block_by_id函数获取到的不可逆区块。 get_block_header_state获取到可逆区块以后,通过以下代码得到其区块头数据并返回。

fc::variant vo; fc::to_variant( static_cast<const block_header_state&>(*b), vo );// block_state_ptr b;return vo;

4. 获取账户信息 get_account

这个功能的实现函数代码较长,但做的工作实际上并不复杂,可以采用从返回的account数据结构来逆向分析该功能的实现方法:

代码语言:javascript复制
struct get_account_results {
  name                       account_name; // 账户名,入参的值。
  uint32_t                   head_block_num = 0; // 头块号,controller的状态主库db获取
  fc::time_point             head_block_time; // 头块时间,controller的状态主库db获取

  bool                       privileged = false; // 是否超级账户,默认false。controller的状态主库db获取账户的属性之一。
  fc::time_point             last_code_update; // 最后的code修改时间,例如给账户set contract的时间。controller的状态主库db获取账户的属性之一。
  fc::time_point             created; // 账户创建时间。controller的状态主库db获取账户的属性之一。

  optional<asset>            core_liquid_balance; // 主币的余额,在accounts状态表里查到的

  int64_t                    ram_quota  = 0; // 内存限额(资源相关部分有详细介绍),从controller的资源管理器获取
  int64_t                    net_weight = 0; // 网络带宽资源权重,从controller的资源管理器获取
  int64_t                    cpu_weight = 0; // cpu资源权重,从controller的资源管理器获取

  account_resource_limit     net_limit; // 网络带宽资源,包括已使用、剩余可用、总量。从controller的资源管理器获取
  account_resource_limit     cpu_limit; // cpu带宽资源,包括已使用、剩余可用、总量。从controller的资源管理器获取
  int64_t                    ram_usage = 0; // 内存已使用量。从controller的资源管理器获取

  vector<permission>         permissions; // 账户的权限内容(账户多签名部分有详细介绍),在状态主库的表里查到的。

  fc::variant                total_resources; // 总资源量,包括网络、cpu、内存资源总量。在userres状态表里查到的
  fc::variant                self_delegated_bandwidth; // 自我抵押带宽。在delband状态表里查到的
  fc::variant                refund_request; // 退款请求。在refunds状态表里查到的
  fc::variant                voter_info; // 投票相关。在voters状态表里查到的
};

5. 获取账户code信息 get_code

注意该接口修改了源码,不支持返回wast数据了。因此在请求该接口的时候,要使用的参数如下:

代码语言:javascript复制
{
    "account_name": "eosio.token",
    "code_as_wasm": true
}

返回的数据将包括

  • 该账户的名字。
  • code的hash值,先通过controller状态库查询到账户对象,然后将其code的data和size值做sha256哈希得到的值。
  • wasm的数据,就是完整的原始code的数据。
  • abi数据,通过abi_serializer将账户的abi数据解析出来。
代码语言:javascript复制
read_only::get_code_results read_only::get_code( const get_code_params& params )const {
   get_code_results result;
   result.account_name = params.account_name;
   const auto& d = db.db();
   const auto& accnt  = d.get<account_object,by_name>( params.account_name );// 从controller状态库中获取账户信息
   // 当前默认不支持返回wast数据
   EOS_ASSERT( params.code_as_wasm, unsupported_feature, "Returning WAST from get_code is no longer supported" );

   if( accnt.code.size() ) {
      if (params.code_as_wasm) {
          // 完整的原始账户的code的数据
         result.wasm = string(accnt.code.begin(), accnt.code.end());
      }
      // 获得code的哈希值:将账户信息下的code的data和size值做sha256哈希得到的值
      result.code_hash = fc::sha256::hash( accnt.code.data(), accnt.code.size() );
   }
   // 获取账户的abi数据:通过abi_serializer将账户的abi数据解析出来。
   abi_def abi;
   if( abi_serializer::to_abi(accnt.abi, abi) ) {
      result.abi = std::move(abi);
   }

   return result;
}

6. 获得账户的code哈希值 get_code_hash

实现方法参照get_code,返回数据只包括code的hash值。

7. 获得账户的abi数据 get_abi

实现方法参照get_code,返回数据只包括账户的abi数据。

8. 获得账户的原始code和abi数据 get_raw_code_and_abi

代码语言:javascript复制
read_only::get_raw_code_and_abi_results read_only::get_raw_code_and_abi( const get_raw_code_and_abi_params& params)const {
   get_raw_code_and_abi_results result;
   result.account_name = params.account_name;

   const auto& d = db.db();
   const auto& accnt = d.get<account_object,by_name>(params.account_name);
   result.wasm = blob{{accnt.code.begin(), accnt.code.end()}}; // 原始wasm值,完整取出即可。
   result.abi = blob{{accnt.abi.begin(), accnt.abi.end()}}; // 原始abi值,完整取出即可。

   return result;
}

9. 获得账户的原始abi数据 get_raw_abi

实现方法参照get_raw_code_and_abi,返回数据只包括账户的原始abi数据。

10. 获得一条状态库表的值 get_table_rows

首先查看该接口的传入参数的数据结构:

代码语言:javascript复制
struct get_table_rows_params {
  bool        json = false; // 是否是json的格式
  name        code; // 传入code值,即拥有该table的账户名
  string      scope; // 传入scope值,即查询条件
  name        table; // 传入table的名字
  string      table_key; // table主键
  string      lower_bound; // 设置检索数据的下限,默认是first
  string      upper_bound; // 设置检索数据的上限,默认是last
  uint32_t    limit = 10; // 数据结果的最大条目限制
  string      key_type;  // 通过指定键的数据类型,定位查询依赖的键
  string      index_position; // 通过传入键的位置,,定位查询依赖的键。1 - 主键(first), 2 - 二级索引 (multi_index定义), 3 - 三级索引,等等
  string      encode_type{"dec"}; //加密类型,有十进制还是十六进制,默认是十进制dec。
};

0 人点赞