EOSIO源码分析 - 交易权限鉴定过程

前言

从前面的分析中,我们知道,从API投递给EOSIO的交易,在执行时是要进行权限鉴定的,如果权限鉴定通过了才能继续执行交易,否则抛出异常。

在EOSIO中,权限的鉴定一般使用在三个地方

  • 新投递交易的权限鉴定
  • 内链交易的权限鉴定
  • get_required_keys函数调用

权限鉴定接口调用

新投递交易的权限鉴定

调用入口如下

// 如果非隐式交易,并且要求鉴定,才进入权限鉴定流程
const bool check_auth = !self.skip_auth_check() && !trx->implicit;
if( check_auth ) {
    
    
   authorization.check_authorization(
           trn.actions,
           trx->recovered_keys(),
           {
    
    },
           trx_context.delay,
           [&trx_context](){
    
     trx_context.checktime(); },
           false
   );
}
// 继续进入函数
/***** 对应参数 ******************************************************
参数1:需要鉴定的action
参数2:需要提供的公钥权限
参数3:需要提供的账户权限
参数4:延迟权限
参数5:超时判断
参数6:表示是否允许未使用的key,这个估计和以前的get_requred_key有关
参数7:继承的权限,这个在内链操作时需要传递
*******************************************************************/
void
   authorization_manager::check_authorization( const vector<action>&                actions,
                                               const flat_set<public_key_type>&     provided_keys,
                                               const flat_set<permission_level>&    provided_permissions,
                                               fc::microseconds                     provided_delay,
                                               const std::function<void()>&         _checktime,
                                               bool                                 allow_unused_keys,
                                               const flat_set<permission_level>&    satisfied_authorizations
                                             )const
{
    
    
	  // 构建权限鉴定时需要的参数
	  // 1, 超时检查,EOSIO是按照资源收费的,所以做任何一项操作都要检查资源,如这里检查CPU
	  // 2, 权限递归深度,前面我们分析过权限的数据结构,EOSIO的权限是一个可以递归嵌套的数据结构
	  // 3, 读取权限的回调函数,get_permission
      const auto& checktime = ( static_cast<bool>(_checktime) ? _checktime : _noop_checktime );
      auto delay_max_limit = fc::seconds( _control.get_global_properties().configuration.max_transaction_delay );
      auto effective_provided_delay =  (provided_delay >= delay_max_limit) ? fc::microseconds::maximum() : provided_delay;
      auto checker = make_auth_checker( [&](const permission_level& p){
    
     return get_permission(p).auth; },
                                        _control.get_global_properties().configuration.max_authority_depth,
                                    provided_keys,
                                    provided_permissions,
                                    effective_provided_delay,
                                    checktime
                                  );

  map<permission_level, fc::microseconds> permissions_to_satisfy;

  // 注意: 对于交易的权限,EOSIO是逐个检查每个action的权限
  // 并且对于如果是eosio账户操作的权限,是要单独鉴定的,按照不同的action,对应不同的权限判断
  for( const auto& act : actions ) {
    
    
     bool special_case = false;
     fc::microseconds delay = effective_provided_delay;

     if( act.account == config::system_account_name ) {
    
    
        special_case = true;

        if( act.name == updateauth::get_name() ) {
    
    
           check_updateauth_authorization( act.data_as<updateauth>(), act.authorization );
        } else if( act.name == deleteauth::get_name() ) {
    
    
           check_deleteauth_authorization( act.data_as<deleteauth>(), act.authorization );
        } else if( act.name == linkauth::get_name() ) {
    
    
           check_linkauth_authorization( act.data_as<linkauth>(), act.authorization );
        } else if( act.name == unlinkauth::get_name() ) {
    
    
           check_unlinkauth_authorization( act.data_as<unlinkauth>(), act.authorization );
        } else if( act.name ==  canceldelay::get_name() ) {
    
    
           delay = std::max( delay, check_canceldelay_authorization(act.data_as<canceldelay>(), act.authorization) );
        } else {
    
    
           special_case = false;
        }
     }

	 // 对最小权限的判断
	 // 对标的结构是我们前面介绍过的权限可以与合约的action进行绑定,如果没有绑定的最小权限,默认使用账户的active权限
     for( const auto& declared_auth : act.authorization ) {
    
    
        checktime();
        if( !special_case ) {
    
    
           auto min_permission_name = lookup_minimum_permission(declared_auth.actor, act.account, act.name);
           if( min_permission_name ) {
    
     // since special cases were already handled, it should only be false if the permission is eosio.any
              const auto& min_permission = get_permission({
    
    declared_auth.actor, *min_permission_name});
              EOS_ASSERT( get_permission(declared_auth).satisfies( min_permission,
                                                                   _db.get_index<permission_index>().indices() ),
                          irrelevant_auth_exception,
                          "action declares irrelevant authority '${auth}'; minimum authority is ${min}",
                          ("auth", declared_auth)("min", permission_level{
    
    min_permission.owner, min_permission.name}) );
           }
        }

        if( satisfied_authorizations.find( declared_auth ) == satisfied_authorizations.end() ) {
    
    
           auto res = permissions_to_satisfy.emplace( declared_auth, delay );
           if( !res.second && res.first->second > delay) {
    
     // if the declared_auth was already in the map and with a higher delay
              res.first->second = delay;
           }
        }
     }
  }
  // 最后对于挑选出来的权限,调用checker.satisfied函数进行权限鉴定
  for( const auto& p : permissions_to_satisfy ) {
    
    
     checktime(); // TODO: this should eventually move into authority_checker instead
     EOS_ASSERT( checker.satisfied( p.first, p.second ), unsatisfied_authorization,
                 "transaction declares authority '${auth}', "
                 "but does not have signatures for it under a provided delay of ${provided_delay} ms, "
                 "provided permissions ${provided_permissions}, provided keys ${provided_keys}, "
                 "and a delay max limit of ${delay_max_limit_ms} ms",
                 ("auth", p.first)
                 ("provided_delay", provided_delay.count()/1000)
                 ("provided_permissions", provided_permissions)
                 ("provided_keys", provided_keys)
                 ("delay_max_limit_ms", delay_max_limit.count()/1000)
               );

  }
  if( !allow_unused_keys ) {
    
    
     EOS_ASSERT( checker.all_keys_used(), tx_irrelevant_sig,
                 "transaction bears irrelevant signatures from these keys: ${keys}",
                 ("keys", checker.unused_keys()) );
  }
}

