BCSkill (Block chain skill )
区块链中文技术社区

只讨论区块链底层技术
遵守一切相关法律政策!

eos linkauth unlinkauth

https://github.com/EOSIO/eos/blob/3fddb727b8f3615917707281dfd3dd3cc5d3d66d/libraries/chain/eosio_contract.cpp#L301

void apply_eosio_linkauth(apply_context& context) {
//   context.require_write_lock( config::eosio_auth_scope );

   auto requirement = context.act.data_as<linkauth>();
   try {
      EOS_ASSERT(!requirement.requirement.empty(), action_validate_exception, "Required permission cannot be empty");

      context.require_authorization(requirement.account); // only here to mark the single authority on this action as used

      auto& db = context.db;
      const auto *account = db.find<account_object, by_name>(requirement.account);
      EOS_ASSERT(account != nullptr, account_query_exception,
                 "Failed to retrieve account: ${account}", ("account", requirement.account)); // Redundant?
      const auto *code = db.find<account_object, by_name>(requirement.code);
      EOS_ASSERT(code != nullptr, account_query_exception,
                 "Failed to retrieve code for account: ${account}", ("account", requirement.code));
      if( requirement.requirement != config::eosio_any_name ) {
         const auto *permission = db.find<permission_object, by_name>(requirement.requirement);
         EOS_ASSERT(permission != nullptr, permission_query_exception,
                    "Failed to retrieve permission: ${permission}", ("permission", requirement.requirement));
      }

      auto link_key = boost::make_tuple(requirement.account, requirement.code, requirement.type);
      auto link = db.find<permission_link_object, by_action_name>(link_key);

      if( link ) {
         EOS_ASSERT(link->required_permission != requirement.requirement, action_validate_exception,
                    "Attempting to update required authority, but new requirement is same as old");
         db.modify(*link, [requirement = requirement.requirement](permission_link_object& link) {
             link.required_permission = requirement;
         });
      } else {
         const auto& l =  db.create<permission_link_object>([&requirement](permission_link_object& link) {
            link.account = requirement.account;
            link.code = requirement.code;
            link.message_type = requirement.type;
            link.required_permission = requirement.requirement;
         });

         context.add_ram_usage(
            l.account,
            (int64_t)(config::billable_size_v<permission_link_object>)
         );
      }

  } FC_CAPTURE_AND_RETHROW((requirement))
}

void apply_eosio_unlinkauth(apply_context& context) {
//   context.require_write_lock( config::eosio_auth_scope );

   auto& db = context.db;
   auto unlink = context.act.data_as<unlinkauth>();

   context.require_authorization(unlink.account); // only here to mark the single authority on this action as used

   auto link_key = boost::make_tuple(unlink.account, unlink.code, unlink.type);
   auto link = db.find<permission_link_object, by_action_name>(link_key);
   EOS_ASSERT(link != nullptr, action_validate_exception, "Attempting to unlink authority, but no link found");
   context.add_ram_usage(
      link->account,
      -(int64_t)(config::billable_size_v<permission_link_object>)
   );

   db.remove(*link);
}
auto link_key = boost::make_tuple(requirement.account, requirement.code, requirement.type);
auto link = db.find<permission_link_object, by_action_name>(link_key);

父级可以把自己已有的action执行权限link给子级,同一子级只能有一个权限有同一action执行权限,赋值给同一级其他的,会把原先的删除掉。如果子级有某个action执行权限,那父级也有。如果子级unlink了某一action执行权限,将从此级追溯到顶父级active或者owner,顶父级之前的此action执行权限都会删除掉(因为active和owner为特殊账户,不会被删除)。
因为account,code,type三个值加起来是查询索引,所以只有一条记录,记录着当前action执行权限link到哪个子权限了,并且子级有的话,父级就有。
新创建的权限,没link的话,没有任何action执行权限。

一个交易,多个签名的调研

