您正在查看: EOS 分类下的文章

eos系统合约介绍 — 提案合约eosio.msig (下)

简介

本篇将为大家介绍eosio.msig的源码实现,合约代码库详见:eosio.msig。eosio.msig主要有propose、approve、unapprove、cancel、exec、invalidate这几种方法,下面会详细逐一介绍每种方法的功能和实现细节。

主要合约方法

eosio.msig合约,在eosio.msig.hpp头文件中,主要定义了以下六个合约方法:

  • propose:提出提案
  • approve:通过提案
  • unapprove:不通过提案
  • cancel:取消提案
  • exec:执行提案
  • invalidate:撤回对之前所有该账户通过、但未被最终执行的提案的通过授权

eosio.msig合约头文件

namespace eosio {

   class multisig : public contract {
      public:
         multisig( account_name self ):contract(self){}

         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 );
         void invalidate( account_name account );

propose方法

propose方法主要功能是提出提案,对应上篇提到的 cleos multisig propose 命令,传参如下:

  • proposer:提案账户
  • proposal_name:提案名
  • requested:提案通过所需权限
  • trx:提案具体执行的交易内容

为了节省资源开销,propose方法并不会根据 cleos multisig propose 传入的参数一一做解析,而是直接解析input data

/*
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)
*/

void multisig::propose() {
   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 );

之后,会做一系列前置检验工作:proposer提案账户授权是否正确、交易是否超时、propose_name是否存在、提案通过所需权限是否正确等

require_auth( proposer );
   eosio_assert( trx_header.expiration >= eosio::time_point_sec(current_time_point()), "transaction expired" );
   //eosio_assert( trx_header.actions.size() > 0, "transaction must have at least one action" );

   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" );

之后,将提案和提案合约的内容存表,将提案通过所需的权限存入requested_approvals表中,我们上篇文章所提到 cleos get table eosio.msig <proposer account> approvals 命令查询的就是这张表

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.reserve( requested.size() );
      for ( auto& level : requested ) {
         a.requested_approvals.push_back( approval{ level, time_point{ microseconds{0} } } );
      }
   });

approve方法

approve方法的主要功能是通过提案,对应上篇提到的 cleos multisig approve 命令,传参如下

  • proposer:提案人
  • proposal_name:提案名
  • permissions:使用哪个权限批准这个提案

首先,系统会查找提案合约内容,查找 requested_approvals 表中需要通过的权限中,是否有和传入permission匹配项。若有匹配项,将此权限加入 provided_approvals 表,即表示该权限通过此提案,并从 requested_approvals 表中移除该权限。

void multisig::approve( account_name proposer, name proposal_name, permission_level level ) {
   require_auth( level );

   approvals apptable(  _self, proposer );
   auto apps_it = apptable.find( proposal_name );
   if ( apps_it != apptable.end() ) {
      auto itr = std::find_if( apps_it->requested_approvals.begin(), apps_it->requested_approvals.end(), [&](const approval& a) { return a.level == level; } );
      eosio_assert( itr != apps_it->requested_approvals.end(), "approval is not on the list of requested approvals" );

      apptable.modify( apps_it, proposer, [&]( auto& a ) {
            a.provided_approvals.push_back( approval{ level, current_time_point() } );
            a.requested_approvals.erase( itr );
         });
   } else {
      old_approvals old_apptable(  _self, proposer );
      auto& apps = old_apptable.get( proposal_name, "proposal not found" );

      auto itr = std::find( apps.requested_approvals.begin(), apps.requested_approvals.end(), level );
      eosio_assert( itr != apps.requested_approvals.end(), "approval is not on the list of requested approvals" );

      old_apptable.modify( apps, proposer, [&]( auto& a ) {
            a.provided_approvals.push_back( level );
            a.requested_approvals.erase( itr );
         });
   }
}

unapprove方法

unapprove方法的主要功能是不通过提案,对应上篇提到的 cleos multisig unapprove 命令,传参如下:

  • proposer:提案人
  • proposal_name:提案名
  • permissions:使用哪个权限拒绝这个提案

首先,系统会查找提案合约内容,查找 provided_approvals 表中通过的权限中,是否有和传入permission匹配项。若有匹配项,将此权限加入requested_approvals 表,即表示该权限还没通过此提案,并从 provided_approvals 表中移除该权限。

void multisig::unapprove( account_name proposer, name proposal_name, permission_level level ) {
   require_auth( level );

   approvals apptable(  _self, proposer );
   auto apps_it = apptable.find( proposal_name );
   if ( apps_it != apptable.end() ) {
      auto itr = std::find_if( apps_it->provided_approvals.begin(), apps_it->provided_approvals.end(), [&](const approval& a) { return a.level == level; } );
      eosio_assert( itr != apps_it->provided_approvals.end(), "no approval previously granted" );
      apptable.modify( apps_it, proposer, [&]( auto& a ) {
            a.requested_approvals.push_back( approval{ level, current_time_point() } );
            a.provided_approvals.erase( itr );
         });
   } else {
      old_approvals old_apptable(  _self, proposer );
      auto& apps = old_apptable.get( proposal_name, "proposal not found" );
      auto itr = std::find( apps.provided_approvals.begin(), apps.provided_approvals.end(), level );
      eosio_assert( itr != apps.provided_approvals.end(), "no approval previously granted" );
      old_apptable.modify( apps, proposer, [&]( auto& a ) {
            a.requested_approvals.push_back( level );
            a.provided_approvals.erase( itr );
         });
   }
}

cancel方法

cancel方法的主要功能是取消提案,对应上篇提到的 cleos multisig cancel 命令,传参如下:

  • proposer:提案账户
  • proposal_name:提案名
  • canceler:取消账户

