美文网首页
EOS智能合约开发系列(12): 多签合约代码分析(一)

EOS智能合约开发系列(12): 多签合约代码分析(一)

作者: 鹏飞_3870 | 来源:发表于2018-08-24 16:24 被阅读0次

    前面几篇文章,我们全面地介绍了EOS的账户和权限,以及多重签名及其用法。今天呢,我们开始分析合约代码,今天分析的正是eosio.msig合约。

    代码在eos源码目录的contracts/eosio.msig/文件夹下面。

    头文件

    我们先看下头文件eosio.msig.hpp。可以看到有五个方法的声明:

             void propose();
             void approve( account_name proposer, name proposal_name, permission_level level );
             void unapprove( account_name proposer, name proposal_name, permission_level level );
             void cancel( account_name proposer, name proposal_name, account_name canceler );
             void exec( account_name proposer, name proposal_name, account_name executer );
    

    分别对应于我们上篇文章我们降到的multisig的五个命令:

    cleos multisig propose
    cleos multisig approve
    cleos multisig unapprove
    cleos multisig cancel
    cleos multisig exec
    

    再来看下eosio.msig合约用到的table类型,先看这个proposals类型:

             struct proposal {
                name                       proposal_name;
                vector<char>               packed_transaction;
    
                auto primary_key()const { return proposal_name.value; }
             };
             typedef eosio::multi_index<N(proposal),proposal> proposals;
    

    这个记录(也就是table中的一行(row)记录)类型是:proposal,它包含两个字段:

    proposal_name: 提案的名字,这个是name类型,也就是说同eos的账户名字类似,长度只能是12位。
    packed_transaction: 这个就是提案要执行的交易内容。

    另外有点需要注意的是:

    primary_key(): 这个方法是必须的,这里返回的是proposal_name所对应的uint64_t整数。
    proposal类型没有加//@abi table标志,也就是说,这个table是无法被外部访问的,比如用cleos get table命令无法获取它的数据,只有本eosio.msig代码可以访问。


    另外一个table类型:

            struct approvals_info {
                name                       proposal_name;
                vector<permission_level>   requested_approvals;
                vector<permission_level>   provided_approvals;
    
                auto primary_key()const { return proposal_name.value; }
             };
             typedef eosio::multi_index<N(approvals),approvals_info> approvals;
    

    这里也有三个字段:

    proposal_name: 提案的名字,与struct proposal中一样
    requested_approvals: 需要签署的权限列表
    provided_approvals: 目前已经签署的权限列表

    有两点需要注意:

    N(approvals): 这里使用的表的名字是'approvals',不是approvals_info
    同样,这里也没有//@abi table注释,所以也无法从此合约外部访问 。

    eosio.msig.cpp文件

    abi声明

    看下eosio.msig.cpp最下方的action声明:

    EOSIO_ABI( eosio::multisig, (propose)(approve)(unapprove)(cancel)(exec) )
    

    你看到了,这说明eosio.msig提供了propose, appove, unapprove, cancel, exec这5个供外部调用的action接口。

    multisig::propose方法

    这个方法没有参数,之所以这样做,注释中有这样的解释:

    propose function manually parses input data (instead of taking parsed arguments from dispatcher)
    because parsing data in the dispatcher uses too much CPU in case if proposed transaction is big
    
    If we use dispatcher the function signature should be:
    
    void multisig::propose( account_name proposer,
                            name proposal_name,
                            vector<permission_level> requested,
                            transaction  trx)
    

    意思是说,这样在方法里面解析数据,而不是让dispatcher去解析数据,可以提高效率,降低cpu占用率。我们来对比下采用dispatcher去解析参数的代码,下面dispatcher的源码:

    template<typename T, typename Q, typename... Args>
       bool execute_action( T* obj, void (Q::*func)(Args...)  ) {
          size_t size = action_data_size();
    
          //using malloc/free here potentially is not exception-safe, although WASM doesn't support exceptions
          constexpr size_t max_stack_buffer_size = 512;
          void* buffer = nullptr;
          if( size > 0 ) {
             buffer = max_stack_buffer_size < size ? malloc(size) : alloca(size);
             read_action_data( buffer, size );
          }
    
          auto args = unpack<std::tuple<std::decay_t<Args>...>>( (char*)buffer, size );
    
          if ( max_stack_buffer_size < size ) {
             free(buffer);
          }
    
          auto f2 = [&]( auto... a ){  
             (obj->*func)( a... ); 
          };
    
          boost::mp11::tuple_apply( f2, args );
          return true;
       }
    

    在看本方法中解析的部分:

      constexpr size_t max_stack_buffer_size = 512;
       size_t size = action_data_size();
       char* buffer = (char*)( max_stack_buffer_size < size ? malloc(size) : alloca(size) );
       read_action_data( buffer, size );
    
       account_name proposer;
       name proposal_name;
       vector<permission_level> requested;
       transaction_header trx_header;
    
       datastream<const char*> ds( buffer, size );
       ds >> proposer >> proposal_name >> requested;
    

    这段代码是特意为解析propose action参数数据写的,因为知道都是些什么参数,自然比通用的参数解析要高效一些。


    继续往下看multisig::propose的代码(留意我加的中文注释):

       size_t trx_pos = ds.tellp();
       ds >> trx_header;
       
       //检查调用者是否有所指定的`proposer`的权限,大部分action都会做类似的检查
       require_auth( proposer );
       eosio_assert( trx_header.expiration >= eosio::time_point_sec(now()), "transaction expired" );
       //eosio_assert( trx_header.actions.size() > 0, "transaction must have at least one action" );
    
       //创建proptable,用于存储提案的相关信息
       proposals proptable( _self, proposer );
       eosio_assert( proptable.find( proposal_name ) == proptable.end(), "proposal with the same name exists" );
    
       bytes packed_requested = pack(requested);
       //检查请求的权限是否可以满足
       auto res = ::check_transaction_authorization( buffer+trx_pos, size-trx_pos,
                                                     (const char*)0, 0,
                                                     packed_requested.data(), packed_requested.size()
                                                   );
       eosio_assert( res > 0, "transaction authorization failed" );
    

    下面的代码也比较容易看懂:

        //存储提案的名字和提案中交易的信息
        proptable.emplace( proposer, [&]( auto& prop ) {
          prop.proposal_name       = proposal_name;
          prop.packed_transaction  = bytes( buffer+trx_pos, buffer+size );
       });
    
       //保存被请求的签署权限信息,也以提案的名字作为主键
       approvals apptable(  _self, proposer );
       apptable.emplace( proposer, [&]( auto& a ) {
          a.proposal_name       = proposal_name;
          a.requested_approvals = std::move(requested);
       });
    

    你可能会有疑惑,上篇文章中,不是说提案是由提案发起人和提案的名字一起唯一标志的吗,这里怎么只有提案名字?

    主要创建proptableapptable的这两条语句:

    proposals proptable( _self, proposer );
     approvals apptable(  _self, proposer );
    

    注意这两个table的构造函数的参数了吗?都是_self, proposer。是什么意思呢?这个构造函数的原型是这样的:

    public inline multi_index(uint64_t code, uint64_t scope)
    

    解释下这两个形参:

    code: 代表拥有此表的账号,上面代码中,传入的实参是_self也就是本合约账号。
    scope: 是范围标志,相当于在拥有者账号下,把表划分为不同的范围,这个范围标志可以是任意定的,这里以propser作为范围标志,这样,proposerA范围下的表与 proposerB范围下的表是两个不同的表,也就是说proposerA发起了一个提案,名为T,proposerB发起了一个提案,名字也是T,那么这两个T将会使用不同的表,不会产生冲突。

    现在你明白为什么提案发起人和提案名字一起唯一标志一个提案了吧。


    今天就是这样了,其他的四个action处理器,我们下次再分析。
    简介:不羁,一名程序员;专研EOS技术,玩转EOS智能合约开发。
    微信公众号:know_it_well
    知识星球地址:https://t.zsxq.com/QvbuzFM

    相关文章

      网友评论

          本文标题:EOS智能合约开发系列(12): 多签合约代码分析(一)

          本文链接:https://www.haomeiwen.com/subject/lqqriftx.html