先做笔记,后面整理更新
相同的需求如
https://eosio.stackexchange.com/questions/4012/double-sign-a-transaction-first-on-client-second-on-server?rq=1

想做的是执行action时做用户客户端以及服务端分别的账号权限验证。
推测逻辑如下

  • 由客户通过scatter签名
  • 发送到后端
  • 由服务器签名
  • 广播到网络

参考下 eosj
https://gist.github.com/adyliu/492503b94d0306371298f24e15481da4

{
  "compression" : "none",
  "transaction" : {
    "expiration" : "2018-09-28T09:28:40.5",
    "ref_block_num" : 16659166,
    "ref_block_prefix" : 4253062493,
    "max_net_usage_words" : 0,
    "max_cpu_usage_ms" : 0,
    "delay_sec" : 0,
    "context_free_actions" : [ ],
    "actions" : [ {
      "account" : "eosio.token",
      "name" : "transfer",
      "authorization" : [ {
        "actor" : "shijiebangmm",
        "permission" : "active"
      } ],
      "data" : "20259be628f75cc3104208aee1a924e5290900000000000004454f53000000002f73656e7420627920656f732073646b202868747470733a2f2f6769746875622e636f6d2f6164796c69752f6a656f73"
    } ],
    "transaction_extensions" : [ ],
    "context_free_data" : [ ]
  },
  "signatures" : [ "SIG_K1_KjY8Cs8zVdg4MNjGdJ51v252V6YVUVnhyDuDGjLuhfsuv4GQTnmu9uV59SiLMmNAAiaSmT7orb1iyZfXYHES15MFWRiBvy" ]
}

最后交易中 signatures 会有多个签名
https://github.com/adyliu/jeos/blob/53dbd027cd59d367d9a197cbff5a58bdd9bf7195/src/main/java/io/jafka/jeos/impl/LocalApiImpl.java

// ⑤ build the packed transaction
PackedTransaction packedTransaction = new PackedTransaction();
packedTransaction.setExpiration(arg.getHeadBlockTime().plusSeconds(arg.getExpiredSecond()));
packedTransaction.setRefBlockNum(arg.getLastIrreversibleBlockNum());
packedTransaction.setRefBlockPrefix(arg.getRefBlockPrefix());

packedTransaction.setMaxNetUsageWords(0);
packedTransaction.setMaxCpuUsageMs(0);
packedTransaction.setDelaySec(0);
packedTransaction.setActions(actions);

String hash = sign(privateKey, arg, packedTransaction);
PushTransactionRequest req = new PushTransactionRequest();
req.setTransaction(packedTransaction);
req.setSignatures(Arrays.asList(hash));
return req;

先解data,然后用服务端私钥签名后,将签名附加上去,

合约内增加对应的 require_auth

其他不必要的记录

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

扒扒链代码,找一下有没有相关的代码

transaction_id_type no_assert_id;
   {
      signed_transaction trx;
      trx.actions.emplace_back( vector<permission_level>{{N(asserter),config::active_name}},
                                assertdef {1, "Should Not Assert!"} );
      trx.actions[0].authorization = {{N(asserter),config::active_name}};

      set_transaction_headers(trx);
      trx.sign( get_private_key( N(asserter), "active" ), control->get_chain_id() );
      auto result = push_transaction( trx );
      BOOST_CHECK_EQUAL(result->receipt->status, transaction_receipt::executed);
      BOOST_CHECK_EQUAL(result->action_traces.size(), 1u);
      BOOST_CHECK_EQUAL(result->action_traces.at(0).receipt.receiver.to_string(),  name(N(asserter)).to_string() );
      BOOST_CHECK_EQUAL(result->action_traces.at(0).act.account.to_string(), name(N(asserter)).to_string() );
      BOOST_CHECK_EQUAL(result->action_traces.at(0).act.name.to_string(),  name(N(procassert)).to_string() );
      BOOST_CHECK_EQUAL(result->action_traces.at(0).act.authorization.size(),  1u );
      BOOST_CHECK_EQUAL(result->action_traces.at(0).act.authorization.at(0).actor.to_string(),  name(N(asserter)).to_string() );
      BOOST_CHECK_EQUAL(result->action_traces.at(0).act.authorization.at(0).permission.to_string(),  name(config::active_name).to_string() );
      no_assert_id = trx.id();
   }

