BCSkill (Block chain skill ) 技术社区
社区QQ群:791420381
推荐论坛:https://eosfans.io
Telegram: https://t.me/bcskill

[EOS源码分析] EOS特殊智能合约eosio

这里说的eosio智能合约不是泛指eos的智能合约,它是一个特殊的具体的合约。它本事可大了,我们一起来看看它有哪些功能

负责智能合约部署

$ cleos set contract hello.code ../eos-contract/hello -p hello.code
Publishing contract...
executed transaction: daabe65267af4b9a11e5ff90a165bbaac68469630f499bcea1ef0eb7da6d970c  1792 bytes  2558 us
#         eosio <= eosio::setcode               {"account":"hello.code","vmtype":0,"vmversion":0,"code":"0061736d01000000013b0c60027f7e006000017e600...
#         eosio <= eosio::setabi                {"account":"hello.code","abi":"00010c6163636f756e745f6e616d65046e616d6501026869000104757365720c61636...

注意下 eosio <= eosio::setcodeeosio <= eosio::setabi
这段log很明显的说明了

$ cleos set contract eosio build/contracts/eosio.bios -p eosio

等价于调用eosio智能合约的setcode和setabi函数

$ cleos push action eosio setcode '[eosio.bios.wasm]' -p eosio
$ cleos push action eosio setabi eosio '[eosio.bios.abi] -p eosio

也就说合约部署是通过调用eosio合约来实现的
对应的源码:
set contract会产生setcode和setabi两个action

add_standard_transaction_options(contractSubcommand, "account@active");
   add_standard_transaction_options(codeSubcommand, "account@active");
   add_standard_transaction_options(abiSubcommand, "account@active");
   contractSubcommand->set_callback([&] {
      shouldSend = false;
      set_code_callback();
      set_abi_callback();
      std::cout << localized("Publishing contract...") << std::endl;
      send_actions(std::move(actions), 10000, packed_transaction::zlib);
   });

chain::action create_setcode(const name& account, const bytes& code) {
   return action {
      tx_permission.empty() ? vector<chain::permission_level>{{account,config::active_name}} : get_account_permissions(tx_permission),
      setcode{
         .account   = account,
         .vmtype    = 0,
         .vmversion = 0,
         .code      = code
      }
   };
}

struct setcode {
   account_name                     account;
   uint8_t                          vmtype = 0;
   uint8_t                          vmversion = 0;
   bytes                            code;

   static account_name get_account() {
      return config::system_account_name;
   }

   static action_name get_name() {
      return N(setcode);
   }
};

const static uint64_t system_account_name    = N(eosio);

set_code和set_abi都是通过调用system_account_name即eosio智能合约来执行的

负责账号创建

同样我们看看create account,其实就是调用eosio合约的newaccount函数

$ cleos create account eosio hello.code EOS7KBTMkUq4VPakqsZUnZfBbMbS2U7cn9qSa3q6G5ZzEeUeNSVgv EOS7KBTMkUq4VPakqsZUnZfBbMbS2U7cn9qSa3q6G5ZzEeUeNSVgv
executed transaction: 01aff4356a6277eec777494fc6aeaf97164c53997c46fe853247ed7e100f4987  200 bytes  911 us
#         eosio <= eosio::newaccount            {"creator":"eosio","name":"hello.code","owner":{"threshold":1,"keys":[{"key":"EOS7KBTMkUq4VPakqsZUnZ...

负责权限管理

这次是调用eosio的updateauth函数

$ cleos set account permission testaccount active '{"threshold" : 1, "keys" : [], "accounts" : [{"permission":{"actor":"bob","permission":"active"},"weight":1}, {"permission":{"actor":"stacy","permission":"active"},"weight":1}]}’ owner
executed transaction: b1bc9680a9ba615a6de8c3f7c692d7d28ff97edae245bb40f948692b14ea6c15  160 bytes  189 us
#         eosio <= eosio::updateauth            {"account":"testaccount","permission":"active","parent":"owner","auth":{"threshold":1,"keys":[],"acc...
warning: transaction executed locally, but may not be confirmed by the network yet

蛋生鸡,鸡生蛋问题

既然eosio是一个智能合约,而它又负责合约部署,那它自己是谁部署的呢?我们先来看下这个结构图

eosio contract负责系统服务,比如部署合约,创建账号。infra contracts层比如eosio.token和eosio.msig类似库作用的合约,比如多签名,发行代币,方便dapp层使用。Dapp才是用户直接接触的,每个开发人员编写程序然后部署,这些程序都是DApp。