内链交易的权限鉴定

// 内链交易的权限鉴定和投递交易的权限鉴定调用函数是一样的,只是在要求提供的权限上要求有config::eosio_code_name权限
if( !control.skip_auth_check() && !privileged ) {
    
    
   try {
    
    
      control.get_authorization_manager()
             .check_authorization( {
    
    a},
                                   {
    
    },
                                   {
    
    {
    
    receiver, config::eosio_code_name}},
                                   control.pending_block_time() - trx_context.published,
                                   std::bind(&transaction_context::checktime, &this->trx_context),
                                   false,
                                   inherited_authorizations
                                 );
   } catch( const fc::exception& e ) {
    
    
   	// 异常处理
   }
}

get_required_keys函数调用

flat_set<public_key_type> authorization_manager::get_required_keys( const transaction& trx,
                                                                    const flat_set<public_key_type>& candidate_keys,
                                                                    fc::microseconds provided_delay
                                                                  )const
{
    
    
   auto checker = make_auth_checker( [&](const permission_level& p){
    
     return get_permission(p).auth; },
                                     _control.get_global_properties().configuration.max_authority_depth,
                                     candidate_keys,
                                     {
    
    },
                                     provided_delay,
                                     _noop_checktime
                                   );

   for (const auto& act : trx.actions ) {
    
    
      for (const auto& declared_auth : act.authorization) {
    
    
         EOS_ASSERT( checker.satisfied(declared_auth), unsatisfied_authorization,
                     "transaction declares authority '${auth}', but does not have signatures for it.",
                     ("auth", declared_auth) );
      }
   }

   return checker.used_keys();
}