首先,先查找表获取提案内容。如果canceler账户和提案账户不同,则在提案交易过期之前,canceler都不能取消提案。若能取消,将提案从表中移除。

void multisig::cancel( account_name proposer, name proposal_name, account_name canceler ) {
   require_auth( canceler );

   proposals proptable( _self, proposer );
   auto& prop = proptable.get( proposal_name, "proposal not found" );

   if( canceler != proposer ) {
      eosio_assert( unpack<transaction_header>( prop.packed_transaction ).expiration < eosio::time_point_sec(current_time_point()), "cannot cancel until expiration" );
   }
   proptable.erase(prop);

   //remove from new table
   approvals apptable(  _self, proposer );
   auto apps_it = apptable.find( proposal_name );
   if ( apps_it != apptable.end() ) {
      apptable.erase(apps_it);
   } else {
      old_approvals old_apptable(  _self, proposer );
      auto apps_it = old_apptable.find( proposal_name );
      eosio_assert( apps_it != old_apptable.end(), "proposal not found" );
      old_apptable.erase(apps_it);
   }
}

exec方法

exec方法的主要功能是执行提案,对应上篇提到的 cleos multisig exec 命令,传参如下:

  • proposer:提案账户
  • proposal_name:提案名
  • executer:执行账户
void multisig::exec( account_name proposer, name proposal_name, account_name executer ) {
   require_auth( executer );

   proposals proptable( _self, proposer );
   auto& prop = proptable.get( proposal_name, "proposal not found" );
   transaction_header trx_header;
   datastream<const char*> ds( prop.packed_transaction.data(), prop.packed_transaction.size() );
   ds >> trx_header;
   //首先,需要做前置检查,检查交易是否过期
   eosio_assert( trx_header.expiration >= eosio::time_point_sec(current_time_point()), "transaction expired" );

   approvals apptable(  _self, proposer );
   auto apps_it = apptable.find( proposal_name );
   vector<permission_level> approvals;
   //然后,查 provided_approvals 表获取通过提案交易的权限们,对比 inv_table 表,如果权限不在 inv_table 表中或者 last_invalidation_time 
   //已经小于当前时间,代表权限有效,放入approvals表中。inv_table 表的用途在下一个invalidate方法中介绍
   invalidations inv_table( _self, _self );
   if ( apps_it != apptable.end() ) {
      approvals.reserve( apps_it->provided_approvals.size() );
      for ( auto& p : apps_it->provided_approvals ) {
         auto it = inv_table.find( p.level.actor );
         if ( it == inv_table.end() || it->last_invalidation_time < p.time ) {
            approvals.push_back(p.level);
         }
      }
      apptable.erase(apps_it);
   } else {
      old_approvals old_apptable(  _self, proposer );
      auto& apps = old_apptable.get( proposal_name, "proposal not found" );
      for ( auto& level : apps.provided_approvals ) {
         auto it = inv_table.find( level.actor );
         if ( it == inv_table.end() ) {
            approvals.push_back( level );
         }
      }
      old_apptable.erase(apps);
   }
   //最后,执行提案。如果交易执行权限检验无误,会发起一个defer延迟合约,去执行提案交易。如果执行成功,
   //`cleos get actions <executer account>` 会产生两条actions,一条是exec的交易,一条是提案执行的交易。
   bytes packed_provided_approvals = pack(approvals);
   auto res = ::check_transaction_authorization( prop.packed_transaction.data(), prop.packed_transaction.size(),
                                                 (const char*)0, 0,
                                                 packed_provided_approvals.data(), packed_provided_approvals.size()
                                                 );
   eosio_assert( res > 0, "transaction authorization failed" );

   send_deferred( (uint128_t(proposer) << 64) | proposal_name, executer, prop.packed_transaction.data(), prop.packed_transaction.size() );

   proptable.erase(prop);
}

invalidate方法

invalidate方法的主要功能是:如果account之前通过的提案还未执行,就可以使用该方法将提案一键设置为无效。这个方法主要是解决:账户权限变更时,之前通过但未执行的提案一旦执行会盗取账户权限的问题,详见issue。该方法传参如下:

  • account:提案的批准账户

该功能的实现非常简单,首先,inv_table 是用来存放权限的,它的两个字段 account 和last_invalidation_time 分别是账户名和账户权限最近失效时间。last_invalidation_time 时间之前,account的提案批准权限都不可用,在该时间之后account的提案批准权限才能生效。

因此,如果想使account之前审批通过的所有提案都失效的话,就将 last_invalidation_time 设置为当前时间即可。exec方法在执行之前会检查 inv_table,则包含在 inv_table 中的account,即便批准了该提案,该批准也会作废

void multisig::invalidate( account_name account ) {
   require_auth( account );
   invalidations inv_table( _self, _self );
   auto it = inv_table.find( account );
   if ( it == inv_table.end() ) {
      inv_table.emplace( account, [&](auto& i) {
            i.account = account;
            i.last_invalidation_time = current_time_point();
         });
   } else {
      inv_table.modify( it, account, [&](auto& i) {
            i.last_invalidation_time = current_time_point();
         });
   }
}

转载自简书

eos系统合约介绍 — 提案合约eosio.msig (上)

简介

本篇将为大家介绍eos另一个系统合约eos.msig的主要功能和源码实现细节。eos.msig是eos的提案合约,同样也是cleos multisig命令调用的系统合约,可用于提案、通过/不通提案、执行多重签名交易等功能。由于涉及内容较多,介绍将分为上下两篇,上篇将围绕eos账户权限、cleos multisig命令的使用这几个方面进行介绍,下篇则会为大家介绍eosio.msig的源码实现

