EOS跨链转账

阅读本文前,您需要熟悉eos节点的操作流程,能够使用客户端cleos完成授权、转账等功能,并且能够完成自定义合约的开发,此外,需要您对EOS源码有着一定的理解,能够简单地对EOS源码进行修改。
操作系统:MAC OS 10.13.x,EOSIO版本号:1.1.3

1 概述

    EOS主网已经启动两个月了,虽然EOS较比特币、以太坊等公链来说其TPS得到了显著的提升,但是EOS也存在一些问题,诸如资源价格昂贵、合约开销大等,为降低EOS主网的压力,我们可以通过部署侧链的方式来解决这些问题。

    EOS侧链是指一条与EOS主网不同chain_id的链,同样采用EOS的去中心化设计理念,并对于EOS目前所存在的问题进行了一些优化。为使得EOS主网资产能够与EOS侧链资产进行相互的转移,首先解决主网与侧链间的跨链转账问题,本文主要介绍了一种将主链账号A的EOS转账至侧链账号B的过程,侧链B账号向主链A账号转账同理。

名词解释:
(1) 主链/主网:由block one与2018年6月2日启动的EOS网络,跨链操作时,我们无法去更改主链的代码,只能在上面部署我们的合约;
(2) 侧链:自发启动的非主链eos网络;
(3) 同步链:用与同步主网块的eos节点,我们可以使用修改过的程序
(4) 主链账号:主网上的用户账号
(5) 侧链账号:侧链上的用户账号
(6) 合约账号: 用于部署合约的账号,对此账号的操作需要侧链见证人的多重签名

2 主链转账合约

    首先,我们需要在主链上部署一条专供向跨链转账的合约,合约应能接收以下3个参数:主链转账发起账号A,侧链接收账号B,转账金额。通过此合约,我们将账户A的资产转移至主链的合约账号M。合约具体实现参考《EOS智能合约开发及授权
(1) 将转账合约部署至合约账号M(本次实验中使用cactus作为合约账号)

cleos set contract cactus ~/cactus/cactus_eos/MainChain/contracts/cactus.token -p cactus

(2) 用户需要使用该合约转账时,需要将自身账号的active权限授予合约账号的eosio.code权限

cleos set account permission zhd active '{"threshold" : 1, "keys" : [{"key":"EOS8GhCnJCnHFDUQhTmfAVun9AfKWp6Puye45uMJZNjYFeDCkc6N1","weight":1}], "accounts" : [{"permission":{"actor":"cactus","permission":"eosio.code"},"weight":1}]}' owner -p zhd@owner

 

3 通过多重签名转移侧链资产

出于跨链转账的安全性以及区块链去中心化考虑,侧链应选举出若干名“见证人”,当见证人接收到主链向侧链的转账时,需要向侧链对改交易进行签名,只有当一笔交易的签名数量达到见证人总数的三分之二以上时,侧链才能够转出侧链合约账号M'的资产给侧链接收账号B。

3.1 自定义多重签名转账合约

  • 定义multi_index,使用表mtrans存储待验证转账的签名
//@abi table mtrans i64
struct mtrans {
    uint64_t id;
    checksum256 trx_id;
    account_name to;
    asset quantity;
    vector<account_name> confirmed;

    uint64_t primary_key() const { return id; }

    key256 by_trx_id() const { return get_trx_id(trx_id); }

    static key256 get_trx_id(const checksum256 &trx_id) {
       const uint64_t *p64 = reinterpret_cast<const uint64_t *>(&trx_id);
       return key256::make_from_word_sequence<uint64_t>(p64[0], p64[1], p64[2], p64[3]);
    }

    EOSLIB_SERIALIZE(mtrans, (id)(trx_id)(to)(quantity)(confirmed))
};

typedef eosio::multi_index<N(mtrans), mtrans,
        indexed_by<N(trx_id), const_mem_fun<mtrans, key256, &mtrans::by_trx_id> >
> mtran_index;
  • 合约中需要维护见证人列表
// 见证人可以由投票等方式选出,这里不做具体说明,直接在代码中给出固定值
set<account_name> witness_set = {N(shengfeng), N(yuanchao), N(haonan)};
uint32_t wits_required_confs = (uint32_t) (witness_set.size() * 2 / 3) + 1;
  • 用于接收见证人签名的action