eosio contract由3个部分构成
  • nativeaction
    nativeactions就是前面提到的setcode, setabi, newaccount功能的函数集。这部分代码是hardcode在EOS系统代码里的,也就说不需要部署这一步骤,所以就解决了蛋生鸡,鸡生蛋问题。
  • eosio.bios, eosio.system
    eosio.bios是一个智能合约的代码,是通过智能合约部署方式绑定到eosio contract上的。那你可能会说,eosio.bios部署后,nativeactions部分是不是就失效了啊。确实可以这样实现,由于setcode这些action需要永久生效,这就需要eosio.bios包含nativeactions这些函数,这样就出现了相同一份代码分散在两个模块,独立性和维护不够好。所以,目前的实现是通过特殊处理让nativeactions的函数有最高优先级,永不覆盖,哪怕eosio.bios实现了同样的函数(比如set_code, set_abi)。但是eosio.system和eosio.bios是一个级别的,都是contract, 是水火不相容的,一旦将eosio.system绑定到eosio这个账号,eosio.bios就失效了,所以eosio.bios的函数要么是临时用途的,要么就需要bios.system重新实现,比如setalimits会失效,而setpriv会在eosio.system重新实现。这个和cpu启动一样,一开始bios(bootloader)代码运行,然后引导system代码,当system加载后,bios(bootloader)代码失效。所以从这个设计和名字可以看出,EOS确实是在按照操作系统的逻辑设计。
    eosio.bios的接口
    EOSIO_ABI( eosio::bios, (setpriv)(setalimits)(setglimits)(setprods)(reqauth) )

    eosio.sytem的接口

    EOSIO_ABI( eosiosystem::system_contract,
    (setram)
    // delegate_bandwith.cpp
    (delegatebw)(undelegatebw)(refund)
    (buyram)(buyrambytes)(sellram)
    // voting.cpp
    // producer_pay.cpp
    (regproxy)(regproducer)(unregprod)(voteproducer)
    (claimrewards)
    // native.hpp
    (onblock)
    (newaccount)(updateauth)(deleteauth)(linkauth)(unlinkauth)(postrecovery)(passrecovery)(vetorecovery)(onerror)(canceldelay)
    //this file
    (setpriv)
    )

