资讯专栏INFORMATION COLUMN

EOS 源码解析 什么是 read only 模式

ernest.wang / 3513人阅读

摘要:接下来我们来分析下为什么会出现这种情况,以及模式起到了什么作用。没错,模式的作用就是禁止途径。模式不接受广播交易接受交易,并执行如果是模式即中断嗯,问题来了,如何开启模式呢。

  大家之前使用 mongodb_plugin 、mysql_plugin 或其他数据持久化插件的时候,可能会发现 transaction 和 trace 的数据重复duplicate ( 多机环境下)。 在最初的时候只能在持久化的时候做去重处理,但 EOS 之后已经推出了 read only 模式,可以避免数据出现 duplicate 的情况, 但笔者发现很多人不知道有这种模式,也不清楚这种情况的发生由来。接下来我们来分析下为什么会出现这种情况,以及 read only 模式起到了什么作用。

首先 read only 模式不能用于出块节点,所以我们以一个同步节点的立场来讲述。
写一个持久化插件,我们必须要有数据源,也就是这几个信号,我们从这里获取数据,这里使用的是观察者模式,每当信号源有新数据 emit 的时候就会调用我们定义的函数,具体观察者模式的实现在这里就不描述了,参考 mongodb_plugin 代码。
signal         pre_accepted_block;
signal          accepted_block_header;
signal          accepted_block;
signal          irreversible_block;
signal accepted_transaction;
signal    applied_transaction;
signal      accepted_confirmation;

出现重复的会是 accepted_transaction 和 applied_transaction 这个信号源,所以我们重点介绍它。

我们会在 controller.push_transaction 发现这两个函数的触发。

transaction_trace_ptr push_transaction( const transaction_metadata_ptr& trx,
                                        fc::time_point deadline,
                                        uint32_t billed_cpu_time_us,
                                        bool explicit_billed_cpu_time = false )
{
   // ...

         // call the accept signal but only once for this transaction
         if (!trx->accepted) {
            trx->accepted = true;
            emit( self.accepted_transaction, trx);
         }

         emit(self.applied_transaction, trace);

   // ...
} /// push_transaction

OK, 看到这一步我们就知道 push_transaction 执行了 2 次同样的 trx 才会导致这2个信号 duplicate。

为什么会执行 2 次呢?

trx 是通过什么来广播的呢, 块广播以及交易广播, 那我们从这入手。

交易广播
每个节点会接受全网上的交易,尝试执行, 如果成功,则他继续向其他节点广播这个交易

// net_plugin.cpp
void net_plugin_impl::handle_message( connection_ptr c, const packed_transaction &msg) {
   // ...

   // read only 模式不接受广播交易
   if( cc.get_read_mode() == eosio::db_read_mode::READ_ONLY ) {
      fc_dlog(logger, "got a txn in read-only mode - dropping");
      return;
   }
   
   // ...

   // 接受交易, 并执行 push  transaction
   spatcher->recv_transaction(c, tid);
   chain_plug->accept_transaction(msg, [=](const static_variant& result) {
      if (result.contains()) {
         peer_dlog(c, "bad packed_transaction : ${m}", ("m",result.get()->what()));
      } else {
         auto trace = result.get();
         if (!trace->except) {
            fc_dlog(logger, "chain accepted transaction");
            dispatcher->bcast_transaction(msg);
            return;
         }

         peer_elog(c, "bad packed_transaction : ${m}", ("m",trace->except->what()));
      }

      dispatcher->rejected_transaction(tid);
   });
}

// chain plugin.cpp
void chain_plugin::accept_transaction(const chain::packed_transaction& trx, next_function next) {
   // 相当于往该节点 push transaction
   my->incoming_transaction_async_method(std::make_shared(trx), false, std::forward(next));
}

第一次 push_transaction 的执行找到啦。

块广播
接下来看块广播, 在网络上广播的交易,最终是会被出块节点打包( 执行失败的例外),每个节点都要去同步块, 接受一个打包好的区块,执行 apply_block 函数。

void apply_block( const signed_block_ptr& b, controller::block_status s ) { try {
   try {
      // ...

      // 多线程签名

      // ...

      transaction_trace_ptr trace;

      size_t packed_idx = 0;
      // 执行块上的交易,更新该节点的状态
      for( const auto& receipt : b->transactions ) {
         auto num_pending_receipts = pending->_pending_block_state->block->transactions.size();
         if( receipt.trx.contains() ) {
            trace = push_transaction( packed_transactions.at(packed_idx++), fc::time_point::maximum(), receipt.cpu_usage_us, true );
         } else if( receipt.trx.contains() ) {
            trace = push_scheduled_transaction( receipt.trx.get(), fc::time_point::maximum(), receipt.cpu_usage_us, true );
         } else {
            EOS_ASSERT( false, block_validate_exception, "encountered unexpected receipt type" );
         }

         // ...
      }

      //...
      return;
   } catch ( const fc::exception& e ) {
      edump((e.to_detail_string()));
      abort_block();
      throw;
   }
} FC_CAPTURE_AND_RETHROW() } /// apply_block

第二次执行 push transaction 也找到啦。

也就是一个 trx 在传播到该节点的时候会被执行一次,trx 被打包后跟随区块到该节点又会被执行一次, 这就造成 accepted_transaction 和 applied_transaction 这两个信号重复,导致重复数据的产生。

