EOS源碼解析 使用多線程從簽名生成對應公鑰

昨天早上,EOS 1.5.0 release 版本發佈了。此次比較大改動點是在多線程簽名上面。它將同步區塊時的 block 簽名驗證和 trx 簽名驗證都使用多線程簽名驗證,來節省同步所須要的時間, 可是生產區塊所須要的成本是不變的,但爲何生產區塊成本不變呢。接下來介紹一下具體的改動。

 區塊多線程簽名改動:同步區塊時進行多線程簽名, replay 過程當中依然是單線程簽名。由於區塊同步時須要回滾 pending block 的 trx 操做, 這塊時間恰好能夠用來並行處理簽名, 但 replay 的時候沒有這一步,即便用多線程簽名也沒法節省時間,反而會讓主線程阻塞等待異步結果返回。

 trx 多線程簽名改動:同步區塊以及 replay 過程都會進行多線程簽名, 由於有多個 trx 要執行,因此執行 trx 的時間能夠供其餘 trx 的簽名並行進行。 但生產區塊的時候沒法使用,由於執行 BP 接受到一個 廣播的 trx 就立馬去執行了,執行完以後纔回去接受下一個廣播 trx, 因此沒法使用多線程簽名。

代碼解析:

塊簽名:

由於 replay 不適用多線程簽名, 因此 replay 依舊沿用以前的簽名代碼, 而同步則使用了新的部分。node

// producer_plugin.cpp 接受到廣播塊
void on_incoming_block(const signed_block_ptr& block) {
   // ...

   // start processing of block
   // 調用一個線程去對塊進行簽名驗證
   auto bsf = chain.create_block_state_future( block );

   // abort the pending block
   // 回滾掉 pending block 的執行 trx, 這段時間恰好能夠用來併發執行區塊簽名驗證
   chain.abort_block();

   // ...
}

// controller.cpp
std::future<block_state_ptr> create_block_state_future( const signed_block_ptr& b ) {

   //驗證區塊是否存在。
   EOS_ASSERT( b, block_validate_exception, "null block" );

   auto id = b->id();

   // no reason for a block_state if fork_db already knows about block
   auto existing = fork_db.get_block( id );
   EOS_ASSERT( !existing, fork_database_exception, "we already know about this block: ${id}", ("id", id) );

   auto prev = fork_db.get_block( b->previous );
   EOS_ASSERT( prev, unlinkable_block_exception, "unlinkable block ${id}", ("id", id)("previous", b->previous) );

   // 進行多線程簽名
   return async_thread_pool( [b, prev]() {
      const bool skip_validate_signee = false;
      return std::make_shared<block_state>( *prev, move( b ), skip_validate_signee );
   } );
}

void push_block( std::future<block_state_ptr>& block_state_future ) {
   controller::block_status s = controller::block_status::complete;
   EOS_ASSERT(!pending, block_validate_exception, "it is not valid to push a block when there is a pending block");

   auto reset_prod_light_validation = fc::make_scoped_exit([old_value=trusted_producer_light_validation, this]() {
      trusted_producer_light_validation = old_value;
   });
   try {
      // 獲取驗證結果, 當區塊驗證失敗時會拋出異常,停止 push block
      block_state_ptr new_header_state = block_state_future.get();
      auto& b = new_header_state->block;
      emit( self.pre_accepted_block, b );

      fork_db.add( new_header_state, false );

      if (conf.trusted_producers.count(b->producer)) {
         trusted_producer_light_validation = true;
      };
      emit( self.accepted_block_header, new_header_state );

      if ( read_mode != db_read_mode::IRREVERSIBLE ) {
         maybe_switch_forks( s );
      }

   } FC_LOG_AND_RETHROW( )
}

交易簽名

從改動得知,apply_block 的時候纔會啓動交易的多線程驗證簽名,而 bcast_transaction 則不會,由於並無多餘的動做能夠與驗證簽名並行。多線程

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" );
      auto producer_block_id = b->id();
      start_block( b->timestamp, b->confirmed, s , producer_block_id);

      // 按順序啓動每一個 trx 的多線程驗證簽名,生產對應公鑰
      std::vector<transaction_metadata_ptr> packed_transactions;
      packed_transactions.reserve( b->transactions.size() );
      for( const auto& receipt : b->transactions ) {
         if( receipt.trx.contains<packed_transaction>()) {
            auto& pt = receipt.trx.get<packed_transaction>();
            auto mtrx = std::make_shared<transaction_metadata>( pt );
            if( !self.skip_auth_check() ) {
               std::weak_ptr<transaction_metadata> mtrx_wp = mtrx;
               mtrx->signing_keys_future = async_thread_pool( [chain_id = this->chain_id, mtrx_wp]() {
                  auto mtrx = mtrx_wp.lock();
                  return mtrx ?
                         std::make_pair( chain_id, mtrx->trx.get_signature_keys( chain_id ) ) :
                         std::make_pair( chain_id, decltype( mtrx->trx.get_signature_keys( chain_id ) ){} );
               } );
            }
            packed_transactions.emplace_back( std::move( mtrx ) );
         }
      }

      // 執行 trx
      // ...

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

// trx 執行時獲取簽名返回的公鑰
const flat_set<public_key_type>& recover_keys( const chain_id_type& chain_id ) {
   // Unlikely for more than one chain_id to be used in one nodeos instance
   if( !signing_keys || signing_keys->first != chain_id ) {
      if( signing_keys_future.valid() ) {
         // 獲取公鑰,若是未簽名完則阻塞等待簽名完畢
         signing_keys = signing_keys_future.get();
         if( signing_keys->first == chain_id ) {
            return signing_keys->second;
         }
      }
      // 當沒開啓多線程簽名時, 直接驗證生成對應公鑰
      signing_keys = std::make_pair( chain_id, trx.get_signature_keys( chain_id ));
   }
   return signing_keys->second;
}

總結

從此次的改動能夠看出主要優化的地方是節點同步區塊的速度, 由於開啓了多線程簽名,因此在 block 驗證以及 apply_block 時節省了必定 CPU 時間, 可供其餘地方使用。 例如 EOS 如今是當線程的,因此當你進行 RPC 訪問的時候,若是涉及到數據提取,主線程的同步時會暫停的,等待你的操做結束, 這樣就會影響節點的同步,因此 get_table_rows API 纔會限制 10 ms。 如今同步所需時間減小,下降了節點既要同步數據也要提供 RPC API 的壓力。

當你們比較關注的 CPU 使用並無獲得改善, 由於多線程簽名沒法應該在生產區塊上。因此在生產區塊時, trx 執行所須要的 CPU 時間並不會減小,也就是 CPU 資源的使用並無獲得改善。 併發

EOS 開發的小夥伴有技術問題能夠進羣討論喲app

clipboard.png

相關文章
相關標籤/搜索