Eos账户权限

回顾一下之前的文章,一个账户最基本的权限owner和active是由公私钥对控制的,然而,eos丰富的权限控制方式,还允许我们将一个账户的权限,下放给其他的账户。比如下面的经典例子:
账户popo的权限情况

permissio account weight threshold
owner 2
@user1 1
@user2 1
active 1
@user1 1
@user2 1

我们将一步一步,通过命令行操作的方式来为大家介绍eos权限的应用。

1.我们分别创建三个账户popo11111111、zoin11111111、zoin22222222,每个账户使用不同的公私钥对。
分别创建6对公私钥,分别用于三个账号的owner和active权限

cleos create key --to-console

| 账号 | 权限|公钥 |私钥 |
| ------------ | ------------ | ------------ |
| popo11111111 | owner | EOS81WjiHefR6c5VKYjvdSat68RDJ3qGPgCckJqG3pXbDbVjZR5QB | 5JAxr3bM6DwwtcKJ7aVydpoaSA2oyiZ94PjqTpW6uXDZ41VuK22|
| popo11111111 | active | EOS7ob62mdBfiRt1a8tQrQGbhnU1gEAvWyQDFHs99HzYEh8wxeQ9T |5KRCeqsHAx1UNTtz7BH5N4SSMe75XMCj3j93TqQRDrYCwbjccoP |
| zoin11111111 | owner | EOS5N3qMPXjtwZpMyPeiYXSmBQbr7Lt5Ro2W8pLqyAzFYQPknoUvZ |5KWYcdZcJ1E8mWtg1Qoq5s1fYXJxMQkLupozfZEUEiu6vn6QNzx |
| zoin11111111 | active | EOS6BAfK8mZZjMaa74zxkT3evWEtWo4AnRQYmnpGWkNJYKVjcrpao | 5Jwy6VbCwpkmczjYgMNVtr9Nd77aenZcPE8DtvPwtYqztbgMzu4|
| zoin22222222 | owner | EOS6hBy5yioHpcH4SxSe91aH8vCGAhhEifKtp1jZEy7PS2rhJFSER |5Hz3u44Avpf4NCVDQdwZGSVXpbpEdRFczo2Wm9FC14pqcx8mUwM |
| zoin22222222 | active | EOS5TeY2C9bdewrbS58SZnrp5azF5Cq7AXraNJihxaW4hbGeWSaeE |5JFoGdAc6zPG9KdHCHPHFL5XbpEnFmDq61y53Bw3Hm3VuFy7Stk |

2.下面开始创建三个账号

cleos system newaccount 已有账号 将要创建的账号 ownerkey  activekey  --stake-net '0.1 EOS' --stake-cpu '0.1 EOS'  --buy-ram-kbytes 4000

利用我们上面文章《以传统的WEB开发方式,来举例理解Dapp开发》中已经在麒麟测试网创建的账号cryptokylinq,并在麒麟测试网来创建其他账号。