/// @abi action
/**
 * @param user 见证人账号
 * @param trx_id 转账交易id
 * @param to 转账对象
 * @param quantity 转账资产数
 */
void msigtrans(account_name user, checksum256 &trx_id, account_name to, asset quantity) {
   eosio_assert(witness_set.count(user) > 0, "user is not witness"); // 验证是否为见证人
   require_auth(user); // 验证此见证人的签名

   auto idx = mtranses.template get_index<N(trx_id)>();
   auto curr_msig = idx.find(mtrans::get_trx_id(trx_id));

   if (curr_msig == idx.end()) {
      mtranses.emplace(_self, [&](auto &a) {
          a.id = mtranses.available_primary_key();
          a.trx_id = trx_id;
          a.to = to;
          a.quantity = quantity;
          a.confirmed.push_back(user);
      });
   } else {
      eosio_assert(curr_msig->to == to, "to account is not correct");
      eosio_assert(curr_msig->quantity == quantity, "quantity is not correct");
      eosio_assert(curr_msig->confirmed.size() < wits_required_confs, "transaction already excused");
      eosio_assert(std::find(curr_msig->confirmed.begin(), curr_msig->confirmed.end(), user)
                   == curr_msig->confirmed.end(), "transaction already confirmed by this account");

      idx.modify(curr_msig, 0, [&](auto &a) {
          a.confirmed.push_back(user); // 表示此见证人已认可此交易
      });

      // 达到2/3+1的签名时发起inline_action转账
      if (curr_msig->confirmed.size() == wits_required_confs) {
         action(
                 permission_level{_self, N(active)},
                 N(eosio.token), N(transfer),
                 std::make_tuple(_self, to, quantity, std::string("cactus transfer"))
         ).send();
      }
   }
}

3.2 给合约账号授权

为使得合约账号能执行inline_action的转账交易,我们需要将合约账号的active权限授予合约的eosio.code权限。安全起见,我们还应删除合约账号的active(以及owner)权限。

./cleos set account permission cactus active '{"threshold":1,"keys":[],"accounts":[{"permission":{"actor":"cactus","permission":"eosio.code"},"weight":1}],"waits":[]}' owner -p cactus@owner

 

4 同步主链的转账交易

由于我们无法去修改EOS主链的代码逻辑,且我们部署的合约发生转账动作时,主网也无法向我们发送消息,为了能够知晓EOS主链上发生了交易,我们应开启一个EOS节点去同步EOS的主链,并且将不可撤销块的转账交易转发给侧链

4.1 同步链增加sync_plugin,并修改controller代码逻辑

我们需要为同步链添加新的plugin以处理特定的合约action,并修改controller部分代码来使用我们的plugin,具体过程如下:

(1) 修改libraries/chain/include/eosio/chain/controller.hpp代码,添加sync_block_transaction信号

//controller.hpp
signal<void(const signed_block_ptr&)>         pre_accepted_block;
signal<void(const block_state_ptr&)>          accepted_block_header;
signal<void(const block_state_ptr&)>          accepted_block;
signal<void(const block_state_ptr&)>          irreversible_block; // 需要用到的信号
signal<void(const transaction_metadata_ptr&)> accepted_transaction;
signal<void(const transaction_trace_ptr&)>    applied_transaction;
signal<void(const header_confirmation&)>      accepted_confirmation;
signal<void(const int&)>                      bad_alloc;

signal<void(const transaction_metadata_ptr&)> sync_block_transaction; // 额外添加的信号,用于处理同步块时接收到的交易

(2) 修改libraries/chain/controller.cpp代码,在apply_block中调用sync_block_transaction信号,处理同步到的块中的交易(只同步块中交易,确保交易被主链生产者打包进块)