从权限鉴定的三处用处我们可以得出以下结论

  • 如果不做特殊过滤,对于投递过来的交易都是要鉴定的
  • 对于投递过来的建议,如果是合约是eosio系统合约,权限是要特别鉴定的
  • 对于内链交易,如果当前合约账户是超级账户,则可以不用鉴定,如eosio账户系统上默认是超级的,如果在eosio合约上调用其他合约是不需要进行鉴定的
  • 内链交易,在权限的要求上,增加了config::eosio_code_name权限,就是说你必须要给账户申请config::eosio_code_name权限,才能进行内链交易
  • 三处权限鉴定最终都是调用checker.satisfied函数来完成的

authority_checker::satisfied函数分析

EOSIO系统权限鉴定最终都是调用authority_checker::satisfied函数来完成的,整个类的实现都在下列文件中

 #include <eosio/chain/authority_checker.hpp>

核心代码如下

template<typename AuthorityType>
bool satisfied( const AuthorityType& authority, permission_cache_type& cached_permissions, uint16_t depth ) {
    
    
   // Save the current used keys; if we do not satisfy this authority, the newly used keys aren't actually used
   auto KeyReverter = fc::make_scoped_exit([this, keys = _used_keys] () mutable {
    
    
      _used_keys = keys;
   });

   // Sort key permissions and account permissions together into a single set of meta_permissions
   detail::meta_permission_map permissions;

   // 构建权限判断的访问器,这里只是定义了权限判断的回调函数
   weight_tally_visitor visitor(*this, cached_permissions, depth);
   auto emplace_permission = [&permissions, &visitor](int priority, const auto& mp) {
    
    
      permissions.emplace(
            std::make_tuple(mp.weight, priority),
            [&mp, &visitor]() {
    
    
               return visitor(mp);  // 调用weight_tally_visitor::operator()函数
            }
      );
   };
   
   // 将权限对应的三个子域变量分别于emplace_permission绑定,建立访问判断的调用关系
   // 请回忆权限的定义结构 { 阈值,keys, accounts, waits }
   permissions.reserve(authority.waits.size() + authority.keys.size() + authority.accounts.size());
   std::for_each(authority.accounts.begin(), authority.accounts.end(), std::bind(emplace_permission, 1, std::placeholders::_1));
   std::for_each(authority.keys.begin(), authority.keys.end(), std::bind(emplace_permission, 2, std::placeholders::_1));
   std::for_each(authority.waits.begin(), authority.waits.end(), std::bind(emplace_permission, 3, std::placeholders::_1));

   // Check all permissions, from highest weight to lowest, seeing if provided authorization factors satisfies them or not
   // 检查所有的权限,调用构建的访问函数,计算对应的权重,计算完成后和权限的阈值做比较,如果大于或者等于阈值则权限判断成功
   for( const auto& p: permissions )
      // If we've got enough weight, to satisfy the authority, return!
      if( p.second() >= authority.threshold ) {
    
    
         KeyReverter.cancel();
         return true;
      }
   return false;
}
// 最核心的获取权限与计算权重之和的函数
uint32_t operator()(const permission_level_weight& permission) {
    
    
    auto status = authority_checker::permission_status_in_cache( cached_permissions, permission.permission );
    // 如果权限在cache中直接计算判断,如果不在需要去数据库读取
    if( !status ) {
    
    
       // 判断递归深度,如果深度大于checker.recursion_depth_limit就不判断了,在config.hpp中定义
       if( recursion_depth < checker.recursion_depth_limit ) {
    
    
          bool r = false;
          typename permission_cache_type::iterator itr = cached_permissions.end();

          bool propagate_error = false;
          try {
    
    
             // 利用回调函数获取要求的权限
             auto&& auth = checker.permission_to_authority( permission.permission );
             propagate_error = true;
             auto res = cached_permissions.emplace( permission.permission, being_evaluated );
             itr = res.first;
             // 注意这里递归调用,它判断了keys,accounts权限子域中出现递归权限
             r = checker.satisfied( std::forward<decltype(auth)>(auth), cached_permissions, recursion_depth + 1 );
          } catch( const permission_query_exception& ) {
    
    
             if( propagate_error )
                throw;
             else
                return total_weight; // if the permission doesn't exist, continue without it
          }
		  // 计算权重值和
          if( r ) {
    
    
             total_weight += permission.weight;
             itr->second = permission_satisfied;
          } else {
    
    
             itr->second = permission_unsatisfied;
          }
       }
    } else if( *status == permission_satisfied ) {
    
    
       //计算权重值和
       total_weight += permission.weight;
    }
    return total_weight;
 }
};