suroudeMacBook-Pro:~ surou$ cleos -u http://kylin.fn.eosbixin.com system newaccount cryptokylinq popo11111111 EOS81WjiHefR6c5VKYjvdSat68RDJ3qGPgCckJqG3pXbDbVjZR5QB  EOS7ob62mdBfiRt1a8tQrQGbhnU1gEAvWyQDFHs99HzYEh8wxeQ9T  --stake-net '0.1 EOS' --stake-cpu '0.1 EOS'  --buy-ram-kbytes 4000
executed transaction: ada14edaa2dc356f02f8f19515f3227669887a5a840601d28f8d59238e1c7890  336 bytes  4284 us
#         eosio <= eosio::newaccount            {"creator":"cryptokylinq","name":"popo11111111","owner":{"threshold":1,"keys":[{"key":"EOS81WjiHefR6...
#         eosio <= eosio::buyrambytes           {"payer":"cryptokylinq","receiver":"popo11111111","bytes":4096000}
#   eosio.token <= eosio.token::transfer        {"from":"cryptokylinq","to":"eosio.ram","quantity":"99.8497 EOS","memo":"buy ram"}
#  cryptokylinq <= eosio.token::transfer        {"from":"cryptokylinq","to":"eosio.ram","quantity":"99.8497 EOS","memo":"buy ram"}
#     eosio.ram <= eosio.token::transfer        {"from":"cryptokylinq","to":"eosio.ram","quantity":"99.8497 EOS","memo":"buy ram"}
#   eosio.token <= eosio.token::transfer        {"from":"cryptokylinq","to":"eosio.ramfee","quantity":"0.5018 EOS","memo":"ram fee"}
#  cryptokylinq <= eosio.token::transfer        {"from":"cryptokylinq","to":"eosio.ramfee","quantity":"0.5018 EOS","memo":"ram fee"}
#  eosio.ramfee <= eosio.token::transfer        {"from":"cryptokylinq","to":"eosio.ramfee","quantity":"0.5018 EOS","memo":"ram fee"}
#         eosio <= eosio::delegatebw            {"from":"cryptokylinq","receiver":"popo11111111","stake_net_quantity":"0.1000 EOS","stake_cpu_quanti...
#   eosio.token <= eosio.token::transfer        {"from":"cryptokylinq","to":"eosio.stake","quantity":"0.2000 EOS","memo":"stake bandwidth"}
#  cryptokylinq <= eosio.token::transfer        {"from":"cryptokylinq","to":"eosio.stake","quantity":"0.2000 EOS","memo":"stake bandwidth"}
#   eosio.stake <= eosio.token::transfer        {"from":"cryptokylinq","to":"eosio.stake","quantity":"0.2000 EOS","memo":"stake bandwidth"}
2018-09-25T04:07:44.999 thread-0   main.cpp:458                  print_result   warning: transaction executed locally, but may not be confirmed by the network yet
suroudeMacBook-Pro:~ surou$ cleos -u http://kylin.fn.eosbixin.com system newaccount cryptokylinq zoin11111111 EOS5N3qMPXjtwZpMyPeiYXSmBQbr7Lt5Ro2W8pLqyAzFYQPknoUvZ  EOS6BAfK8mZZjMaa74zxkT3evWEtWo4AnRQYmnpGWkNJYKVjcrpao  --stake-net '0.1 EOS' --stake-cpu '0.1 EOS'  --buy-ram-kbytes 4000
executed transaction: daf4b102f68d7161022c0ac8635d91137a0d2d24072d49fdad13a608e2e03072  336 bytes  5102 us
#         eosio <= eosio::newaccount            {"creator":"cryptokylinq","name":"zoin11111111","owner":{"threshold":1,"keys":[{"key":"EOS5N3qMPXjtw...
#         eosio <= eosio::buyrambytes           {"payer":"cryptokylinq","receiver":"zoin11111111","bytes":4096000}
#   eosio.token <= eosio.token::transfer        {"from":"cryptokylinq","to":"eosio.ram","quantity":"99.8589 EOS","memo":"buy ram"}
#  cryptokylinq <= eosio.token::transfer        {"from":"cryptokylinq","to":"eosio.ram","quantity":"99.8589 EOS","memo":"buy ram"}
#     eosio.ram <= eosio.token::transfer        {"from":"cryptokylinq","to":"eosio.ram","quantity":"99.8589 EOS","memo":"buy ram"}
#   eosio.token <= eosio.token::transfer        {"from":"cryptokylinq","to":"eosio.ramfee","quantity":"0.5019 EOS","memo":"ram fee"}
#  cryptokylinq <= eosio.token::transfer        {"from":"cryptokylinq","to":"eosio.ramfee","quantity":"0.5019 EOS","memo":"ram fee"}
#  eosio.ramfee <= eosio.token::transfer        {"from":"cryptokylinq","to":"eosio.ramfee","quantity":"0.5019 EOS","memo":"ram fee"}
#         eosio <= eosio::delegatebw            {"from":"cryptokylinq","receiver":"zoin11111111","stake_net_quantity":"0.1000 EOS","stake_cpu_quanti...
#   eosio.token <= eosio.token::transfer        {"from":"cryptokylinq","to":"eosio.stake","quantity":"0.2000 EOS","memo":"stake bandwidth"}
#  cryptokylinq <= eosio.token::transfer        {"from":"cryptokylinq","to":"eosio.stake","quantity":"0.2000 EOS","memo":"stake bandwidth"}
#   eosio.stake <= eosio.token::transfer        {"from":"cryptokylinq","to":"eosio.stake","quantity":"0.2000 EOS","memo":"stake bandwidth"}
2018-09-25T04:11:22.091 thread-0   main.cpp:458                  print_result   warning: transaction executed locally, but may not be confirmed by the network yet
suroudeMacBook-Pro:~ surou$ cleos -u http://kylin.fn.eosbixin.com system newaccount cryptokylinq zoin22222222 EOS6hBy5yioHpcH4SxSe91aH8vCGAhhEifKtp1jZEy7PS2rhJFSER  EOS5TeY2C9bdewrbS58SZnrp5azF5Cq7AXraNJihxaW4hbGeWSaeE  --stake-net '0.1 EOS' --stake-cpu '0.1 EOS'  --buy-ram-kbytes 4000
executed transaction: 7b184ccebaeb2cd2b6cece49ec46abd86b20a1d20b29722d21029dd1abc958f0  336 bytes  2667 us
#         eosio <= eosio::newaccount            {"creator":"cryptokylinq","name":"zoin22222222","owner":{"threshold":1,"keys":[{"key":"EOS6hBy5yioHp...
#         eosio <= eosio::buyrambytes           {"payer":"cryptokylinq","receiver":"zoin22222222","bytes":4096000}
#   eosio.token <= eosio.token::transfer        {"from":"cryptokylinq","to":"eosio.ram","quantity":"99.8723 EOS","memo":"buy ram"}
#  cryptokylinq <= eosio.token::transfer        {"from":"cryptokylinq","to":"eosio.ram","quantity":"99.8723 EOS","memo":"buy ram"}
#     eosio.ram <= eosio.token::transfer        {"from":"cryptokylinq","to":"eosio.ram","quantity":"99.8723 EOS","memo":"buy ram"}
#   eosio.token <= eosio.token::transfer        {"from":"cryptokylinq","to":"eosio.ramfee","quantity":"0.5019 EOS","memo":"ram fee"}
#  cryptokylinq <= eosio.token::transfer        {"from":"cryptokylinq","to":"eosio.ramfee","quantity":"0.5019 EOS","memo":"ram fee"}
#  eosio.ramfee <= eosio.token::transfer        {"from":"cryptokylinq","to":"eosio.ramfee","quantity":"0.5019 EOS","memo":"ram fee"}
#         eosio <= eosio::delegatebw            {"from":"cryptokylinq","receiver":"zoin22222222","stake_net_quantity":"0.1000 EOS","stake_cpu_quanti...
#   eosio.token <= eosio.token::transfer        {"from":"cryptokylinq","to":"eosio.stake","quantity":"0.2000 EOS","memo":"stake bandwidth"}
#  cryptokylinq <= eosio.token::transfer        {"from":"cryptokylinq","to":"eosio.stake","quantity":"0.2000 EOS","memo":"stake bandwidth"}
#   eosio.stake <= eosio.token::transfer        {"from":"cryptokylinq","to":"eosio.stake","quantity":"0.2000 EOS","memo":"stake bandwidth"}
2018-09-25T04:13:52.457 thread-0   main.cpp:458                  print_result   warning: transaction executed locally, but may not be confirmed by the network yet