// controller.cpp
void apply_block( const signed_block_ptr& b, controller::block_status s ) { try {
      try {
         EOS_ASSERT( b->block_extensions.size() == 0, block_validate_exception, "no supported extensions" );
         start_block( b->timestamp, b->confirmed, s );

         transaction_trace_ptr trace;

         for( const auto& receipt : b->transactions ) {
            auto num_pending_receipts = pending->_pending_block_state->block->transactions.size();
            if( receipt.trx.contains<packed_transaction>() ) {
               auto& pt = receipt.trx.get<packed_transaction>();
               auto mtrx = std::make_shared<transaction_metadata>(pt);
               trace = push_transaction( mtrx, fc::time_point::maximum(), false, receipt.cpu_usage_us);
                    if( !trace->except ) {
                        // 使用此信号 CALL sync_plugin
                        emit(self.sync_block_transaction, mtrx);
                    }
            } else if( receipt.trx.contains<transaction_id_type>() ) {
               trace = push_scheduled_transaction( receipt.trx.get<transaction_id_type>(), fc::time_point::maximum(), receipt.cpu_usage_us );
            } else {
               EOS_ASSERT( false, block_validate_exception, "encountered unexpected receipt type" );
            }

(3) 在libraries/chain/include/eosio/chain/types.hpp中添加新的object_type

enum object_type {
      null_object_type,
      account_object_type,
      account_sequence_object_type,
      /* 此处省略... */
      reversible_block_object_type,
      OBJECT_TYPE_COUNT, ///< Sentry value which contains the number of different object types
      transaction_reversible_object_type // 新添加的类型,不要忘了在文件最下面FC_REFLECT_ENUM中也要添加此类型
   };

(4) 创建sync_plugin,用于处理接收到的转账交易

  • 在plugin中注册 sync_block_transaction信号,使用accepted_transaction处理接收到的交易
my->sync_block_transaction_connection.emplace(chain.sync_block_transaction.connect( [&](const transaction_metadata_ptr& trx) {
                my->accepted_transaction(trx);
            }));
  • 判断交易中是否包含cactus合约中的transfer action,若包含,则将其记录到transaction_reversible表中
void accepted_transaction(const transaction_metadata_ptr& trx) {
   auto& chain = chain_plug->chain();
   auto& db = chain.db();
   auto block_num = chain.pending_block_state()->block_num;

   for (const auto action : trx->trx.actions) {
      if (action.account == N(cactus)
          && action.name == N(transfer)) {
         const auto* existed = db.find<transaction_reversible_object, by_trx_id>(trx->id);
         if (existed != nullptr) {
            return;
         }
         const auto transaction_reversible = db.create<transaction_reversible_object>([&](auto& tso) {
             tso.block_num = block_num;
             tso.trx_id = trx->id;
             tso.data = action.data;
         });
         break;
      } 
   }
}
  • transaction_reversible表的结构
#define DEFINE_INDEX(object_type, object_name, index_name) \
    struct object_name \
            : public chainbase::object<object_type, object_name> { \
            OBJECT_CTOR(object_name) \
            id_type id; \
            uint32_t block_num; \
            transaction_id_type trx_id; \
            action_data data; \
    }; \
    \
    struct by_block_num; \
    struct by_trx_id; \
    using index_name = chainbase::shared_multi_index_container< \
        object_name, \
        indexed_by< \
                ordered_unique<tag<by_id>, member<object_name, object_name::id_type, &object_name::id>>, \
                ordered_unique<tag<by_trx_id>, member<object_name, transaction_id_type, &object_name::trx_id>>, \
                ordered_non_unique<tag<by_block_num>, member<object_name, uint32_t, &object_name::block_num>> \
        > \
    >;
  • 在plugin中注册irreversible_block信号,使用send_transaction处理不可撤销的交易
my->irreversible_block_connection.emplace(chain.irreversible_block.connect( [&](const block_state_ptr& irb) {
                my->send_transaction(irb);
            }));
void send_transaction(const block_state_ptr& irb) {
   auto &chain = chain_plug->chain();
   auto &db = chain.db();

   const auto &trmi = db.get_index<transaction_reversible_multi_index, by_block_num>();
   auto itr = trmi.begin();
   while (itr != trmi.end()) {
      if (itr->block_num <= irb->block_num) {
         auto data = fc::raw::unpack<cactus_transfer>(itr->data);

         // send propose or approve
         // datastr = "["zhd", "longge", "100.0000 EOS"]"
         string datastr = DATA_FORMAT(_peer_chain_account, string(itr->trx_id), string(data.to), data.quantity.to_string());
         vector <string> permissions = {_peer_chain_account};
         try {
            app().find_plugin<client_plugin>()->get_client_apis().push_action(_peer_chain_address/*侧链地址*/,
                                                                              _peer_chain_constract/*侧链多签合约账号*/,
                                                                              "msigtrans", datastr, permissions);
         } catch (...) {
            wlog("send transaction failed");
         }

         db.remove(*itr);
      }
      ++itr;
   }
}

4.2 启动同步链

sync_plugin中需配置以下参数

max-irreversible-transaction-age 接收交易的时限,防止转发无效的过期交易
enable-send-propose 是否同步主链向侧链的转账交易
peer-chain-address 侧链节点的http地址
peer-chain-account 提交侧链多重签名的见证人账号
peer-chain-constract 侧链部署多重签名合约的合约账号
my-chain-constract 主链部署自定义转账合约的合约账号

代码如下

void sync_plugin::set_program_options(options_description& cli, options_description& cfg) {
        cfg.add_options()
                ("max-irreversible-transaction-age", bpo::value<int32_t>()->default_value( -1 ), "Max irreversible age of transaction to deal")
                ("enable-send-propose", bpo::bool_switch()->notifier([this](bool e){my->_send_propose_enabled = e;}), "Enable push propose.")
                ("peer-chain-address", bpo::value<string>()->default_value("http://127.0.0.1:8899/"), "In MainChain it is SideChain address, otherwise it's MainChain address")
                ("peer-chain-account", bpo::value<string>()->default_value("cactus"), "In MainChain it is your SideChain's account, otherwise it's your MainChain's account")
                ("peer-chain-constract", bpo::value<string>()->default_value("cactus"), "In MainChain it is SideChain's cactus constract, otherwise it's MainChain's cactus constract")
                ("my-chain-constract", bpo::value<string>()->default_value("cactus"), "this chain's cactus contract")
            ;
    }

在终端启动同步链命令如下,client_plugin的相关配置详见EOS跨链http通讯

nodeos --plugin eosio::sync_plugin --plugin eosio::client_plugin
--http-server-address 192.168.31.167:8889 --p2p-listen-endpoint 192.168.31.167:9878 --p2p-peer-address 192.168.31.151:9876
--config-dir nodeos2 --data-dir nodeos2
--client-private-key 5K8MzDTmBKfGWE5wDpTcpmMimHH2SzFADjmSkvJe47RWHv3nbke
--enable-send-propose --peer-chain-address http://192.168.31.144:8888 --peer-chain-account yc

 

5.发起转账

  • 执行转账前,首先我们要确保:
    (1) 主链已部署eosio.token合约(仅限测试环境,官方主链无需自己操作)
    (2) 侧链已部署eosio.token合约,可以进行转账
    (3) 主侧链上都具备转账的token类型,如SYS(EOS)

  • 主链账号zhd执行转账action,将100EOS转至侧链账号longge

cleos -u http://192.168.31.151:9876 push action cactus transfer '["zhd", "longge", "100.0000 EOS"]' -p zhd

  • 主链转账成功信息

chaochaodeMacBook-Pro:cleos yuanchao$ ./cleos -u http://192.168.31.151:9876 push action cactus transfer '["zhd", "longge", "100.0000 EOS"]' -p zhd
executed transaction: 5f7bcf984b8a7bd026b38448963a2a14ee3660a5f2b2eeddfa2e79f297ce7823 128 bytes 14472 us
# cactus <= cactus::transfer {"from":"zhd","to":"longge","quantity":"100.0000 EOS"}
# eosio.token <= eosio.token::transfer {"from":"zhd","to":"cactus","quantity":"100.0000 EOS","memo":"cactus transfer"}
# zhd <= eosio.token::transfer {"from":"zhd","to":"cactus","quantity":"100.0000 EOS","memo":"cactus transfer"}
# cactus <= eosio.token::transfer {"from":"zhd","to":"cactus","quantity":"100.0000 EOS","memo":"cactus transfer"}
warning: transaction executed locally, but may not be confirmed by the network yet ]

  • 在侧链上查看转账结果

cleos -u http://192.168.31.144:8888 get currency balance eosio.token longge

longge账号到账100EOS
经过了sf、yc两个账号的确认,执行转账成功
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,639评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,277评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,221评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,474评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,570评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,816评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,957评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,718评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,176评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,511评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,646评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,322评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,934评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,755评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,987评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,358评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,514评论 2 348