总体来看,权限的判断比较简单,就是判断了账户对应权限中权重之和是否大于权限对应的阈值,如果大于或者等于则权限验证通过。
注意这里需要着重理解权重,阈值相关概念,同时也要记住权限相关的数据结构,这样我们在后续的权限操作和阅读代码会更加容易

eosio合约的权限判断

在前面的介绍中,我们看到对于eosio的合约,权限是要单独判断的,如下代码

		if( act.name == updateauth::get_name() ) {
    
    
		   check_updateauth_authorization( act.data_as<updateauth>(), act.authorization );
		} else if( act.name == deleteauth::get_name() ) {
    
    
		   check_deleteauth_authorization( act.data_as<deleteauth>(), act.authorization );
		} else if( act.name == linkauth::get_name() ) {
    
    
		   check_linkauth_authorization( act.data_as<linkauth>(), act.authorization );
		} else if( act.name == unlinkauth::get_name() ) {
    
    
		   check_unlinkauth_authorization( act.data_as<unlinkauth>(), act.authorization );
		} else if( act.name ==  canceldelay::get_name() ) {
    
    
		   delay = std::max( delay, check_canceldelay_authorization(act.data_as<canceldelay>(), act.authorization) );
		} else {
    
    
		   special_case = false;
		}

上面的代码显示出,如果是权限相关的操作,需要特别鉴定,我们以updateauth操作为例, 分析代码如下

void authorization_manager::check_updateauth_authorization( const updateauth& update,
                                                               const vector<permission_level>& auths
                                                             )const
{
    
    
  // 账户只能携带一个权限
  EOS_ASSERT( auths.size() == 1, irrelevant_auth_exception,
              "updateauth action should only have one declared authorization" );
  const auto& auth = auths[0];
  // 只能有eosio账户来操作
  EOS_ASSERT( auth.actor == update.account, irrelevant_auth_exception,
              "the owner of the affected permission needs to be the actor of the declared authorization"          );

  // 判断权限是否存在
  const auto* min_permission = find_permission({
    
    update.account, update.permission});
  if( !min_permission ) {
    
     // creating a new permission
     min_permission = &get_permission({
    
    update.account, update.parent});
  }

  // 判断权限父子关系,只有父权限才能操作
  EOS_ASSERT( get_permission(auth).satisfies( *min_permission,
                                              _db.get_index<permission_index>().indices() ),
              irrelevant_auth_exception,
              "updateauth action declares irrelevant authority '${auth}'; minimum authority is ${min}",
              ("auth", auth)("min", permission_level{
    
    update.account, min_permission->name}) );
}

其他的action权限,我们对应分析就可以逐个理解关于eosio合约的权限鉴定,总结出以下共同规则

  • 账户只能携带一个权限
  • 只能有eosio账户来操作
  • 只有父权限才有操作的权利

至于每个动作私有的判断,分别判断就可以

总结

以上就是对EOSIO权限鉴定的分析过程,总结起来牢记以下几点就能深刻理解EOSIO的权限管理

  • 熟悉并牢记权限相关的数据结构,特别要理解阈值,权重的概念
  • 明确EOSIO的权限是可以嵌套的,一个账户可以借助另外一个账户的权限
  • 公钥与账户是多对多的关系,所以EOSIO是支持多账户签名的
  • eosio合约中关于权限操作时交易是要做特别鉴权的
  • 深刻理解内链交易,特别要理解内链时,对相关账户分配code权限

猜你喜欢

转载自blog.csdn.net/whg1016/article/details/128733837