3.将popo11111111的owner权限交出,由zoin11111111和zoin22222222的owner权限控制。每个权限的权重weight是1,而门槛threshold是2,即使用popo11111111的owner权限发送交易,需要zoin11111111和zoin22222222进行多重签名才行
先将popo11111111的owner私钥导入钱包

suroudeMacBook-Pro:~ surou$ cleos wallet import
private key: imported private key for: EOS81WjiHefR6c5VKYjvdSat68RDJ3qGPgCckJqG3pXbDbVjZR5QB

查看下popo11111111信息

suroudeMacBook-Pro:~ surou$ cleos -u http://kylin.fn.eosbixin.com get account popo11111111
permissions: 
     owner     1:    1 EOS81WjiHefR6c5VKYjvdSat68RDJ3qGPgCckJqG3pXbDbVjZR5QB
        active     1:    1 EOS7ob62mdBfiRt1a8tQrQGbhnU1gEAvWyQDFHs99HzYEh8wxeQ9T

将popo11111111的owner权限交出

suroudeMacBook-Pro:~ surou$ cleos -u http://kylin.fn.eosbixin.com set account permission popo11111111 owner '{"threshold":2, "keys":[{"key": "EOS81WjiHefR6c5VKYjvdSat68RDJ3qGPgCckJqG3pXbDbVjZR5QB"}], "accounts": [{"permission": {"actor": "zoin11111111", "permission": "owner"}, "weight": 1}, {"permission": {"actor": "zoin22222222", "permission": "owner"}, "weight": 1}]}' -p popo11111111@owner
executed transaction: 38317f76ad1eb90601ad9bd2c6720e322196aebbb8bed5afacfd4f7c5098b111  200 bytes  759 us
#         eosio <= eosio::updateauth            {"account":"popo11111111","permission":"owner","parent":"","auth":{"threshold":2,"keys":[{"key":"EOS...

再次查看popo11111111账户

suroudeMacBook-Pro:~ surou$ cleos -u http://kylin.fn.eosbixin.com get account popo11111111
permissions: 
     owner     2:    60450 EOS81WjiHefR6c5VKYjvdSat68RDJ3qGPgCckJqG3pXbDbVjZR5QB1 zoin11111111@owner, 1 zoin22222222@owner, 
        active     1:    1 EOS7ob62mdBfiRt1a8tQrQGbhnU1gEAvWyQDFHs99HzYEh8wxeQ9T

已将权限owner转交给了 zoin11111111@owner, 1 zoin22222222@owner

4.将popo11111111的active权限交出,由zoin11111111和zoin22222222的active权限控制。每个权限的权重weight是1,而门槛threshold是1,即使用popo11111111的active权限发送交易,zoin11111111和zoin22222222任意一个账户签名即可
先将popo11111111的owner私钥导入钱包

suroudeMacBook-Pro:~ surou$ cleos wallet import
private key: imported private key for: EOS7ob62mdBfiRt1a8tQrQGbhnU1gEAvWyQDFHs99HzYEh8wxeQ9T

将popo11111111的active权限交出

suroudeMacBook-Pro:~ surou$ cleos -u http://kylin.fn.eosbixin.com set account permission popo11111111 active '{"threshold":1, "keys":[{"key": "EOS6L7pr3AaKekTs1dbratDq1PutoSdmBWJFwbLcStsnKBbJtNUws"}], "accounts": [{"permission": {"actor": "zoin11111111", "permission": "active"}, "weight": 1}, {"permission": {"actor": "zoin22222222", "permission": "active"}, "weight": 1}]}' -p popo11111111@active
executed transaction: c51dede340204a6561b9ad511efcf1f194075319a97f54e7fce1dbee8b20eb18  200 bytes  748 us
#         eosio <= eosio::updateauth            {"account":"popo11111111","permission":"active","parent":"owner","auth":{"threshold":1,"keys":[{"key...

再次查看popo11111111账户

suroudeMacBook-Pro:~ surou$ cleos -u http://kylin.fn.eosbixin.com get account popo11111111
permissions: 
     owner     2:    60450 EOS81WjiHefR6c5VKYjvdSat68RDJ3qGPgCckJqG3pXbDbVjZR5QB1 zoin11111111@owner, 1 zoin22222222@owner, 
        active     1:    31778 EOS6L7pr3AaKekTs1dbratDq1PutoSdmBWJFwbLcStsnKBbJtNUws1 zoin11111111@active, 1 zoin22222222@active, 

已经权限active转交给1 zoin11111111@active, 1 zoin22222222@active

cleos multisig命令

EOS账户这一节,我们将popo11111111账户的owner和active权限交给了zoin11111111和zoin22222222账户,则popo11111111账户原来的公私钥对就失效了,交易的发送需要zoin11111111和zoin22222222账户授权。