https://github.com/EOSIO/eos/blob/686f0deb5dac097cc292f735ccb47c238e763de0/unittests/wasm_tests.cpp#L85

const signature_type& signed_transaction::sign(const private_key_type& key, const chain_id_type& chain_id) {
   signatures.push_back(key.sign(sig_digest(chain_id, context_free_data)));
   return signatures.back();
}

https://github.com/EOSIO/eos/blob/686f0deb5dac097cc292f735ccb47c238e763de0/libraries/chain/transaction.cpp#L138

fc::microseconds
signed_transaction::get_signature_keys( const chain_id_type& chain_id, fc::time_point deadline,
                                        flat_set<public_key_type>& recovered_pub_keys,
                                        bool allow_duplicate_keys)const
{
   return transaction::get_signature_keys(signatures, chain_id, deadline, context_free_data, recovered_pub_keys, allow_duplicate_keys);
}

https://github.com/EOSIO/eos/blob/686f0deb5dac097cc292f735ccb47c238e763de0/libraries/chain/transaction.cpp#L147

看链代码,应该是支持一个交易多个签名的,那继续跟。

那看下eosjs

/** Sign a transaction */
    public async sign({ chainId, requiredKeys, serializedTransaction }: SignatureProviderArgs) {
        const signBuf = Buffer.concat([
            new Buffer(chainId, 'hex'), new Buffer(serializedTransaction), new Buffer(new Uint8Array(32)),
        ]);
        const signatures = requiredKeys.map(
            (pub) => ecc.Signature.sign(signBuf, this.keys.get(convertLegacyPublicKey(pub))).toString(),
        );
        return { signatures, serializedTransaction };
    }

https://github.com/EOSIO/eosjs/blob/849c03992e6ce3cb4b6a11bf18ab17b62136e5c9/src/eosjs-jssig.ts#L33
也是支持多个私钥给同一个交易签名的。

 it('signs a transaction', async () => {
        const eccSignatureSign = jest.spyOn(ecc.Signature, 'sign');
        eccSignatureSign.mockImplementation((buffer, signKey) => signKey);

        const provider = new JsSignatureProvider(privateKeys);
        const chainId = '12345';
        const requiredKeys = [
            publicKeys[0],
            publicKeys[2],
        ];
        const serializedTransaction = new Uint8Array([
            0, 16, 32, 128, 255,
        ]);
        const abis: any[] = [];

        const signOutput = await provider.sign({ chainId, requiredKeys, serializedTransaction, abis });

        expect(eccSignatureSign).toHaveBeenCalledTimes(2);
        expect(signOutput).toEqual({ signatures: [privateKeys[0], privateKeys[2]], serializedTransaction });
    });

https://github.com/EOSIO/eosjs/blob/849c03992e6ce3cb4b6a11bf18ab17b62136e5c9/src/tests/eosjs-jssig.test.ts#L37

template <typename T>
transaction_trace_ptr CallAction(TESTER& test, T ac, const vector<account_name>& scope = {N(testapi)}) {
   signed_transaction trx;


   auto pl = vector<permission_level>{{scope[0], config::active_name}};
   if (scope.size() > 1)
      for (size_t i = 1; i < scope.size(); i++)
         pl.push_back({scope[i], config::active_name});

   action act(pl, ac);
   trx.actions.push_back(act);

   test.set_transaction_headers(trx);
   auto sigs = trx.sign(test.get_private_key(scope[0], "active"), test.control->get_chain_id());
   flat_set<public_key_type> keys;
   trx.get_signature_keys(test.control->get_chain_id(), fc::time_point::maximum(), keys);
   auto res = test.push_transaction(trx);
   BOOST_CHECK_EQUAL(res->receipt->status, transaction_receipt::executed);
   test.produce_block();
   return res;
}