nativeaction解读

  • nativeaction注册
    nativeaction是通过SET_APP_HANDLER注册的

    #define SET_APP_HANDLER( receiver, contract, action) \
     set_apply_handler( #receiver, #contract, #action, &BOOST_PP_CAT(apply_, BOOST_PP_CAT(contract, BOOST_PP_CAT(_,action) ) ) )
    
     SET_APP_HANDLER( eosio, eosio, newaccount );
     SET_APP_HANDLER( eosio, eosio, setcode );
     SET_APP_HANDLER( eosio, eosio, setabi );
     SET_APP_HANDLER( eosio, eosio, updateauth );
     SET_APP_HANDLER( eosio, eosio, deleteauth );
     SET_APP_HANDLER( eosio, eosio, linkauth );
     SET_APP_HANDLER( eosio, eosio, unlinkauth );
    /*
     SET_APP_HANDLER( eosio, eosio, postrecovery );
     SET_APP_HANDLER( eosio, eosio, passrecovery );
     SET_APP_HANDLER( eosio, eosio, vetorecovery );
    */
    
     SET_APP_HANDLER( eosio, eosio, canceldelay );
     void set_apply_handler( account_name receiver, account_name contract, action_name action, apply_handler v ) {
        apply_handlers[receiver][make_pair(contract,action)] = v;
     }

    对应的函数名是apply_eosio_xxx,比如apply_eosio_setcode,apply_eosio_newaccount

  • nativeaction函数调用
    系统会先检测action的名字是否注册在native handler里,如果在则直接调用,不在的话,执行合约代码,并跳转到相应的action函数

      class apply_context { 
          { 
          public:
            apply_context(controller& con, transaction_context& trx_ctx, const action& a, uint32_t depth=0)
            :control(con)
            ,db(con.db())
            ,trx_context(trx_ctx)
            ,act(a)
            //合约的账号
            ,receiver(act.account)
            ,used_authorizations(act.authorization.size(), false)
      }
    
      action_trace apply_context::exec_one()
      {
         auto start = fc::time_point::now();
    
         const auto& cfg = control.get_global_properties().configuration;
         try {
            //获取智能合约对象
            const auto &a = control.get_account(receiver);
            privileged = a.privileged;
            //检测该action是否是native action,如果是则调用native handler
            auto native = control.find_apply_handler(receiver, act.account, act.name);
            if (native) {
               //hative handler(action)存在,则调用
               (*native)(*this);
            }
            //只要不是setcode调用,允许nativehandler和contract部署的代码都执行
            if( a.code.size() > 0
                && !(act.account == config::system_account_name && act.name == N(setcode) && receiver == config::system_account_name) ) {
               try {
                  control.get_wasm_interface().apply(a.code_version, a.code, *this);
               } catch ( const wasm_exit& ){}
            }
           ….
    
         } FC_CAPTURE_AND_RETHROW((_pending_console_output.str()));
      }
    转载自:http://blog.csdn.net/itleaks

[EOS源码分析] EOS编写HelloWorld智能合约

HelloWorld源码

#include <eosiolib/eosio.hpp>
#include <eosiolib/print.hpp>
using namespace eosio;

class hello : public eosio::contract {
  public:
      using contract::contract;

      /// @abi action 
      void hi( account_name user ) {
         print( "Hello, ", name{user} );
      }
};

EOSIO_ABI( hello, (hi) )

EOSIO_ABI是一个生成智能合约初始化函数apply的宏,生成的apply函数是智能合约的入口,它采用switch case的方式调用具体action对应的函数

#define EOSIO_ABI( TYPE, MEMBERS ) \
extern "C" { \
   void apply( uint64_t receiver, uint64_t code, uint64_t action ) { \
      auto self = receiver; \
      if( code == self ) { \
         TYPE thiscontract( self ); \
         switch( action ) { \
            EOSIO_API( TYPE, MEMBERS ) \
         } \
         /* does not allow destructor of thiscontract to run: eosio_exit(0); */ \
      } \
   } \
} \

编译

  • 生成wast文件
    $ eosiocpp -o hello.wast hello.cpp

    编译完成后,会生成两个新文件,hello.wast, hello.wasm
    .wast文件是wasm的代码文本格式,.wasm是汇编代码二级制格式

  • 生成abi文件
    eosiocpp -g hello.abi hello.cpp

部署智能合约

  • 创建新账号
    我们知道智能合约是附着在账号上的,因而需要为合约准备一个账号
    创建key并导入到钱包

    $ cleos create key
    Private key: 5JdchMrwMwD1PsZKCjpbaCQ4aJ3cFKzSWmCQfRzKCiGrDWds3PU
    Public key: EOS7KBTMkUq4VPakqsZUnZfBbMbS2U7cn9qSa3q6G5ZzEeUeNSVgv
    $ cleos wallet import 5JdchMrwMwD1PsZKCjpbaCQ4aJ3cFKzSWmCQfRzKCiGrDWds3PU

    以上面的key创建账号

    $ cleos create account eosio hello.code EOS7KBTMkUq4VPakqsZUnZfBbMbS2U7cn9qSa3q6G5ZzEeUeNSVgv EOS7KBTMkUq4VPakqsZUnZfBbMbS2U7cn9qSa3q6G5ZzEeUeNSVgv
    executed transaction: 9cdfd59700f8fb13b9a3b330d75f9e1d6a014d54f5bec21cee9afe35c8c49b99  200 bytes  5304 us
    #         eosio <= eosio::newaccount            {"creator":"eosio","name":"hello.code","owner":{"threshold":1,"keys":[{"key":"EOS7KBTMkUq4VPakqsZUnZ...
    warning: transaction executed locally, but may not be confirmed by the network yet
  • 将合约代码绑定到账号
    这里需要注意的是,xxx.wasm文件的目录必须是xxx,这里的hello.cpp, hello.wasm的目录就必须是hello,因为该命令会搜索xx/xx.wasm文件,这里会搜索hello/hello.wasm文件

    $ cleos set contract hello.code ./hello -p hello.code
    Reading WAST/WASM from ./hello/hello.wasm...
    Using already assembled WASM...
    Publishing contract...
    executed transaction: b26872e44b439ce289f7c449f959a7c9ad574045f69c53039ff365e79e1b8494  1800 bytes  5647 us
    #         eosio <= eosio::setcode               {"account":"hello.code","vmtype":0,"vmversion":0,"code":"0061736d01000000013b0c60027f7e006000017e600...
    #         eosio <= eosio::setabi                {"account":"hello.code","abi":"00010c6163636f756e745f6e616d650675696e74363401026869000104757365720c6...
    warning: transaction executed locally, but may not be confirmed by the network yet
  • 执行智能合约函数

    $ cleos push action hello.code hi '["args.user"]' -p args.user
    executed transaction: f694bfbc0c4d26751aa35a57bc1695fb24ff1a7606934dea64edd1e15f79664d  104 bytes  2444 us
    #    hello.code <= hello.code::hi               {"user":"args.user"}
    >> Hello, args.user

    智能合约权限检测实践

    前面的智能合约没有执行任何权限检测,因而在hi函数里新增一个权限检测

    class hello : public eosio::contract {
    public:
        using contract::contract;
    
        /// @abi action
        void hi( account_name user) {
           require_auth(user);
           print( "Hello, ", name{user} );
        }
    };

    红色字体的意思是执行"hi" action的权限必须是作为参数传进来的user
    如果使用hello.code账号执行则会报错

    $ cleos push action hello.code hi '["args.user"]' -p hello.code
    Error 3090004: missing required authority
    Ensure that you have the related authority inside your transaction!;
    If you are currently using 'cleos push action' command, try to add the relevant authority using -p option.
    Error Details:
    missing authority of args.user

    将参数换成hello.code执行成功

    $ cleos push action hello.code hi '["hello.code"]' -p hello.code
    executed transaction: 49db2db96c3719992af6e31d378385ac99ff611453ef81fd03cbcb822d223bd5  104 bytes  1752 us
    #    hello.code <= hello.code::hi               {"user":"hello.code"}
    >> Hello, hello.code
    warning: transaction executed locally, but may not be confirmed by the network yet

    源码一键实践

    https://github.com/itleaks/eos-contract下载源码,即可一键实践执行该智能合约

转载自:http://blog.csdn.net/itleaks

[EOS源码分析] EOS源码调试 (Mac OS)

编译带调试信息的EOS

编译可以调试(带源码信息)的EOS程序,增加-o Debug参数即可,即

$./eosio_build.sh -o Debug

下载VSCode

和比特币,以太坊源码调试一样,vscode是最佳的调试EOS源码的调试工具
下载地址https://code.visualstudio.com/Download

调试配置流程

导入源码

  • 配置调试文件
  • 执行后,会生成launch.json, 然后修改program字段值为对应的程序即可,比如要调试nodeos, 则修改为如下

    添加断点并开始调试

    在行号左边点击即可添加断点

    点击调试界面的右三角开始调试程序

    注意

    如果你曾经执行过release版本的nodeos,需要将数据清空,否则会报错
    database created by a different compiler, build, boost version, or operating system
    执行如下命令清空老数据
    $ rm -rf ~/Library/Application\ Support/eosio

    转载自:http://blog.csdn.net/itleaks

[EOS源码分析] EOS权限模型机制分析

cleos涉及account和contract的命令都会产生一个action,进而生成一个transaction,所有的action都需要指定permission权限
权限验证流程图如下

主要分为三个部分:

  • permission声明:1~3

  • permission授权证明:4~9

  • 权限检测:10~14,其中本地节点的nodeos和miner节点的nodeos都会执行权限检测,10~11(本地节点)和12~14(外部矿工节点)的工作内容是一样

    权限声明

    所有action相关命令都是通过通过-p/--permission声明permission参数,permission参数有几种表达形式:account, account@permission, publickey, account等价于account@active

    cleos create account -j eosio testaccount -p eosio@owner
    cleos set account permission testaccount active -p eosio@active
    cleos push action contractaccount method 'data' -p account@publish
    cleos push action contractaccount method1 'data' -p publickey

    如果用户没有输入该参数,cleos会自动添加默认permission,各种action的默认permission是不一样的

  • create account命令的默认permission是creator@active

  • set account permission命令的默认permission是account@active

  • push action contractaccount命令的默认permission是contractaccount@active

      chain::action create_newaccount(const name& creator, const name& newaccount, public_key_type owner, public_key_type active) {
         return action {
            //tx_permission就是-p参数的值,如果没有account@permission这个值,则默认为creater@active
            tx_permission.empty() ? vector<chain::permission_level>{{creator,config::active_name}} : get_account_permissions(tx_permission),
            eosio::chain::newaccount{
               .creator      = creator,
               .name         = newaccount,
               .owner        = eosio::chain::authority{1, {{owner, 1}}, {}},
               .active       = eosio::chain::authority{1, {{active, 1}}, {}}
            }
         };
      }
    
      chain::action create_updateauth(const name& account, const name& permission, const name& parent, const authority& auth) {
         return action { tx_permission.empty() ? vector<chain::permission_level>{{account,config::active_name}} : get_account_permissions(tx_permission),
                         updateauth{account, permission, parent, auth}};
      }

    如果-p account@permission只带了account,则默认为account@active

    vector<chain::permission_level> get_account_permissions(const vector<string>& permissions) {
     auto fixedPermissions = permissions | boost::adaptors::transformed([](const string& p) {
        vector<string> pieces;
        split(pieces, p, boost::algorithm::is_any_of("@"));
        //如果没有@permission这个声明,则默认为@active
        if( pieces.size() == 1 ) pieces.push_back( "active" );
        return chain::permission_level{ .actor = pieces[0], .permission = pieces[1] };
     });
     vector<chain::permission_level> accountPermissions;
     boost::range::copy(fixedPermissions, back_inserter(accountPermissions));
     return accountPermissions;
    }

    权限授权证明

    用户在执行cleos相关命令时通过-p声明了permission,这个permission只是一个字符串,谁都可以伪造的,因而需要提交真实可验证的证据,这个证据就是permission的授权(authority)信息。一个permission的authority可以是public key,也可以是子permission(另一个账号的permission, anotheraccount@permission), 这样就形成了一颗树, 叶子节点是public key, 只有该叶子节点的public key才有该权限。

    所以提交授权证明的过程由两部分构成

    收集权限permission的public key

    搜集permission生成的授权树的叶子节点的public key,即找出哪些public key被授予该权限
    比如上图的account@publish权限展开后得到如下叶子节点public key集合【key1, key10, key11, key20, key21, key60, key61】,只要用户拥有这些key集合中的一个public key对应的私钥就可以证明该用户可以以该account@publish权限提交action.

    cleos端:

      fc::variant push_transaction( signed_transaction& trx, int32_t extra_kcpu = 1000, packed_transaction::compression_type compression = packed_transaction::none ) {
         auto required_keys = determine_required_keys(trx);
      }
    
      fc::variant determine_required_keys(const signed_transaction& trx) {
         // TODO better error checking
         //wdump((trx));
         //拿到本地所有的public keys,这些key中可能拥有account@publish权限
         const auto& public_keys = call(wallet_url, wallet_public_keys);
         //trx包含action,action包含account@publish权限信息
         auto get_arg = fc::mutable_variant_object
                 ("transaction", (transaction)trx)
                 ("available_keys", public_keys);
         //调用keosd的服务获取本地满足account@publish权限的public key
         const auto& required_keys = call(get_required_keys, get_arg);
         return required_keys["required_keys"];
      }

    nodeos端:

    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) {
              //判断candidate_keys是否有合适的key被授予了act.authorization
              EOS_ASSERT( checker.satisfied(declared_auth), unsatisfied_authorization,
                          "transaction declares authority '${auth}', but does not have signatures for it.",
                          ("auth", declared_auth) );
           }
        }
        //返回满足条件的public key
        return checker.used_keys();
     }

    通过私钥签名提供授权证明

    搜索上一步收集到的public key,检测是否含有本用户的key,如果存在,则用相应的private key 签名交易,这样就可以证明该交易的具备account@publish这一permission

      fc::variant push_transaction( signed_transaction& trx, int32_t extra_kcpu = 1000, packed_transaction::compression_type compression = packed_transaction::none ) {
         //上一步获取到的被授权的public key
         auto required_keys = determine_required_keys(trx);
         if (!tx_skip_sign) {
            //通过签名交易提交permission证明
            sign_transaction(trx, required_keys);
         }
    
         if (!tx_dont_broadcast) {
            //广播交易
            return call(push_txn_func, packed_transaction(trx, compression));
         } else {
            return fc::variant(trx);
         }
      }
    
      void sign_transaction(signed_transaction& trx, fc::variant& required_keys) {
         // TODO determine chain id
         fc::variants sign_args = {fc::variant(trx), required_keys, fc::variant(chain_id_type{})};
         //cleos调用keosd的sign_trx api来执行签名操作
         const auto& signed_trx = call(wallet_url, wallet_sign_trx, sign_args);
         trx = signed_trx.as<signed_transaction>();
      }
    
      chain::signed_transaction
      wallet_manager::sign_transaction(const chain::signed_transaction& txn, const flat_set<public_key_type>& keys, const chain::chain_id_type& id) {
         check_timeout();
         chain::signed_transaction stxn(txn);
    
         for (const auto& pk : keys) {
            bool found = false;
            for (const auto& i : wallets) {
               if (!i.second->is_locked()) {
                  //根据public key拿到private key并签名,这个是没法伪造的
                  const auto& k = i.second->try_get_private_key(pk);
                  if (k) {
                     stxn.sign(*k, id);
                     found = true;
                     break; // inner for
                  }
               }
            }
         }
    
         return stxn;
      }

    节点验证权限授权证明

    用权限permission授权的私钥签名(授权证明)的交易发布到网络后,矿工收到该交易后,还需要解释签名并验证权限。验证分为两部分

  • 声明的权限是否满足action的最低权限要求

    transaction_trace_ptr push_transaction( const transaction_metadata_ptr& trx,
                                             fc::time_point deadline,
                                             bool implicit,
                                             uint32_t billed_cpu_time_us  )
     {
        FC_ASSERT(deadline != fc::time_point(), "deadline cannot be uninitialized");
    
        transaction_trace_ptr trace;
        try {
              if (!implicit) {
                 //检验权限和
                 authorization.check_authorization(
                         trx->trx.actions,
                         trx->recover_keys(),
                         {},
                         trx_context.delay,
                         [](){}
              }
    
        } FC_CAPTURE_AND_RETHROW((trace))
     } /// push_transaction
    
       //从签名里获取action发起者拥有的public key
        const flat_set<public_key_type>& recover_keys() {
           // TODO: Update caching logic below when we use a proper chain id setup for the particular blockchain rather than just chain_id_type()
           if( !signing_keys )
              signing_keys = trx.get_signature_keys( chain_id_type() );
           return *signing_keys;
        }
      void
         authorization_manager::check_authorization( const vector<action>&                actions,
                                                     const flat_set<public_key_type>&     provided_keys,
                                                     const 
      flat_set<permission_level>&    provided_permissions,
                                                    )const
         {
            map<permission_level, fc::microseconds> permissions_to_satisfy;
    
            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;
                  //系统级action,比如修改权限的授权,链接授权等action,它的执行权限permission是固定的,需要在这里检测签名的keys是否具备相应的permission
                  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 );
                  ……..
                  }
               }
    
               //authorization
               //其他action,检测授权是否正确
               for( const auto& declared_auth : act.authorization ) {
                  //对于上面的case,这里的declared_auth=account@publish
                  checktime();
    
                  if( !special_case ) {
                     //获取该action需要的最低权限
                     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}) );
                     }
                  }
    
         }

    声明的权限的授权签名是否正确

    void
     authorization_manager::check_authorization( const vector<action>&                actions,
                                                 const flat_set<public_key_type>&     provided_keys,
                                                 const flat_set<permission_level>&    provided_permissions,
                                                )const
     { 
       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
                                        );
      …..
      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",
                       ("auth", p.first)("provided_delay", provided_delay.count()/1000)
                       ("delay_max_limit_ms", delay_max_limit.count()/1000)
                     );
    
        }

    权限验证实例

    修改权限,命令如下:

    $cleos set account permission testaccount active '{"threshold" : 1, "keys" : [], "accounts" : [{"permission":{"actor":"bob","permission":"active"},"weight":1}, {"permission":{"actor":"stacy","permission":"active"},"weight":1}]}’ owner

    该命令没有添加permission参数,cleos会自动添加默认权限声明,其等价于

    $cleos set account permission testaccount active '{"threshold" : 1, "keys" : [], "accounts" : [{"permission":{"actor":"bob","permission":"active"},"weight":1}, {"permission":{"actor":"stacy","permission":"active"},"weight":1}]}’ owner
    -p testaccount@active

    -p testaccount@active是cleos自动补全的
    该action打包到transaction然后进入到了某一个矿工节点,然后就会执行上面的authorization_manager::check_authorization函数来验证权限,而对于该系统action,会调用check_updateauth_authorization来检验

    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];
        EOS_ASSERT( auth.actor == update.account, irrelevant_auth_exception,
                    "the owner of the affected permission needs to be the actor of the declared authorization" );
        //检测对应的permission是否存在
        const auto* min_permission = find_permission({update.account, update.permission});
        if( !min_permission ) { // creating a new permission
           //不存在则以父permission为min_permission检测
           min_permission = &get_permission({update.account, update.parent});
        }
        //示例中,testaccount.active存在,所以min_permission=testaccount@active
        //声明的也是testaccount@ative,所以能通过验证
        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}) );
     }

    contract函数执行类型的action, 权限检测由contract代码激发,比如下面的例子

    void hi( account_name user ) {
     require_auth( user );
     print( "Hello, ", name{user} );
    }

    require_auth(user)就会激发对‘user@active’权限的检测

    转载自:http://blog.csdn.net/itleaks