额外提一句,主网启动后,超级节点们会将eosio账户的权限交给eosio.prods账户,而eosio.prods账户是由21个超级节点的账户联合控制的,需要15个节点联合签名才能使用eosio账户,可以避免拿管理员权限作恶的现象

  • 主网eosio账户权限
    suroudeMacBook-Pro:~ surou$ cleos -u http://kylin.fn.eosbixin.com get account eosio
    privileged: true
    permissions: 
       owner     1:    1 eosio.prods@active, 
          active     1:    1 eosio.prods@active,
  • 主网eosio.prods账户权限
    suroudeMacBook-Pro:~ surou$ cleos -u http://kylin.fn.eosbixin.com get account eosio.prods
    permissions: 
       owner     1:    
          active    15:    1 acryptotitan@active, 1 alohaeostest@active, 1 blockmatrix2@active, 1 eosargentina@active, 1 eosasia11111@active, 1 eosbeijingbp@active, 1 eoscanadacom@active, 1 eosecoeoseco@active, 1 eoshuobipool@active, 1 eosiomeetone@active, 1 eosiosg11111@active, 1 eoslaomaocom@active, 1 eosnodeonebp@active, 1 eospaceioeos@active, 1 eosriobrazil@active, 1 eosstorebest@active, 1 eossv12eossv@active, 1 eosswedenorg@active, 1 eoszb1111111@active, 1 helloeoschbp@active, 1 superoneiobp@active, 
             prod.major    11:    1 acryptotitan@active, 1 alohaeostest@active, 1 blockmatrix2@active, 1 eosargentina@active, 1 eosasia11111@active, 1 eosbeijingbp@active, 1 eoscanadacom@active, 1 eosecoeoseco@active, 1 eoshuobipool@active, 1 eosiomeetone@active, 1 eosiosg11111@active, 1 eoslaomaocom@active, 1 eosnodeonebp@active, 1 eospaceioeos@active, 1 eosriobrazil@active, 1 eosstorebest@active, 1 eossv12eossv@active, 1 eosswedenorg@active, 1 eoszb1111111@active, 1 helloeoschbp@active, 1 superoneiobp@active, 
                prod.minor     8:    1 acryptotitan@active, 1 alohaeostest@active, 1 blockmatrix2@active, 1 eosargentina@active, 1 eosasia11111@active, 1 eosbeijingbp@active, 1 eoscanadacom@active, 1 eosecoeoseco@active, 1 eoshuobipool@active, 1 eosiomeetone@active, 1 eosiosg11111@active, 1 eoslaomaocom@active, 1 eosnodeonebp@active, 1 eospaceioeos@active, 1 eosriobrazil@active, 1 eosstorebest@active, 1 eossv12eossv@active, 1 eosswedenorg@active, 1 eoszb1111111@active, 1 helloeoschbp@active, 1 superoneiobp@active, 

下面,我们就要介绍cleos multisig命令如何调用eosio.msig合约进行提案、发送多重签名的交易。

1.zoin11111111发起一个转账提案,提案名是transferpopo,将popo11111111账户中的10EOS转给cryptokylinq账户,这之前需要保证popo11111111账户有币。
先给popo11111111转入点EOS

cleos -u http://kylin.fn.eosbixin.com push action eosio.token transfer '["cryptokylinq", "popo11111111","10.0000 EOS","test"]' -p cryptokylinq

再导入zoin11111111的active的私钥

suroudeMacBook-Pro:~ surou$ cleos wallet import
private key: imported private key for: EOS6BAfK8mZZjMaa74zxkT3evWEtWo4AnRQYmnpGWkNJYKVjcrpao

发起提案

suroudeMacBook-Pro:~ surou$ cleos -u http://kylin.fn.eosbixin.com multisig propose transferpopo '[{"actor": "zoin11111111", "permission": "active"}, {"actor": "zoin22222222", "permission": "active"}]'  '[{"actor": "popo11111111", "permission":"active"}]'  eosio.token transfer '{"from": "popo11111111", "to": "cryptokylinq", "quantity":"10.0000 EOS", "memo": "test multisig"}' -p zoin11111111@active
executed transaction: 83cde06310cd6cd74dfb180aa38fa4f0a5d950b6f39a4f963237a5ea6d2c39aa  240 bytes  810 us
#    eosio.msig <= eosio.msig::propose          {"proposer":"zoin11111111","proposal_name":"transferpopo","requested":[{"actor":"zoin11111111","perm...

详细分析下cleos multisig propose命令参数
- proposal_name:提案名

  • requested_permissions:提案审批通过需要的权限,这里需要zoin11111111和zoin22222222的active权限
  • trx_permission:提案执行需要的权限,需要popo11111111的active权限就能发起转账
  • contract:提案调用的合约账户,转账使用eosio.token账户合约
  • action:提案调用的合约方法,转账使用transfer方法
  • data:具体数据
  • proposer:提案发起人,zoin11111111发起
    - proposal_expiration:提案的有效时间

2.查看提案交易cleos multisig review <proposer> <proposal_name>

suroudeMacBook-Pro:~ surou$ cleos -u http://kylin.fn.eosbixin.com multisig review zoin11111111 transferpopo
{
  "proposal_name": "transferpopo",
  "packed_transaction": "4224ab5b000000000000000000000100a6823403ea3055000000572d3ccdcd011042082184402bad00000000a8ed32322e1042082184402bad60a78b1ed25cfd45a08601000000000004454f53000000000d74657374206d756c746973696700",
  "transaction": {
    "expiration": "2018-09-26T06:16:34",
    "ref_block_num": 0,
    "ref_block_prefix": 0,
    "max_net_usage_words": 0,
    "max_cpu_usage_ms": 0,
    "delay_sec": 0,
    "context_free_actions": [],
    "actions": [{
        "account": "eosio.token",
        "name": "transfer",
        "authorization": [{
            "actor": "popo11111111",
            "permission": "active"
          }
        ],
        "data": {
          "from": "popo11111111",
          "to": "cryptokylinq",
          "quantity": "10.0000 EOS",
          "memo": "test multisig"
        },
        "hex_data": "1042082184402bad60a78b1ed25cfd45a08601000000000004454f53000000000d74657374206d756c7469736967"
      }
    ],
    "transaction_extensions": []
  }
}

