昨天早上,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