[EOS源码分析]EOS账号钱包密钥等基本概念及操作实践

cleos

cleos应用程序是用户端命令行交互模块,用于解析用户命令,执行钱包,账号等如下操作
cleos依赖keosd和nodeos等应用程序处理这些操作。
当keosd没有启动时,cleos会自动启动该程序,对应的代码如下:

void ensure_keosd_running() {
    …
    binPath.append("keosd"); // if cleos and keosd are in the same installation directory

    if (boost::filesystem::exists(binPath)) {
        //启动keosd
        ::boost::process::child keos(binPath, pargs,
                                     bp::std_in.close(),
                                     bp::std_out > bp::null,
                                     bp::std_err > bp::null);
        if (keos.running()) {
            std::cerr << binPath << " launched" << std::endl;
            keos.detach();
            sleep(1);
        } else {
        }
    } else {
    }
}

action&message&Transaction

目前在EOS中message和action的概念是一样的,实际上action是自3.0以后的新规范,message的概念已经可以抛弃,由于3.0版本发布不久,互联网上的大部分文字和材料依然使用message的概念.
一个transaction包含一个或者多个action, 和以太坊交易概念差不多。所以,你可以简单的把action看成以太坊的transaction

key

EOS里的key就是public key公钥,类似以太坊的地址。key是通过类似RSA,椭圆曲线算法生成的公钥。