3.查看提案审批情况,provided_approvals为空表示尚未审批,request_approvals表示需要哪些权限进行审批
cleos get table eosio.msig <proposer> approvals

suroudeMacBook-Pro:~ surou$ cleos -u http://kylin.fn.eosbixin.com get table eosio.msig zoin11111111 approvals
{
  "rows": [{
      "proposal_name": "transferpopo",
      "requested_approvals": [{
          "actor": "zoin11111111",
          "permission": "active"
        },{
          "actor": "zoin22222222",
          "permission": "active"
        }
      ],
      "provided_approvals": []
    }
  ],
  "more": false
}

4.通过提案 cleos multisig approve 提案人 提案 权限

  • zoin11111111通过提案
    suroudeMacBook-Pro:~ surou$ cleos -u http://kylin.fn.eosbixin.com multisig approve zoin11111111 transferpopo '{"actor": "zoin11111111", "permission": "active"}'  -p zoin11111111@active
    executed transaction: bcc0495b88d7b897853af11ace59029a32f16f1727de04e4c1fa7fc85e7df1dc  128 bytes  1091 us
    #    eosio.msig <= eosio.msig::approve          {"proposer":"zoin11111111","proposal_name":"transferpopo","level":{"actor":"zoin11111111","permissio...

    查看提案审批情况

    suroudeMacBook-Pro:~ surou$ cleos -u http://kylin.fn.eosbixin.com get table eosio.msig zoin11111111 approvals
    {
    "rows": [{
        "proposal_name": "transferpopo",
        "requested_approvals": [{
            "actor": "zoin22222222",
            "permission": "active"
          }
        ],
        "provided_approvals": [{
            "actor": "zoin11111111",
            "permission": "active"
          }
        ]
      }
    ],
    "more": false
    }
  • zoin22222222通过提案
    suroudeMacBook-Pro:~ surou$ cleos -u http://kylin.fn.eosbixin.com multisig approve zoin11111111 transferpopo '{"actor": "zoin22222222", "permission": "active"}'  -p zoin22222222@active
    executed transaction: 62ef04a0ee345ac604ca9fe7be034a0cb69524b1ed146e22c44a806a03337919  128 bytes  633 us
    #    eosio.msig <= eosio.msig::approve          {"proposer":"zoin11111111","proposal_name":"transferpopo","level":{"actor":"zoin22222222","permissio...

    查看提案审批情况

    suroudeMacBook-Pro:~ surou$ cleos -u http://kylin.fn.eosbixin.com get table eosio.msig zoin11111111 approvals
    {
    "rows": [{
        "proposal_name": "transferpopo",
        "requested_approvals": [],
        "provided_approvals": [{
            "actor": "zoin11111111",
            "permission": "active"
          },{
            "actor": "zoin22222222",
            "permission": "active"
          }
        ]
      }
    ],
    "more": false
    }

5.执行提案cleos multisig exec <proposer> <proposal_name> -p 谁想执行都可以
执行前查看账户

  • cryptokylinq 余额4,148.3135 EOS
  • popo11111111 余额 10 EOS
suroudeMacBook-Pro:~ surou$ cleos -u http://kylin.fn.eosbixin.com multisig exec zoin11111111 transferpopo -p zoin11111111
executed transaction: caaa900536463207713f6af031ee315125d993f0af56ea91aafff3dcdf8b1353  160 bytes  1098 us
#    eosio.msig <= eosio.msig::exec             {"proposer":"zoin11111111","proposal_name":"transferpopo","executer":"zoin11111111"}

执行完成后查看账户

  • cryptokylinq 余额4,158.3135 EOS
  • popo11111111 余额 0 EOS

且提案将被自动删除

suroudeMacBook-Pro:~ surou$ cleos -u http://kylin.fn.eosbixin.com get table eosio.msig zoin11111111 approvals
{
  "rows": [],
  "more": false
}

并注意到,该笔转账没有交易记录actions 额,是不是在某些情况,需要隐藏交易记录时,可以通过此方法。。。


使用场景

某个账户发起一个提案后,需事先明确指定哪些账户/权限,必须全部通过审核后,合约动作才能被执行。

常见问题

  • 提案操作很消耗CPU Error 3080004: Transaction exceeded the current CPU usage limit imposed on the transaction

    cleos -u http://kylin.fn.eosbixin.com system delegatebw cryptokylinq zoin11111111 "0 EOS" "10 EOS"
  • 执行提案时,可能出现下面未知错误

    需要部署eosio.system系统合约,并开启提案合约的功能即可解决(具体原理会在介绍eos.system合约的时候介绍)

    cleos push action eosio setpriv '["eosio.msig", 1]' -p eosio

    后记

    本篇为大家介绍了eos的账户权限,以及eosio.msig提案合约如何通过cleos multisig调用使用,如何发起提案、审批提案等。别走开,下一篇将深入源码,分析eosio.msig提案合约是怎么写的。
    参考自简书

Error 3050003: eosio_assert_message assertion failure

创建账号时Error 3050003

suroudeMacBook-Pro:~ surou$ cleos -u http://kylin.fn.eosbixin.com system newaccount cryptokylinq popo EOS81WjiHefR6c5VKYjvdSat68RDJ3qGPgCckJqG3pXbDbVjZR5QB  EOS7ob62mdBfiRt1a8tQrQGbhnU1gEAvWyQDFHs99HzYEh8wxeQ9T  --stake-net '0.1 EOS' --stake-cpu '0.1 EOS'  --buy-ram-kbytes 4000
Error 3050003: eosio_assert_message assertion failure

