EOS 源碼解析 區塊回滾對交易的影響

    在主網上玩耍的小夥伴們確定遇到過區塊回滾致使本身的交易沒有上鍊。這種狀況讓有些人誤覺得區塊回滾會丟棄交易。 其實區塊回滾並非致使交易沒上鍊的主要緣由, 主要緣由是交易過時了才致使交易被丟棄。node

流程描述:

每一個交易是會被廣播到全網每一個節點上面的( ps: 固然傳播過程當中過時的話,當我沒說哈 ),假如出塊節點 A 打包了 trx a, 但此時出塊節點 B 沒接受到 A 的打包塊,他也開始打包了,那麼他也包含了該 trx a 並會將他打包( ps: 固然也有例外狀況,那就是 出塊節點 B 接收到 trx a 時,他就過時了,因此還沒打包就丟棄他,或者還沒傳遞到出塊節點 B, 但會抵達下個節點 C D E, 但狀況類似,就不另外說明了 )。但若是 a 的過時時間設置太短,致使出塊節點 B 打包時發現他過時了,就會丟棄他。 這即是交易沒上鍊的緣由。

源碼解析:

咱們來看看區塊生產時是如何丟棄過時交易的。區塊生產的流程 區塊的分叉處理能夠看下我以前的文章。這裏生產區塊以及回滾的細節就不贅述了。

區塊打包時,會將 pending block 裏已經執行成功了的 trx 另外存起來, 並初始化 pending block。 等到打包的時候會再去執行一次這些 trx , 咦,爲何要從新執行一遍浪費資源,由於這些 trx 都是在區塊打包以前執行的,鬼知道過時了沒有 =,=。微信

如下是交易被殘忍丟棄的過程:app

// controller.cpp
// 將 pending block 的 trx 保存到 unapplied_transaction
void abort_block() {
if( pending ) {this

if ( read_mode == db_read_mode::SPECULATIVE ) {
     for( const auto& t : pending->_pending_block_state->trxs )
        unapplied_transactions[t->signed_id] = t;
  }
  pending.reset();

}
}code

// producer_plugin.cpp
producer_plugin_impl::start_block_result producer_plugin_impl::start_block(bool &last_block) {
// ...圖片

try {ci

// ...

  // 將 pending block 裏面的 trx 保存了 unapplied_transaction
  chain.abort_block();
  // 初始化 pending block
  chain.start_block(block_time, blocks_to_confirm);

} FC_LOG_AND_DROP();資源

const auto& pbs = chain.pending_block_state();
if (pbs) {開發

// ...


  // _persistent_transactions 是指經過該節點的 http 端口 push_transaction 推送過來的 trx
  // remove all persisted transactions that have now expired
  auto& persisted_by_id = _persistent_transactions.get<by_id>();
  auto& persisted_by_expiry = _persistent_transactions.get<by_expiry>();
  if (!persisted_by_expiry.empty()) {
     int num_expired_persistent = 0;
     int orig_count = _persistent_transactions.size();

     // 丟棄過時交易
     while(!persisted_by_expiry.empty() && persisted_by_expiry.begin()->expiry <= pbs->header.timestamp.to_time_point()) {
        auto const& txid = persisted_by_expiry.begin()->trx_id;
        if (_pending_block_mode == pending_block_mode::producing) {
           fc_dlog(_trx_trace_log, "[TRX_TRACE] Block ${block_num} for producer ${prod} is EXPIRING PERSISTED tx: ${txid}",
                   ("block_num", chain.head_block_num() + 1)
                   ("prod", chain.pending_block_state()->header.producer)
                   ("txid", txid));
        } else {
           fc_dlog(_trx_trace_log, "[TRX_TRACE] Speculative execution is EXPIRING PERSISTED tx: ${txid}",
                   ("txid", txid));
        }

        persisted_by_expiry.erase(persisted_by_expiry.begin());
        num_expired_persistent++;
     }

     fc_dlog(_log, "Processed ${n} persisted transactions, Expired ${expired}",
            ("n", orig_count)
            ("expired", num_expired_persistent));
  }

  try {
     size_t orig_pending_txn_size = _pending_incoming_transactions.size();

     // Processing unapplied transactions...
     // 當節點不是 出塊節點而且 也沒有經過該節點推送的 trx, 則 unapplied_transaction 無心義
     // 由於你不須要打包區塊, 也沒有 trx 須要廣播出去。
     if (_producers.empty() && persisted_by_id.empty()) {
        // if this node can never produce and has no persisted transactions,
        // there is no need for unapplied transactions they can be dropped
        chain.drop_all_unapplied_transactions();
     } else {
        std::vector<transaction_metadata_ptr> apply_trxs;
        { // derive appliable transactions from unapplied_transactions and drop droppable transactions
           auto unapplied_trxs = chain.get_unapplied_transactions();
           apply_trxs.reserve(unapplied_trxs.size());

           auto calculate_transaction_category = [&](const transaction_metadata_ptr& trx) {
              if (trx->packed_trx.expiration() < pbs->header.timestamp.to_time_point()) {
                 return tx_category::EXPIRED;
              } else if (persisted_by_id.find(trx->id) != persisted_by_id.end()) {
                 return tx_category::PERSISTED;
              } else {
                 return tx_category::UNEXPIRED_UNPERSISTED;
              }
           };


           // 將沒過時的放進 apply_trxs, 過時的丟棄掉。
           for (auto& trx: unapplied_trxs) {
              auto category = calculate_transaction_category(trx);
              if (category == tx_category::EXPIRED || (category == tx_category::UNEXPIRED_UNPERSISTED && _producers.empty())) {
                 if (!_producers.empty()) {
                    fc_dlog(_trx_trace_log, "[TRX_TRACE] Node with producers configured is dropping an EXPIRED transaction that was PREVIOUSLY ACCEPTED : ${txid}",
                           ("txid", trx->id));
                 }
                 chain.drop_unapplied_transaction(trx);
              } else if (category == tx_category::PERSISTED || (category == tx_category::UNEXPIRED_UNPERSISTED && _pending_block_mode == pending_block_mode::producing)) {
                 apply_trxs.emplace_back(std::move(trx));
              }
           }
        }

        if (!apply_trxs.empty()) {
           // 執行 trx, 成功的 emplace_back 進 pending block
        }
     }

     // ...
     // 執行 deffered transaction
     // 和執行 初始化 pending block 時推送進來的 transcation ( 由於初始化時,pending block 不能存 trx, 因此先另外存起來)

}rem

return start_block_result::failed;
}

總結:

回滾並不會丟棄 trx, 只會致使 trx 延後打包,以至於 trx 可能過時被丟棄。
設置過時時間時,時間跨度應該足夠 2 個 BP 出完塊,這樣即便 B 沒接收到 A 的區塊,但 trx 不會由於過時而被 B 丟棄,固然還要大體估算你的 trx 廣播到出塊節點的時間。

有任何疑問或者想交流的朋友能夠加 EOS LIVE 小助手,備註 eos開發者拉您進 EOS LIVE DAPP 開發者社區微信羣哦。
圖片描述

原文連接:https://eos.live/detail/18931

相關文章
相關標籤/搜索