1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

[翻訳]EOS ソースコード解析 マルチスレッドを使ってサインからパブリックキーを生成する

Last updated at Posted at 2018-12-07

本記事は、本人の許可を得て翻訳する内容です。
翻訳元:EOS LIVE - 发现 EOS 最热门产品及最新优质资讯

昨日の朝(訳者:2018/12/05) EOS 1.5.0 リリースされました。このバージョンの大きい変更点は、マルチスレッドサインのところです。ブロック同期する際の block サイン検証と trx サイン検証処理をマルチスレッドにすることで、ブロック同期の時間を短縮しました。ただ、ブロック生成のコストは変わっていません。何故でしょうか。具体的な変更点を紹介します。

マルチスレッドサイン検証の変更

ブロック同期する際、マルチスレッドでサインしますが、replay の処理ではまだシングルスレッドのままです。それは、ブロック同期する際は pending blocktrx 操作をロールバックする必要があるからです。この時間は、並列でサインを処理することができますが、 replay の時のこのステップがないので、マルチスレッドでサインしても時間を短縮できませんし、メインスレッドが非同期処理結果を待たないと行けません。

trx マルチサイン変更

ブロックの同期および replay 処理ともマルチスレッドでサインするようになります。複数の trx を実行するので、並列実行出来ます。ただ、ブロック生成する時は使えません。BP がブロードキャストされた trx を受信するとすぐ実行し、実行完了してから次の trx を受信するようにしているので、マルチスレッドを使えません。

コード解析

ブロックサイン

replay にマルチスレッドを適用できないため、replay は今までのサインコードを流用しています。同期は新しい処理を使ってます。

// 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 時間は減りません。

EOS LIVE DApp 開発者コミュニティ

QRコードは 7 日以内(12 月 13 日までに)有効

1
0
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?