如果nodeos启动时开始错误显示

nodeos --verbose-http-errors
Error 3050003: eosio_assert_message assertion failure
Error Details:
assertion failure with message: no active bid for name

根据Details就能看出,由于创建的账户名不属于常规的账户名“1-5,a-z”12位。是需要事先投标购买成功的。

DApp接入最新版本Scatter.js支持,以及ScatterDesktop的使用

我们知道目前EOS DApp运行,目前分为两种方式

  • App提供DApp运行环境。App内置浏览器控件,注入js,DApp运行时用注入的js与App内置的钱包交互,DApp需要私钥签名时,将需要签名的数据通过注入的js将数据传给外部的钱包,使用钱包当前选中的私钥将数据签名,然后将数据返回给DApp。
  • 浏览器扩展提供支持。目前支持EOS链的扩展主要是以Scatter,或者基于此二次开发为主。

本篇我们主要讲下DApp对Scatter的接入,大多的App提供的运行环境也是支持Scatter的,比如tp钱包等,并且Scatter在EOS链的绝对地位(不低于Metamask对于以太坊),所以我们开发DApp最少要支持Scatter的支持。

先来个主角Scatter介绍

Scatter 是一个去中心化的签名、身份、私人数据与身份验证解决方案。它在用户的计算机本地运行,采用非对称加密技术,令用户可以在不访问集中式服务器的情况下通过网站进行身份验证。Scatter 主要包含两个部分:钱包与 RIDL (Reputation & Identity Layer) 系统。

Scatter 钱包目前支持 EOS 和 ETH 钱包,未来会支持更多钱包。Scatter 已推出 Chrome 浏览器插件版本和桌面 Beta 版本,之后会推出移动端版本,其中浏览器插件版与以太坊轻钱包 MetaMask 类似。Scatter 钱包作为 EOS 应用程序与用户信息之间的桥梁,能让用户安全地向应用程序签名和提供私人数据,并保证在与EOS 区块链通信的 Web 应用程序进行交互时不会暴露密钥和其他不必要的信息。Scatter 不需要用户提交私钥,只需对交易签名,它是本地的应用,不会给用户发送需要解密或加密私钥的请求。而用户如果使用直接手动将私钥输入到应用程序的网页表单中,会存在资产被盗取的严重风险。

Scatter 的 RIDL (Reputation & Identity Layer) 系统包含了声望与身份系统。用户可以创建多个身份,每个身份都包含用户身份名称、EOS 账户/密钥对、姓名、电话和地址等个人信息。用户只需设置一次身份, Dapp 就可以经过用户授权后从 Scatter 接入用户身份信息。因此用户不必在多个 Dapp 重复填写个人信息,并避免个人信息泄漏。而在 Scatter 声望系统当中,用户和 Dapp 可以互相对彼此的声望进行评分。这一系统允许应用程序评估用户做出某种行为的可能性,比如偿还贷款。反之,它也允许用户衡量应用程序的质量,例如安全性与可用性。

Scatter 项目代码

现在Scatter 分为ScatterWebExtension(浏览器扩展)和ScatterDesktop(桌面客户端)两个版本。

编写一个支持Scatter的DApp例子

//TODO

ScatterDesktop使用步骤,并与上面例子交互

//TODO

中秋快乐,稍后继续完善本文。节后会紧跟几篇,详细针对Scatter接口方面的讲解。

EOSIO cleos命令汇总

version返回版本信息

返回EOS客户端版本信息

$cleos version client
Build version: cc9decff

create在区块链上和区域外创建数据

create key 创建密钥

命令格式:$ cleos create key
为了创建一个帐户,你需要两个新的密钥:所有者和激活密钥。你可以要求cleos为你创建一些钥匙:
这将是你的Owner Key,

$ cleos create key --to-console
Private key: 5HtimtY36YN8mB5MRKh2FDkdtUg98dZ5HrnC9hhCkf5KsZvXR11
Public key: EOS86qf6z2CuNfYDADMMysy2Gfs6p3GVK5k71BbGovmtmkBwvMxPq

再次执行,生成active key

$ cleos create key --to-console
Private key: 5K4SVxGkSrUWJVSsxtyJx7nqwVoNwgEZ77KxwVXaB2yzCf2ri3q
Public key: EOS5aC4xP3rf1sJVfNudJaa4CikUpmAYqXuiCxfL8nQT3tFDys9X1

注意:cleos不会把生成的私钥自动导入钱包,需手动导入cleos wallet import

create account 创建账号

命令格式:$ cleos create account ${control_account} ${account_name} ${owner_public_key} ${active_public_key}

Usage: ./cleos create account [OPTIONS] creator name OwnerKey ActiveKey

Positionals:
  creator TEXT                创建新帐户的帐户的名称
  name TEXT                   新帐户的名称
  OwnerKey TEXT               新帐户的所有者公钥
  ActiveKey TEXT              新帐户的激活公钥

Options:
  -x,--expiration             在交易到期之前以秒为单位设置时间,默认值为30s。
  -f,--force-unique           强制使交易独一无二,这将消耗额外的带宽,并消除对多次发布同一交易的任何保护。
  -s,--skip-sign              指定未锁定钱包密钥是否用于签署交易。即跳过签署直接交易。
  -d,--dont-broadcast         不要向网络广播交易(只需打印到stdout)。
  -p,--permission TEXT ...    授权的帐户和权限级别,如“帐户@权限”(默认值'creator@active')

待续