eos源码赏析(十九):EOS智能合约之合约中数据表中RAM的使用

2021-11-23 10:36:19 浏览数 (1)

上周eos更新版本至1.2.4,其中修复了ram使用权限的相关缺陷。当时在开发者群内也引发了一些讨论。那么我们今天来看看最新的版本在ram使用权限上到底做了哪些改动呢。涉及到权限的问题自然都是很严重的问题,因为它关乎到用户的资金安全,恰如这次关于ram的使用。

本文主要包含有以下内容

  • 智能合约中ram的使用
  • eos中lambda表达式的使用

1、智能合约中ram的使用

我们在以前的文章中多次提到,通过多索引的模式将数据写入到数据表,其中有包括有增、删、改、查,这次我们以数据删除为例:

代码语言:javascript复制
void apply_context::db_remove_i64( int iterator ) {
   const key_value_object& obj = keyval_cache.get( iterator );

   const auto& table_obj = keyval_cache.get_table( obj.t_id );
   EOS_ASSERT( table_obj.code == receiver, table_access_violation, "db access violation" );
   update_db_usage( obj.payer,  -(obj.value.size()   config::billable_size_v<key_value_object>) );

   db.modify( table_obj, [&]( auto& t ) {
      --t.count;
   });
   db.remove( obj );

   if (table_obj.count == 0) {
      remove_table(table_obj);
   }
   keyval_cache.remove( iterator );
}

不管是增删还是改,都会调用 update_db_usage这个函数,亦即更新ram的使用量。传入的参数中有两个,即谁来支付(payer)和支付多少(delta)。在本次版本更新之前,合约的开发者是可以指定本次action将数据写入到table中是由合约账户本身还是action的发起者即用户来支付ram。我们来看更新之前的代码:

代码语言:javascript复制
void apply_context::update_db_usage( const account_name& payer, int64_t delta ) {
   if( delta > 0 ) {
      if( !(privileged || payer == account_name(receiver)) ) {
         require_authorization( payer );
      }
   }

   trx_context.add_ram_usage(payer, delta);
}

这里的receiver是指什么呢

account_name receiver; ///< the code that is currently running,当前正在运行的合约账户

在这里如果支付ram的人payer和ram的接受者也就是智能合约账户本身不是同一个人的话,需要获取用户的权限,而在本次的修改中,修改的标题为:

subjectively fail transaction if unprivileged code bills RAM to an account other than itself within a notify context。

也就是在没有通知用户(action的发起者)之前,智能合约账户是无法通过获取用户的权限从而让用户为此次交易支付ram。在笔者看来,这使eos开发者的开发成本增加了,却是不得不做的事情,我们仍旧以代码来说明问题:

代码语言:javascript复制
void apply_context::update_db_usage( const account_name& payer, int64_t delta ) {
   if( delta > 0 ) {
      if( !(privileged || payer == account_name(receiver)) ) {
         EOS_ASSERT( control.is_ram_billing_in_notify_allowed() || (receiver == act.account),
                     subjective_block_production_exception, "Cannot charge RAM to other accounts during notify." );
         require_authorization( payer );
      }
   }
   trx_context.add_ram_usage(payer, delta);
}

也就是现在在通知用户要用户支付ram之前,只能由智能合约账户本身来支付ram。为什么要这样做呢,和以前狼人游戏的权限问题一样,试想如果一个合约开发者获取到用户账户中有多少ram,而后恶意更新合约代码,大量使用用户的ram来创建表或者往表中添加内容,这将是个可怕的现象。为了防止恶意合约消费用户的ram,此次更新是有必要的。

2、eos中lambda表达式的使用

周末的时候,群内前辈中山狼写了一篇关于C 基础知识关于函数相关的内容,提到一些函数的基本知识以及lambda表达式的相关内容,具体的可以参见这篇文章函数和lambda表达式,

由于使用方法较多,在此摘抄一段:

Lambda表达式其实专门讲一章都讲不完,这里只是顺带分析一下知道这个的用法,现在Lambda表达式在c 11完成的代码上基本上是满天飞了,不懂这个,估计看代码会崩溃。Lambda表达式可以叫做闭包,也可以叫做匿名函数。这个应用在几乎所有的开发语言中都在大热,就连JAVA都不得不最终妥协,支持了Lambda表达式。网上有一个图形:

说明:

  1. []代表Lambda表达式开始,在[]中可以填入=、&或者参数等表示该lambda表达式“捕获”(lambda表达式变量处理的域,或者闭包处理的范围)的数据的类型,&表示一引用的方式捕获;=表明值传递的方式捕获。如果又想有值传递的方式又想有引用的方式就自己直接在[]中声明就可以了。
  2. Lambda表达式的参数列表,和函数的参数列表一样。
  3. mutable可改变标识
  4. 异常处理
  5. 返回值
  6. Lambda表达式的主体即真正处理部分(叫函数体有点那个)。

以eos中的使用为例,仍旧是数据库的增删改查,这次我们以数据更新为例:

代码语言:javascript复制
void apply_context::db_update_i64( int iterator, account_name payer, const char* buffer, size_t buffer_size ) {
   const key_value_object& obj = keyval_cache.get( iterator );

   const auto& table_obj = keyval_cache.get_table( obj.t_id );
   EOS_ASSERT( table_obj.code == receiver, table_access_violation, "db access violation" );
   const int64_t overhead = config::billable_size_v<key_value_object>;
   int64_t old_size = (int64_t)(obj.value.size()   overhead);
   int64_t new_size = (int64_t)(buffer_size   overhead);

   if( payer == account_name() ) payer = obj.payer;

   if( account_name(obj.payer) != payer ) {
      update_db_usage( obj.payer,  -(old_size) );
      update_db_usage( payer,  (new_size));
   } else if(old_size != new_size) {
      update_db_usage( obj.payer, new_size - old_size);
   }

   db.modify( obj, [&]( auto& o ) {
     o.value.resize( buffer_size );
     memcpy( o.value.data(), buffer, buffer_size );
     o.payer = payer;
   });
}

在数据修改的过程使用lambda表达式,我们对应的看[&]表示引用方式的捕获,对应参数列表,在大括号里面实现了函数的功能,相当于向db.modify传入一个函数,通过这个函数来修改数据表中的内存的占用大小,并确定由谁来支付这个内存的消耗。

关于lambda表达式,笔者使用的并不多,但是中山狼前辈有句话说的很对,要敢于尝试用新东西,用错了再改,慢慢的就会了。

本文简单的介绍了在智能合约开发过程中由谁来支付RAM的问题,以及在1.2.3版本更新之前和更新之后的对比。对于智能合约开发者来说本次的更新可能会适当的增加了开发的成本,但是这样可以避免恶意合约消费用户的内存,笔者相信随着生态的逐步完善,只要是有价值的DAPP,不会因为RAM的占用而无法生存下去,也让我们一起期待这个行业的发展和壮大。

0 人点赞