解决问题
问题找到了,接下来解决问题。

出现两次调用 push_transaction 的操作,那么肯定要禁掉其中一个,才会使信号只触发一次,那同步区块的步骤肯定不能禁掉, 块广播和交易广播,我们只能选择禁止交易广播的执行,所以为什么出块节点不能用 read only 模式( ps: 交易广播都被你禁掉了,我还怎么打包区块???黑人问号脸)

交易广播有 2 个途径一个是接受链上的交易传播, 一个是通过 chain_api_plugin 的 push_transaction API 推送,所以禁掉这两个就可以了。没错, read only 模式的作用就是禁止 2 途径。

// net_plugin.cpp
void net_plugin_impl::handle_message( connection_ptr c, const packed_transaction &msg) {
   // ...

   // read only 模式不接受广播交易
   if( cc.get_read_mode() == eosio::db_read_mode::READ_ONLY ) {
      fc_dlog(logger, "got a txn in read-only mode - dropping");
      return;
   }
   
   // ...

   // 接受交易, 并执行 push  transaction
   spatcher->recv_transaction(c, tid);
   chain_plug->accept_transaction(msg, [=](const static_variant& result) {
      if (result.contains()) {
         peer_dlog(c, "bad packed_transaction : ${m}", ("m",result.get()->what()));
      } else {
         auto trace = result.get();
         if (!trace->except) {
            fc_dlog(logger, "chain accepted transaction");
            dispatcher->bcast_transaction(msg);
            return;
         }

         peer_elog(c, "bad packed_transaction : ${m}", ("m",trace->except->what()));
      }

      dispatcher->rejected_transaction(tid);
   });
}

// controller.cpp
transaction_trace_ptr controller::push_transaction( const transaction_metadata_ptr& trx, fc::time_point deadline, uint32_t billed_cpu_time_us ) {
   validate_db_available_size();
   // 如果是 read only 模式即中断
   EOS_ASSERT( get_read_mode() != chain::db_read_mode::READ_ONLY, transaction_type_exception, "push transaction not allowed in read-only mode" );
   EOS_ASSERT( trx && !trx->implicit && !trx->scheduled, transaction_type_exception, "Implicit/Scheduled transaction not allowed" );
   return my->push_transaction(trx, deadline, billed_cpu_time_us, billed_cpu_time_us > 0 );
}

嗯,问题来了,如何开启 read only 模式呢。

很简单,在config.ini 加上read-mode = read-only 即可。

总结:

accepted_transaction 和 applied_transaction 信号重复的原因在于 trx 被执行了两次,即块广播与交易广播,所以禁止交易广播即可, 但此时节点只供读取数据,不能写入数据。所以如果节点要来提供 push_transaction 这个 http api 的话不能开启此模式。
trx 通过交易广播在非出块节点执行是为了验证该 trx 是否能合法执行,如果不能,则该节点不会向网络传播该交易
为什么单机模式不会出现信号重复,因为单机节点只有一个,不会出现块传播,只有交易传播。
如果你要写持久化插件,记得开启 read only 模式,或者在持久化的时候去重。

有任何疑问或者想交流的朋友可以加 EOS LIVE 小助手,备注 eos开发者拉您进 EOS LIVE DAPP 开发者社区微信群哦。

转载请注明来源:https://eos.live/detail/18718

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/24489.html

相关文章

  • EOS源码解析 使用多线程从签名生成对应公钥

    摘要:区块多线程签名改动同步区块时进行多线程签名,过程中依然是单线程签名。代码解析块签名因为不适用多线程签名,所以依旧沿用之前的签名代码,而同步则使用了新的部分。当大家比较关注的使用并没有得到改善,因为多线程签名无法应该在生产区块上。 昨天早上,EOS 1.5.0 release 版本发布了。这次比较大改动点是在多线程签名上面。它将同步区块时的 block 签名验证和 trx 签名验证都使用...

    mist14 评论0 收藏0
  • EOS源码解析 创建账号的三种方式。

    摘要:第一种创建系统账号的方式。的默认合约是来自源码提前定义好的。具体的信息在,。判断的合法性只有才能创建为前缀的账号。第三种当部署合约时,创建账号都必须使用该合约的的。值得一提的是用第三种方式创建时,第二种方式的也会执行。 第一种:创建系统账号eosio的方式。 直接调用create_native_account 方法直接进行创建。并将资源设置成无限。 void create_nat...

    Me_Kun 评论0 收藏0
  • EOS 源码解析 区块回滚对交易的影响

    摘要:在主网上玩耍的小伙伴们肯定遇到过区块回滚导致自己的交易没有上链。这种情况让有些人误以为区块回滚会丢弃交易。其实区块回滚并不是导致交易没上链的主要原因,主要原因是交易过期了才导致交易被丢弃。源码解析我们来看看区块生产时是如何丢弃过期交易的。     在主网上玩耍的小伙伴们肯定遇到过区块回滚导致自己的交易没有上链。这种情况让有些人误以为区块回滚会丢弃交易。 其实区块回滚并不是导致交易没上链...

    diabloneo 评论0 收藏0

发表评论

0条评论

ernest.wang

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<