$ cleos create key
Private key: 5JdchMrwMwD1PsZKCjpbaCQ4aJ3cFKzSWmCQfRzKCiGrDWds3PU
Public key: EOS7KBTMkUq4VPakqsZUnZfBbMbS2U7cn9qSa3q6G5ZzEeUeNSVgv

以太坊地址可以收款,付款,而EOS的key只具备验证作用,只有account才具备收款,付款功能。
注意:cleos并不保存生成的私钥,所以得你自己记录private key,public key

钱包

钱包是一个私钥库,里面保存着私钥公钥对, 类似以太坊里的keystore信息。里面保存的私钥数据其实是私钥加密后数据,需要用户输入密码才能还原出真正的私钥。智能合约的action执行都需要钱包来解锁相关的公钥(key).所以钱包可以看成是钥匙箱。

创建默认钱包并导入key

  • 创建钱包
    $ cleos wallet create
    Creating wallet: default
    Save password to use in the future to unlock this wallet.
    Without password imported keys will not be retrievable.
    "PW5KWPhDnRCLBFjBuEBdUmFP4a2F8H36KvTv4DhtfHRZokrqAK9bT"

    钱包本身也是有密码的,用来加密钱包数据的

  • 导入key
    $ cleos wallet import 5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3
  • 查看wallet里的keys
    $ cleos wallet keys
    [[
      "EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV”, //公钥
      “5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3”    //私钥
    ]
    ]

    创建其他钱包并导入key

    
    $ cleos wallet create -n test
    Creating wallet: test
    Save password to use in the future to unlock this wallet.
    Without password imported keys will not be retrievable.
    “PW5KNZ27fR8qcYpsw2B5uM7yir1CnYVSejAi8R3sYdRK4DyW6BN6v"

