前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >eos源码赏析(十四):EOS智能合约数据持久化存储(下)

eos源码赏析(十四):EOS智能合约数据持久化存储(下)

作者头像
用户2569546
发布2021-11-23 10:32:25
5970
发布2021-11-23 10:32:25
举报
文章被收录于专栏:eosfanseosfans

上篇文章发出来之后,群内前辈@郭其淼 针对性的提出两个问题:

1、 创建用户时可以用emplace的返回值作为已创建对象的编号。

2、 未对Multi-Index的一个关键性二级索引作出说明。

今天我们结合这两个问题,去看看Multi-Index中的相关函数的实现,以及Multi-Index和chainbase之间的交互。

本文主要包含有以下内容:

  • Multi-Index中增、删、改、查的实现
  • Multi-Index和chainbase之间的交互

Multi-Index中增、删、改、查的实现

Multi-Index的实现集中在multi-index.hpp里,可参考:https://github.com/EOSIO/eos/blob/master/contracts/eosiolib/multi_index.hpp

以问题1为例,primary-key可以用emplace的返回值替代,那么emplace返回的到底是什么呢?emplace的代码实现如下:

代码语言:javascript
复制
      template<typename Lambda>
      const_iterator emplace( uint64_t payer, Lambda&& constructor ) {
         using namespace _multi_index_detail;
         ......
         //1、做判断,不能在别的合约数据表内创建对象
         //2、创建对象并根据对象大小使用malloc分配内存
         //3、数据使用datastream写入对象
         ......
            auto pk = obj.primary_key();

            i.__primary_itr = db_store_i64( _scope, TableName, payer, pk, buffer, size );

            if ( max_stack_buffer_size < size ) {
               free(buffer);
            }

            if( pk >= _next_primary_key )
               _next_primary_key = (pk >= no_available_primary_key) ? no_available_primary_key : (pk + 1);
         //4、二级索引的处理

         const item* ptr = itm.get();
         auto pk   = itm->primary_key();
         auto pitr = itm->__primary_itr;

         _items_vector.emplace_back( std::move(itm), pk, pitr );

         return {this, ptr};
      }

由于代码内容较多,其中一部分转化为我个人的总结,详细内容可参考代码中的注释内容。在获取到当前对象的primary_key之后调用db_store_i64函数将数据存储到chainbase中,关于db_store_i64我们在本文的下半部分会具体提到。再来看emplace函数分别传入了什么参数和返回了什么,emplace函数的相关注释如下:

代码语言:javascript
复制
*  Adds a new object (i.e., row) to the table.(在数据表中创建一个新的对象)
*  @brief Adds a new object (i.e., row) to the table.
*  @param payer - Account name of the payer for the Storage usage of the new object
//谁创建,谁支付内存消耗的费用
*  @param constructor - Lambda function that does an in-place initialization of the object to be created in the table
//Lambda函数,可以直接初始化刚才创建对的对象
*  @pre A multi index table has been instantiated //前置条件:multi-index已经被初始化
*  @post A new object is created in the Multi-Index table, with a unique primary key (as specified in the object).  The object is serialized and written to the table. If the table does not exist, it is created.
//使用emplace之后:
//带有唯一主键的新对象在multi-index表中被创建; 
//这个对象会被序列化,然后写入表中; 
//如果表不存在,则创建表。 
*  @post Secondary indices are updated to refer to the newly added object. If the secondary index tables do not exist, they are created.
//二级索引被更新,用以引用新添加的对象; 
//如果二级索引表不存在,则创建它们。 
*  @post The payer is charged for the storage usage of the new object and, if the table (and secondary index tables) must be created, for the overhead of the table creation.
//payer为创建新对象所使用的存储付费; 
//如果multi-index表和二级索引表需要被创建,则payer为表的创建付费。
 *  @return A primary key iterator to the newly created object
//返回一个新创建的对象的主键迭代器
*  Exception - The account is not authorized to write to the table.
//异常:没有权限去写入这张数据表

通过官方注释我们可以知道,emplace返回了一个新创建对象的主键迭代器,而从代码中可以看出,ptr调用了get函数,而get又使用了我们上篇文章中所用到的find,当然上篇文章中我们也使用了get根据主键的id来获取英雄的相关信息。我们继续来看get和find:

代码语言:javascript
复制
//get
const T& get( uint64_t primary, const char* error_msg = "unable to find key" )const {
   auto result = find( primary );
   eosio_assert( result != cend(), error_msg );
   return *result;
   }
//find
const_iterator find( uint64_t primary )const {
   auto itr2 = std::find_if(_items_vector.rbegin(), _items_vector.rend(), [&](const item_ptr& ptr) {
   return ptr._item->primary_key() == primary;
   });
   if( itr2 != _items_vector.rend() )
       return iterator_to(*(itr2->_item));

   auto itr = db_find_i64( _code, _scope, TableName, primary );
       if( itr < 0 ) return end();

       const item& i = load_object_by_primary_iterator( itr );
       return iterator_to(static_cast<const T&>(i));
   }

可以看出,这其实和我们上篇文章中所使用的get来获取英雄相关信息是殊途同归,也就是使用emplace的返回结果也是该主键的一个迭代器。同时我们可以发现在find中使用了db_find_i64,他也包含在我们接下来要说的二级索引以及和chainbase的交互中,现在我们来将上篇文章中的代码稍作修改,然后再执行create action看看emplace的返回结果:

代码语言:javascript
复制
void tianlongbabu::create(account_name heroname,uint64_t heroforceidx,uint64_t heroinsideidx,string herodes,string heroborn)
{
    print(name{heroname},"出生在:",heroborn,",现在是",herodes,",他的武力值:",heroforceidx,",他的内力值:",heroinsideidx);
    uint64_t heroindex = 0;
    auto dbhero = ht.emplace(_this_contract,[&](auto &p)
    {
        p.heroid = ht.available_primary_key();   //主键自增
        heroindex = p.heroid;
        p.herodes        = herodes;
        p.heroborn       = heroborn;
        p.heroforceidx   = heroforceidx;
        p.heroinsideidx  = heroinsideidx;

    });
    print("--emplace返回该英雄的编号:",dbhero->heroid);
}
//push action
ubuntu@VM-16-6-ubuntu:~/eos/contracts/tlbb$ cleos push action tlbb.code create '["xiaofeng","1000","500","丐帮帮主","辽国"]' -p xiaofeng
executed transaction: a92821dacc552d8cb9a9b60330a878b4d95e32a11a148fe7559a7189d6253e89  136 bytes  1274 us
#     tlbb.code <= tlbb.code::create            {"heroname":"xiaofeng","heroforceidx":1000,"heroinsideidx":500,"herodes":"丐帮帮主","heroborn":"...
>> xiaofeng出生在:辽国,现在是丐帮帮主,他的武力值:1000,他的内力值:500--emplace返回该英雄的编号:0

看完了第一个问题,我们再来看看关于二级索引的使用,在Multi-Index.hpp中对二级索引做了部分介绍可以参考下官方的例子来实现下,这里实现一个action并简单的测试下,根据英雄的武力值来查找该英雄的信息:

代码语言:javascript
复制
//关于hero_table的multi-index的声明修改如下:
typedef eosio::multi_index<N(heros),heros,indexed_by<N(heroforceidx),const_mem_fun<heros,uint64_t,&heros::get_heroforceidx>>> heros_table;
heros_table ht;
//根据武力值二级索引来查找天龙英雄的相关信息
void tianlongbabu::selectbyfidx(uint64_t heroforceidx)
{
    auto force_index = ht.get_index<N(heroforceidx)>();
    auto findhero = force_index.lower_bound(heroforceidx);
    eosio_assert(findhero != force_index.end(),"您查找的英雄不存在");
    print("武力值为",heroforceidx,"的英雄其编号为",findhero->heroid);
}
//push action
ubuntu@VM-16-6-ubuntu:~/eos/contracts/tlbb$ cleos push action tlbb.code selectbyfidx '["1000"]' -p tlbb.code
executed transaction: b2ccddeacabe7f9b5abe7f92725dd91c0f028ed1da414f276597d821def3c9dd  104 bytes  1013 us
#     tlbb.code <= tlbb.code::selectbyfidx      {"heroforceidx":1000}
>> 武力值为1000的英雄其编号为0

Multi-Index和chainbase之间的交互

在上面的内容中我们多次提到了db_store_i64以及db_find_i64,通过这两个函数来实现数据的存储及查找,那么这两个函数又分别是什么呢?我们以db_store_i64为例来看Multi-Index和chianbase之间是如何关联并实现数据的增、删、改、查操作的,代码如下:

代码语言:javascript
复制
int apply_context::db_store_i64( uint64_t code, uint64_t scope, uint64_t table, const account_name& payer, uint64_t id, const char* buffer, size_t buffer_size ) {
//   require_write_lock( scope );
   const auto& tab = find_or_create_table( code, scope, table, payer );
   auto tableid = tab.id;

   FC_ASSERT( payer != account_name(), "must specify a valid account to pay for new record" );

   const auto& obj = db.create<key_value_object>( [&]( auto& o ) {
      o.t_id        = tableid;
      o.primary_key = id;
      o.value.resize( buffer_size );
      o.payer       = payer;
      memcpy( o.value.data(), buffer, buffer_size );
   });

   db.modify( tab, [&]( auto& t ) {
     ++t.count;
   });
         //而后我们再来看db.create,其最终还是会走向chainbase中的emplace
         /**
          * Construct a new element in the multi_index_container.//使用boost::multi-index容器创建一个新的元素
          * Set the ID to the next available ID, then increment _next_id and fire off on_create().//将id设置为下一个可用id
          */
         template<typename Constructor>
         const value_type& emplace( Constructor&& c ) {
            auto new_id = _next_id;

            auto constructor = [&]( value_type& v ) {
               v.id = new_id;
               c( v );
            };

            auto insert_result = _indices.emplace( constructor, _indices.get_allocator() );

            if( !insert_result.second ) {
               BOOST_THROW_EXCEPTION( std::logic_error("could not insert object, most likely a uniqueness constraint was violated") );
            }

            ++_next_id;
            on_create( *insert_result.first );
            return *insert_result.first;
         }

可以看出,db_store_i64的最终归宿还是通过chainbase走向了boost::multi-index,在chainbase.hpp中还有若干操作来实现对数据的增、删、改、查,代码阅读起来相对较难,因笔者能力和笔力有限,不再对和boost::multi-index的相关内容做分析,感兴趣的朋友可以一起来讨论。

db_store_i64中我们发现了一个很有趣的函数update_db_usage,我们知道在eosio整个系统中ram扮演着举足轻重的角色,没有ram不管是开发者还是代币持有人都无法在主网上进行相应的操作。而ram的使用规则也很明了,即:谁使用,谁支付,那么这个使用的限定又是什么?什么样的数据写入需要使用ram?update_db_usage实现了什么功能?这其中的buffer_size和我们真正使用的内存之间有什么关系?我们在下篇文章中继续讨论。

本文主要针对上篇文章中存在的问题作出解释说明,并从源码的角度给出相应的答案。包含有emplace的返回值探讨,Multi-Index二级索引的查询使用,Multi-Index和chainbase之间是如何建立连接的,在本文的最后留下update_db_usage的相关疑问,待下一步学习探讨。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-08-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 eosfans 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档