https://github.com/EOSIO/eos/blob/686f0deb5dac097cc292f735ccb47c238e763de0/unittests/api_tests.cpp#L198

action data pack/unpack

// pack action data
   string unpacked_action_data_account_string;
   string unpacked_action_data_name_string;
   string unpacked_action_data_string;
   auto pack_action_data = convert->add_subcommand("pack_action_data", localized("From json action data to packed form"));
   pack_action_data->add_option("account", unpacked_action_data_account_string, localized("The name of the account that hosts the contract"))->required();
   pack_action_data->add_option("name", unpacked_action_data_name_string, localized("The name of the function that's called by this action"))->required();
   pack_action_data->add_option("unpacked_action_data", unpacked_action_data_string, localized("The action data expressed as json"))->required();
   pack_action_data->set_callback([&] {
      fc::variant unpacked_action_data_json;
      try {
         unpacked_action_data_json = json_from_file_or_string(unpacked_action_data_string);
      } EOS_RETHROW_EXCEPTIONS(transaction_type_exception, "Fail to parse unpacked action data JSON")
      bytes packed_action_data_string = variant_to_bin(unpacked_action_data_account_string, unpacked_action_data_name_string, unpacked_action_data_json);
      std::cout << fc::to_hex(packed_action_data_string.data(), packed_action_data_string.size()) << std::endl;
   });

   // unpack action data
   string packed_action_data_account_string;
   string packed_action_data_name_string;
   string packed_action_data_string;
   auto unpack_action_data = convert->add_subcommand("unpack_action_data", localized("From packed to json action data form"));
   unpack_action_data->add_option("account", packed_action_data_account_string, localized("The name of the account that hosts the contract"))->required();
   unpack_action_data->add_option("name", packed_action_data_name_string, localized("The name of the function that's called by this action"))->required();
   unpack_action_data->add_option("packed_action_data", packed_action_data_string, localized("The action data expressed as packed hex string"))->required();
   unpack_action_data->set_callback([&] {
      EOS_ASSERT( packed_action_data_string.size() >= 2, transaction_type_exception, "No packed_action_data found" );
      vector<char> packed_action_data_blob(packed_action_data_string.size()/2);
      fc::from_hex(packed_action_data_string, packed_action_data_blob.data(), packed_action_data_blob.size());
      fc::variant unpacked_action_data_json = bin_to_variant(packed_action_data_account_string, packed_action_data_name_string, packed_action_data_blob);
      std::cout << fc::json::to_pretty_string(unpacked_action_data_json) << std::endl;
   });

https://github.com/EOSIO/eos/blob/686f0deb5dac097cc292f735ccb47c238e763de0/programs/cleos/main.cpp#L2401

https://github.com/EOSIO/eos/blob/686f0deb5dac097cc292f735ccb47c238e763de0/programs/cleos/main.cpp#L2418

EOS 插入数据的RAM消耗

context.add_ram_usage(
    l.account,
    (int64_t)(config::billable_size_v<permission_link_object>)
);

https://github.com/EOSIO/eos/blob/3fddb727b8f3615917707281dfd3dd3cc5d3d66d/libraries/chain/eosio_contract.cpp#L340

eosio action abi 部分语义化

账号 合约 Action 语义 备注
eosio eosio.system delegatebw 抵押资源
undelegatebw 赎回资源
buyramkbytes 购买内存
sellram 卖出内存
refund 赎回退款
voteproducer 投票
claimrewards 申请奖励
bidname 竞拍账户
bidrefund 竞拍退还
newaccount 创建账户
updateauth 更新授权
deleteauth 删除授权
linkauth 链接授权
unlinkauth 取消链接授权
canceldelay 取消延迟交易
regproducer 注册生产者
unregprod 取消生产者
eosio.token eosio.token issue 代币增发
transfer 代币转账
create 创建代币