$ cleos wallet import 5JdchMrwMwD1PsZKCjpbaCQ4aJ3cFKzSWmCQfRzKCiGrDWds3PU

$ build/programs/cleos/cleos wallet keys
[[
//这个是default钱包的key
"EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV",
“5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3”
],[
//这个是test钱包的key
"EOS7KBTMkUq4VPakqsZUnZfBbMbS2U7cn9qSa3q6G5ZzEeUeNSVgv",
"5JdchMrwMwD1PsZKCjpbaCQ4aJ3cFKzSWmCQfRzKCiGrDWds3PU"
]
]

####列出所有钱包
```shell
$ build/programs/cleos/cleos wallet list
Wallets:
[
  "default *",
  "test *"
]

解锁及锁定钱包

$ cleos wallet unlock -n test
$ cleos wallet lock -n test

-n xxx是指定操作的钱包名字,如果不带-n xxx则是操作默认钱包

钱包数据文件

$ls ~/eosio-wallet/
config.ini    default.wallet    test.wallet
$ cat ~/eosio-wallet/default.wallet
{
  "cipher_keys": "0bcea4c898817192773ca628e9f829eed0c1e5024bf3de3d5032e2e029842b9756b46e37357403858a6f38a67268f30d9f70d8ce45e930802927f227d65b0162615872173637142a645cb50f1816d47c187e6345f8ced73896efe5a5a636197595f457641379408afb501dc483b108c131cf4f7fdd3381497c6a3245b9773c02217a4cd77b30f4da23a519c5caa01e56"
}

可见一个钱包对应~/eosio-wallet/的一个文件,文件里面的内容就是密码加密私钥后的内容

Account(账号)

EOS的账号对应以太坊的智能合约地址,它扩展了账号的概念,一个账号由智能合约+权限管理构成。

权限管理

权限管理模块可以精细的定义一个Account/key或者几个Account/key(比如多重签名机制)对账号数据的访问权限。目前有两个默认的权限类型

  • owner权限
    可以修改任意账号和key的权限
  • active
    可转账,DPOS投票,及其它上层定义的权限修改
    用户也可自定义权限类型。同时引入了权重和阈值的概念,该机制使得多重签名实现非常简单,也更有扩展性,permission格式如下
    {
      "threshold": 100,/*An integer that defines cumulative signature weight required for authorization*/
      "keys": [], /*An array made up of individual permissions defined with an EOS PUBLIC KEY*/
      "accounts": [] /*An array made up of individual permissions defined with an EOS ACCOUNT*/
    }

配置示例如下

默认权限配置:

Permission Accounts/Keys Weight Threshold
owner 1
EOS7KBTMkUq4VPakqsZUnZfBbMbS2U7cn9qSa3q6G5ZzEeUeNSVgv 1
active 1
EOS7KBTMkUq4VPakqsZUnZfBbMbS2U7cn9qSa3q6G5ZzEeUeNSVgv 1

组合和自定义权限

Permission Accounts/Keys Weight Threshold
owner 2
@bob 1
@stacy 1
active 1
@bob 1
@stacy 1
publish 2
@bob 2
@stacy 1
EOS7KBTMkUq4VPakqsZUnZfBbMbS2U7cn9qSa3q6G5ZzEeUeNSVgv 1
  • bob和stacy两个账号一起才能行使owner权限

  • bob, stacy两个账号的任一一个都可以行使active权限

  • bob可以行使publish权限,但是stacy和”EOS7KBTMkU…"没法单独行使publish权限,但是他们一起可以行使publish权限
    这些权限的检测一般在执行action操作时执行。

    创建账号account

    cleos create account [OPTIONS] creator name OwnerKey ActiveKey

    该命令本身是一个action,会产生一个transaction,最后会保存在链上的,所以该操作依赖nodeos程序,必须启动nodeos程序。上面的OwnerKey,ActiveKey都是公钥。creator必须是一个已经存在的账号,这里就有个问题了,我们第一次创建账号,从哪里获取这个creator账号?这个账号就是eosio, eosio这个特殊账号是在nodeos启动时自动生成的,且这个账号的private key,和public key是hardcode固定的。当然搭建私有网络你可以通过修改config文件来修改这两个值,但是你同时也得修改这个私有网络的其他节点对应的private key, public key值。

    $ cleos create account -j eosio testaccount EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV
    {
    "transaction_id": "18440905e1dc17eecee17eaa50f5c589f703c3c4f66738316c2f56d12ce7ec84",
      "action_traces": [{
            ...
            "data": {
              "creator": "eosio",
              "name": "testaccount",
              "owner": {
                "threshold": 1,
                "keys": [{
                    "key": "EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV",
                    "weight": 1
                  }
                ],
                "accounts": [],
                "waits": []
              },
              "active": {
                "threshold": 1,
                "keys": [{
                    "key": "EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV",
                    "weight": 1
                  }
                ],
                "accounts": [],
                "waits": []
              }
            },
            ….
          },
          ...
        }
      ],
    }
    }

    会产生两种默认权限owner和active
    创建账号其实就是创建一个action

    chain::action create_newaccount(const name& creator, const name& newaccount, public_key_type owner, public_key_type active) {
     return action {
        tx_permission.empty() ? vector<chain::permission_level>{{creator,config::active_name}} : get_account_permissions(tx_permission),
        eosio::chain::newaccount{
           .creator      = creator,
           .name         = newaccount,
           .owner        = eosio::chain::authority{1, {{owner, 1}}, {}},
           .active       = eosio::chain::authority{1, {{active, 1}}, {}}
        }
     };
    }

    你在nodeos程序端会看到一个trxs

    1254502ms thread-0   producer_plugin.cpp:585       block_production_loo ] Produced block 000006d1c28d6c68... #1745 @ 2018-05-22T02:20:54.500 signed by eosio [trxs: 1, lib: 1744, confirmed: 0]

    查看账号信息

      $ cleos  get account testaccount
      privileged: false
      permissions:
           owner     1:    1 EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV
              active     1:    1 EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV
      memory:
           quota:        -1 bytes  used:      2.66 Kb   
    
      net bandwidth: (averaged over 3 days)
           used:                -1 bytes
           available:           -1 bytes
           limit:               -1 bytes
    
      cpu bandwidth: (averaged over 3 days)
           used:                -1 us   
           available:           -1 us   
           limit:               -1 us

    修改权限命令格式

    $ cleos set account permission [OPTIONS] account permission authority [parent]
  • account,表示要修改的账户

  • permission 表示要设置的权限(上面的owner,active,publish)

  • authority 权限内容,JSON字符串

  • parent 上级权限

    修改active权限

      $ cleos set account permission testaccount active '{"threshold" : 1, "keys" : [], "accounts" : [{"permission":{"actor":"bob","permission":"active"},"weight":1}, {"permission":{"actor":"stacy","permission":"active"},"weight":1}]}’ owner
      executed transaction: b1bc9680a9ba615a6de8c3f7c692d7d28ff97edae245bb40f948692b14ea6c15  160 bytes  189 us
      #         eosio <= eosio::updateauth            {"account":"testaccount","permission":"active","parent":"owner","auth":{"threshold":1,"keys":[],"acc...
      warning: transaction executed locally, but may not be confirmed by the network yet
      $ build/programs/cleos/cleos  get account testaccount
      privileged: false
      permissions:
           owner     1:    1 EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV
              active     1:    1 bob@active, 1 stacy@active,
      memory:
           quota:        -1 bytes  used:     2.674 Kb   
    
      net bandwidth: (averaged over 3 days)
           used:                -1 bytes
           available:           -1 bytes
           limit:               -1 bytes
    
      cpu bandwidth: (averaged over 3 days)
           used:                -1 us   
           available:           -1 us   
           limit:               -1 us 

    新增自定义权限

      $ cleos set account permission testaccount publish '{"threshold" : 2, "keys" : [{"permission":{"key":"EOS8X7Mp7apQWtL6T2sfSZzBcQNUqZB7tARFEm9gA9Tn9nbMdsvBB","permission":"active"},"weight":1}], "accounts" : [{"permission":{"actor":"bob","permission":"active"},"weight":2}, {"permission":{"actor":"stacy","permission":"active"},"weight":1}]}’ active
      executed transaction: a0f8d79f92e375b13c6f6da55b5b1f1aeebcbf1240a9bd287d3c845fc6b6941d  200 bytes  171 us
      #         eosio <= eosio::updateauth            {"account":"testaccount","permission":"publish","parent":"active","auth":{"threshold":2,"keys":[{"ke...
      warning: transaction executed locally, but may not be confirmed by the network yet
      $ cleos  get account testaccount
      privileged: false
      permissions:
           owner     1:    1 EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV
              active     1:    1 bob@active, 1 stacy@active,
                 publish     2:    1 EOS1111111111111111111111111111111114T1Anm2 bob@active, 1 stacy@active,
      memory:
           quota:        -1 bytes  used:     3.066 Kb   
    
      net bandwidth: (averaged over 3 days)
           used:                -1 bytes
           available:           -1 bytes
           limit:               -1 bytes
    
      cpu bandwidth: (averaged over 3 days)
           used:                -1 us   
           available:           -1 us   
    
           limit:               -1 us   

    权限应用场景

    一个account(账号)可以有智能合约,智能合约有各种action,每个action可以指定permission,这样就可以限制action的执行权限了,具体细节我会在后面的博文里单独介绍

    转载自:http://blog.csdn.net/itleaks