您正在查看: Surou 发布的文章

EOS区块结构、生产、打包、验证、存储等流程分析

Table of Content

总览

本文主要致力于EOS区块结构、生产、打包、验证、存储等流程分析,当然其他部分源码也有涉及,但在此不做详细讨论。

环境

Mac OS High Sierra 10.13.6
EOS源码版本1.2.1
CLion
CMake

预备知识

chain分析

blockchain基本数据结构

区块结构:

  1. block_header,定义在:libraries/chain/include/eosio/chain/block_header.hpp第7行
  struct block_header {
    block_timestamp_type             timestamp;            //区块产生时间
    account_name                     producer;             //区块生产者
    uint16_t                         confirmed = 1;        //dpos确认数
    block_id_type                    previous;             //前一个区块的头的hash值
    checksum256_type                 transaction_mroot;    //区块包含的transactions的merkel树根
    checksum256_type                 action_mroot;         //区块包含的actions的merkel树根,这些actions实际包含在transactions中
    uint32_t                         schedule_version = 0;
    optional<producer_schedule_type> new_producers;
    extension_type                   header_extension;
  }
  1. signed_block_header,定义在:libraries/chain/include/eosio/chain/block_header.hpp第45行
  struct signed_block_header : public block_header {
      signature_type producer_signature; //生产者的签名
  }
  1. signed_block,定义在:libraries/chain/include/eosio/chain/block.hpp 57行
  struct signed_block : public signed_block_header {
      vector<transacton_receipt> transactions; //区块包含的transactions执行后得到的回执
      extrension_type            block_extensions;
  }
  1. transaction_receipt_header,定义在:libraries/chain/include/eosio/chain/block.hpp 12行
  struct transaction_receipt_header {
      enum status_enum {
          executed   = 0,   //transaction成功执行,没有错误发生
          soft_fail  = 1,   //
          hard_fail  = 2,
          delayed    = 3,
          expired    = 4
      };

      fc::enum_type<uint8_t,status_enum> status;
      uint32_t                           cpu_usage_us;   //总CPU使用时间,单位为微秒
      fc::unsigned_int                   net_usage_words;//总网络使用量
  }
  1. transaction_receipt,定义在:libraries/chain/include/eosio/chain/block.hpp 33行
  struct transaction_receipt : public transaction_receipt_header {
    fc::static_variant<transaction_id_type,packed_transaction> trx; //已经执行过的transactions
  }
  1. transaction_header,定义在:libraries/chain/include/eosio/chain/transaction.hpp 30行
  struct transaction_header {
    time_point_sec               expiration;               //过期时间
    uint16_t                     ref_block_num = 0U;       //用于TaPos验证
    uint32_t                     ref_block_prefix = 0UL;   //用于TaPos验证
    fc::unsigned_int             max_net_usage_words = 0UL;
    uint8_t                      max_cpu_usage_ms = 0;
    fc::unsigned_int             delay_sec;
  }
  1. transaction,定义在:libraries/chain/include/eosio/chain/transaction.hpp 54行
  struct transaction : public transaction_header {
    vector<action>                 context_free_actions; //上下文无关的actions
    vector<action>                 actions;
    extension_type                 transaction_extensions;
  }
  1. signed_transaction,定义在:libraries/chain/include/eosio/chain/transaction.hpp 78行
  struct signed_transaction : public transaction {
    vector<signature>         signatures;
    vector<bytes>             context_free_data; //和context_free_action一一对应
  }
  1. packed_transaction,定义在:libraries/chain/include/eosio/chain/transaction.hpp 98行
  struct packed_transaction {
    enum compression_type {
      none  = 0,
      zlib  = 1
    }

    vector<signature_type>                  signatures;
    fc::enum_type<uint8_t,compression_type> compression;
    bytes                                   packed_context_free_data;
    bytes                                   packed_trx;
  }
  1. deferred_transaction,定义在:libraries/chain/include/eosio/chain/transaction.hpp 157行
  struct deferred_transaction : public signed_transaction {
    uint128_t                  sender_id;
    account_name               sender;
    account_name               payer;
    time_point_sec             execute_after;
  }
  1. action,定义在:libraries/chain/include/eosio/chain/action.hpp 60行
   struct action {
       account_name             account;
       action_name              name;
       vector<permission_level> authorization;
       bytes                    data;
   }
  1. pending_state,定义在:libraries/chain/controller.cpp 91行,这是区块生产过程和区块同步过程中一个非常关键的数据结构
   struct pending_state {
       maybe_session            _db_session; //数据库session,主要涉及undo,squash,push相关操作,使数据库undo_state处于正确状态
       block_state_ptr          _pending_block_state;
       vector<action_receipt>   _actions;   //transactions在执行过程中生成的action_receipt,会打包到区块中(finalize_block)
       controller::block_status _block_status;
   }
  1. block_header_state,定义在:libraries/chain/include/eosio/chain/block_header_state.hpp 11行
    这个结构定义了验证transaction所需的头部信息,以及生成一个新的block所需的信息

    struct block_header_state {
     block_id_type             id;//最近的block_id
     uint32_t                  block_num = 0;//最近的block的高度/值
     signed_block_header       header;       //最近的block header;
     uint32_t                  dpos_proposed_irreversible_blocknum = 0;//最新的被提出dpos不可逆的区块高度/值,需要dpos计算确认
     uint32_t                  dpos_irreversible_blocknum = 0;//最新的dpos不可逆区块高度/值,这个是已经确认了的
     uint32_t                  bft_irreversible_block = 0;    //bft不可逆区块高度/值
     uint32_t                  pending_schedule_lib_num;      //
     digest_type               pending_schedule_hash;
     producer_schedule_type    pending_schedule;
     producer_schedule_type    active_schedule;
     incremental_merkel        block_root_merkle;
     flat_map<account_name,uint32_t> producer_to_last_produced;
     flat_map<account_name,uint32_t> procuer_to_last_implied_irb;
     public_key_type                 block_signing_key;        //当前生产者的签名
     vector<uint8_t>                 confirm_count;
     vector<header_confirmation>     confirmations;
    }
  2. block_state,定义在:libraries/chain/include/eosio/chain/block_state.hpp 14行

    struct block_state : public block_header_state {
     signed_block_ptr              block;            //前一个block指针
     bool                          validated = false;
     bool                          in_current_chain = false;
    }
  3. 以上为EOS区块的关键数据结构,下面的分析都是围绕着以上的数据结构来进行的。数据结构之间的关系如下:

producer_plugin

producer_plugin实现了区块生产和区块同步的调用功能。
头文件定义在:plugins/producer_plugin/include/eosio/producer_plugin/producer_plugin.hpp
实现文件定义在:plugins/producer_plugin/producer_plugin.cpp

开始插件系统会调用producer_plugin::set_program_options函数进行相关程序项的设置:

  1. 生成config.ini文件(如果该文件不存在的话)
  2. 读取配置
    调用producer_plugin::plugin_initialize函数进行初始化工作:
  3. 初始化配置
  4. 设置信号函数

调用producer_plugin::plugin_start函数,主要完成的功能如下:

  1. 设置信号函数:
    my->_accepted_block_connection.emplace(chain.accepted_block.connect( [this]( const auto& bsp ){ my->on_block( bsp ); } ));
    my->_irreversible_block_connection.emplace(chain.irreversible_block.connect( [this]( const auto& bsp ){ my->on_irreversible_block( bsp->block ); } ));
  2. 获取最新的不可逆的区块号
  3. 进入生产区块的调度 producer_plugin_impl::schedule_production_loop

producer_plugin_impl::schedule_production_loop:

  1. 取消前一次的_timer操作:
    chain::controller& chain = app().get_plugin<chain_plugin>().chain();
    _timer.cancel();
    std::weak_ptr<producer_plugin_impl> weak_this = shared_from_this();
  2. 调用 result = start_block(bool &last_block),函数定义在plugins/producer_plugin/producer_plugin.cpp 882行:
    在该函数中:
  • 首先会取得chain::controller的引用chain,判断chain当前的数据库模式是否为db_read_mode::READ_ONLY,如果是则返回状态start_block_result::waiting;如果不是则将当前的_pending_block_mode设为pending_block_mode::producing:
    chain::controller& chain = app().get_plugin<chain_plugin>().chain();
    if( chain.get_read_mode() == chain::db_read_mode::READ_ONLY )
     return start_block_result::waiting;
  • 计算当前节点是否为生产节点,获取当前被调度的生产者的watermark和signature:
    last_block = ((block_timestamp_type(block_time).slot % config::producer_repetitions) == config::producer_repetitions - 1);
    const auto& scheduled_producer = hbs->get_scheduled_producer(block_time);
    auto currrent_watermark_itr = _producer_watermarks.find(scheduled_producer.producer_name);
    auto signature_provider_itr = _signature_providers.find(scheduled_producer.block_signing_key);
    auto irreversible_block_age = get_irreversible_block_age();
  • 进行一系列的条件判断:
    检查当前节点是否被允许生产、被调度的生产者是否在生产队列中等:
    if( !_production_enabled ) {
     _pending_block_mode = pending_block_mode::speculating;
    } else if( _producers.find(scheduled_producer.producer_name) == _producers.end()) {
     _pending_block_mode = pending_block_mode::speculating;
    } else if (signature_provider_itr == _signature_providers.end()) {
     elog("Not producing block because I don't have the private key for ${scheduled_key}", ("scheduled_key", scheduled_producer.block_signing_key));
     _pending_block_mode = pending_block_mode::speculating;
    } else if ( _pause_production ) {
     elog("Not producing block because production is explicitly paused");
     _pending_block_mode = pending_block_mode::speculating;
    } else if ( _max_irreversible_block_age_us.count() >= 0 && irreversible_block_age >= _max_irreversible_block_age_us ) {
     elog("Not producing block because the irreversible block is too old [age:${age}s, max:${max}s]", ("age", irreversible_block_age.count() / 1'000'000)( "max", _max_irreversible_block_age_us.count() / 1'000'000 ));
     _pending_block_mode = pending_block_mode::speculating;
    }
  • 调用controller::abort_block
  • 调用controller::start_block 这两个函数在后文详述。
  • 在调用controller::start_block之后,在controller_impl中就会生成一个全新的pending包含了最新生成的区块头部信息
    然后需要对新块进行transaction打包:
  • 清理过期的transaction:
      // 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>();
     while(!persisted_by_expiry.empty() && persisted_by_expiry.begin()->expiry <= pbs->header.timestamp.to_time_point()) {
        persisted_by_expiry.erase(persisted_by_expiry.begin());
     }
  1. 判断start_block返回值:
  • result == failed
    start pending block 失败,稍后再试.启动定时器_timer,等待50ms再次进入schedule_production_loop

     if (result == start_block_result::failed) {
     elog("Failed to start a pending block, will try again later");
     _timer.expires_from_now( boost::posix_time::microseconds( config::block_interval_us  / 10 ));
    
     // we failed to start a block, so try again later?
     //启动定时器,待会儿再试
     _timer.async_wait([weak_this,cid=++_timer_corelation_id](const boost::system::error_code& ec) {
        auto self = weak_this.lock();
        if (self && ec != boost::asio::error::operation_aborted && cid == self->_timer_corelation_id) {
           self->schedule_production_loop();
        }
     });
    }
  • result == waiting
    调用producer_plugin_impl::schedule_delayed_production

    if (result == start_block_result::waiting){
    
        //这里检查生产者队列是否为空和是否被允许生产
     if (!_producers.empty() && !production_disabled_by_policy()) {
        fc_dlog(_log, "Waiting till another block is received and scheduling Speculative/Production Change");
        schedule_delayed_production_loop(weak_this, calculate_pending_block_time());
     } else {
        fc_dlog(_log, "Waiting till another block is received");
        // nothing to do until more blocks arrive
     }
    
    }
  • _pending_block_mode == producint && result == successed
    启动定时器,在若干毫秒之后调用producer_plugin_impl::maybe_produce_block进行区块生产的完成工作,在这个时间段内当前节点收到的所有transaction都会被打进这个区块中。

    if (_pending_block_mode == pending_block_mode::producing) {
    
     // we succeeded but block may be exhausted
     static const boost::posix_time::ptime epoch(boost::gregorian::date(1970, 1, 1));
     if (result == start_block_result::succeeded) {
        // ship this block off no later than its deadline
        _timer.expires_at(epoch + boost::posix_time::microseconds(chain.pending_block_time().time_since_epoch().count() + (last_block ? _last_block_time_offset_us : _produce_time_offset_us)));
        fc_dlog(_log, "Scheduling Block Production on Normal Block #${num} for ${time}", ("num", chain.pending_block_state()->block_num)("time",chain.pending_block_time()));
     } else {
        auto expect_time = chain.pending_block_time() - fc::microseconds(config::block_interval_us);
        // ship this block off up to 1 block time earlier or immediately
        if (fc::time_point::now() >= expect_time) {
           _timer.expires_from_now( boost::posix_time::microseconds( 0 ));
        } else {
           _timer.expires_at(epoch + boost::posix_time::microseconds(expect_time.time_since_epoch().count()));
        }
        fc_dlog(_log, "Scheduling Block Production on Exhausted Block #${num} immediately", ("num", chain.pending_block_state()->block_num));
     }
    
     _timer.async_wait([&chain,weak_this,cid=++_timer_corelation_id](const boost::system::error_code& ec) {
        auto self = weak_this.lock();
        if (self && ec != boost::asio::error::operation_aborted && cid == self->_timer_corelation_id) {
           auto res = self->maybe_produce_block();
           fc_dlog(_log, "Producing Block #${num} returned: ${res}", ("num", chain.pending_block_state()->block_num)("res", res) );
        }
     });
    }
  1. producer_plugin_impl::maybe_produce_block,这个函数会调用producer_plugin_impl::produce_block完成区块生产:
    
    //确保在异常退出候,scheudle_production_loop依然能够正常进行下去
    auto reschedule = fc::make_scoped_exit([this]{
     schedule_production_loop();
    });

try {
//完成区块的finalize_block,区块签名,更新fork_db
produce_block();
return true;
} catch ( const guard_exception& e ) {
app().get_plugin().handle_guard_exception(e);
return false;
} catch ( boost::interprocess::bad_alloc& ) {
raise(SIGUSR1);
return false;
} FC_LOG_AND_DROP();

fc_dlog(_log, "Aborting block due to produce_block error");
chain::controller& chain = app().get_plugin().chain();
chain.abort_block();
return false;


  5. producer_plugin_impl::produce_block函数主要完成区块生产的主要工作包括:  
  * finalize_block:  
    更新资源限制  
    设置action merkle树根  
    设置transaction merkle树根
    ...在controller中有更详细说明
  * sign_block
   对block进行签名,防止被篡改
  * commit_block
   将新产生的区块加到数据库中,并将该区块广播出去。在controller有详细叙述

   ```cpp
   EOS_ASSERT(_pending_block_mode == pending_block_mode::producing, producer_exception, "called produce_block while not actually producing");
   chain::controller& chain = app().get_plugin<chain_plugin>().chain();
   const auto& pbs = chain.pending_block_state();
   const auto& hbs = chain.head_block_state();
   EOS_ASSERT(pbs, missing_pending_block_state, "pending_block_state does not exist but it should, another plugin may have corrupted it");
   auto signature_provider_itr = _signature_providers.find( pbs->block_signing_key );

   EOS_ASSERT(signature_provider_itr != _signature_providers.end(), producer_priv_key_not_found, "Attempting to produce a block for which we don't have the private key");

   //idump( (fc::time_point::now() - chain.pending_block_time()) );
   //完成块
   chain.finalize_block();
   //对块进行签名
   chain.sign_block( [&]( const digest_type& d ) {
      auto debug_logger = maybe_make_debug_time_logger();
      return signature_provider_itr->second(d);
   } );
   //提交块到数据库
   chain.commit_block();
   auto hbt = chain.head_block_time();
   //idump((fc::time_point::now() - hbt));

   block_state_ptr new_bs = chain.head_block_state();
   _producer_watermarks[new_bs->header.producer] = chain.head_block_num();

   ilog("Produced block ${id}... #${n} @ ${t} signed by ${p} [trxs: ${count}, lib: ${lib}, confirmed: ${confs}]",
        ("p",new_bs->header.producer)("id",fc::variant(new_bs->id).as_string().substr(0,16))
        ("n",new_bs->block_num)("t",new_bs->header.timestamp)
        ("count",new_bs->block->transactions.size())("lib",chain.last_irreversible_block_num())("confs", new_bs->header.confirmed));

至此producer_plugin中区块的生产流程已经介绍完毕,更详细的分析会在controller中体现出来。 总体时序如下:

![image](diagram/producer_sequence.png)

区块同步流程:

controller

producer_plugin在区块生产的过程中扮演着调度的角色,而实际工作是放在controller中来完成的,下面将纤细分析controller在区块生成过程中所扮演的角色功能:
上文说到在producer_plugin_impl::start_block函数中会调用controller::abort_block和controller::start_block两个函数,这里需要展示一下controller相关数据结构,controller的功能主要是在controller_impl中实现的,这里只列举关键部分:

struct controller {
    enum class block_status {
        irreversible = 0, //区块已经被应用,且不可逆
        validated = 1,    //区块已经被可信任的生产者签名,并已经应用但还不是不可逆状态
        complete = 2,     //区块已经被可信任的生产者签名,但是还没有被应用,状态为可逆
        incomplete = 3    //区块正在生产过程
    };

    //信号量集合
    signal<void(const signed_block_ptr&)>         pre_accepted_block;
    signal<void(const block_state_ptr&)>          accepted_block_header;
    signal<void(const block_state_ptr&)>          accepted_block;
    signal<void(const block_state_ptr&)>          irreversible_block;
    signal<void(const transaction_metadata_ptr&)> accepted_transaction;
    signal<void(const transaction_trace_ptr&)>    applied_transaction;
    signal<void(const header_confirmation&)>      accepted_confirmation;
    signal<void(const int&)>                      bad_alloc;

    private:
        std::unique_ptr<controller_impl>   my;
};

struct controller_impl {
    controller&                  self;
    chainbase::database          db;   // state db,主要是存储合约执行后的各种状态信息
    chainbase::database          reversible_blocks; //用来存储已经成功应用但是还是可逆状态
    block_log                    blog;
    optional<pending_state>      pending;   //保存正在生成的block信息,该结构在上文已经列出
    block_state_ptr              head;      //上一次block state信息,该结构在上文已经列出
    fork_database                fork_db;
    wasm_interface               wasmif;
    resource_limits_manager      resource_limits;
    authorization_manager        authorization;
    ...
    /**
    *  Transactions that were undone by pop_block or abort_block, transactions
    *  are removed from this list if they are re-applied in other blocks. Producers
    *  can query this list when scheduling new transactions into blocks.
    */

    /**transaction的撤销由pop_block或abort_block来完成。如果有其他块重新应用了这些事物,则需要从该列表中将其删除。
    * 当新transaction被调度成块是,用户可以查询列表。
    * 从后面的分析中可以看到,abort_block并没有完成撤销工作
    */
    map<digest_type,transaction_metadata_ptr> unapplied_transactions;
    .
    .
    .
}

controller的初始化工作是由chain_plugin::plugin_initialize函数来完成的:检查白名单、黑名单、灰名单,数据库目录、检查点、及命令行参数的检查,主要功能定义在:plugins/chain_plugin/chain_plugin.cpp 314行。
在chain_plugin中还负责相关channel的初始化工作。
然后chain_plugin::plugin_start函数会将controller启动,定义在:plugins/chain_plugin/chain_plugin.cpp 633行:

try {
   try {
       //controller启动
      my->chain->startup();
   } catch (const database_guard_exception& e) {
      log_guard_exception(e);
      // make sure to properly close the db
      my->chain.reset();
      throw;
   }

   if(!my->readonly) {
      ilog("starting chain in read/write mode");
   }

   ilog("Blockchain started; head block is #${num}, genesis timestamp is ${ts}",
        ("num", my->chain->head_block_num())("ts", (std::string)my->chain_config->genesis.initial_timestamp));

   my->chain_config.reset();
} FC_CAPTURE_AND_RETHROW()

在controller::startup中会调用controller_impl::add_index:
这个函数主要为controller_impl::reversible_block和db添加索引:

      //为reversible block建立索引
      reversible_blocks.add_index<reversible_block_index>();

      db.add_index<account_index>();
      db.add_index<account_sequence_index>();

      db.add_index<table_id_multi_index>();
      db.add_index<key_value_index>();
      db.add_index<index64_index>();
      db.add_index<index128_index>();
      db.add_index<index256_index>();
      db.add_index<index_double_index>();
      db.add_index<index_long_double_index>();

      db.add_index<global_property_multi_index>();
      db.add_index<dynamic_global_property_multi_index>();
      db.add_index<block_summary_multi_index>();
      db.add_index<transaction_multi_index>();
      db.add_index<generated_transaction_multi_index>();

      authorization.add_indices();
      resource_limits.add_indices();

上述结构在后文有详细说明;然后进行fork_db的初始化工作,设置controller_impl::head,使其处于正确的状态为后续的区块生产做准备工作,到这里区块的初始化基本完成了,下面就到了区块生产的环节了。

从上文我们知道producer_plugin::start_block最后会调用controller::abort_block和start_block两个函数,这两个函数最终会调用controller_impl::abort_block和controller_impl::start_block两个函数:
controller_impl::abort_block重置controller_impl::pending信息,使pending处于全新状态:

if( pending ) {

    //这里只是将_pending_block_state中的transaction重新放到unapplied_transactions中,并没有做撤销工作
    if ( read_mode == db_read_mode::SPECULATIVE ) {
    for( const auto& t : pending->_pending_block_state->trxs )
        unapplied_transactions[t->signed_id] = t;
    }
    pending.reset();
}

controller_impl::start_block函数接受三个参数:1.即将要产生的区块的时间戳when,2.区块确认数量confirm_block_count,3.区块当前的状态status:

  • 判断controller_impl::pending是否为初始状态,否则抛出异常

    EOS_ASSERT( !pending, block_validate_exception, "pending block already exists" );
  • 建立db session

     if (!self.skip_db_sessions(s)) {
         EOS_ASSERT( db.revision() == head->block_num, database_exception, "db revision is not on par with head block",
                     ("db.revision()", db.revision())("controller_head_block", head->block_num)("fork_db_head_block", fork_db.head()->block_num) );
    
         pending.emplace(maybe_session(db));
      } else {
         pending.emplace(maybe_session());
      }
  • 根据最近的controller_impl::head生成新的pending

    pending->_block_status = s;
    
    //这里会调用block_head::block_head(const block_header_state& prev, block_timestamp_type when)
    //然后调用block_state_head::generate_next根据传进来的时间戳when生成新的block_header_state(新块)
    //应为当前节点是正在出块的节点,所以在generate_next不需要对块进行完整性验证
    //在同步块的时候则需要调用next函数,并做完整性验证后面详述
    //generate_next代码定义在 libraries/chain/block_header_state.cpp 36行
    pending->_pending_block_state = std::make_shared<block_state>( *head, when ); // promotes pending schedule (if any) to active
    pending->_pending_block_state->in_current_chain = true;
  • 将出块action打进transaction并执行,然后清理过期的transactions更新生产者授权

        try {
            auto onbtrx = std::make_shared<transaction_metadata>( get_on_block_transaction() );
            onbtrx->implicit = true;
            auto reset_in_trx_requiring_checks = fc::make_scoped_exit([old_value=in_trx_requiring_checks,this](){
                  in_trx_requiring_checks = old_value;
               });
            in_trx_requiring_checks = true;
            push_transaction( onbtrx, fc::time_point::maximum(), self.get_global_properties().configuration.min_transaction_cpu_usage, true );
         } catch( const boost::interprocess::bad_alloc& e  ) {
            elog( "on block transaction failed due to a bad allocation" );
            throw;
         } catch( const fc::exception& e ) {
            wlog( "on block transaction failed, but shouldn't impact block generation, system contract needs update" );
            edump((e.to_detail_string()));
         } catch( ... ) {
         }
    
         clear_expired_input_transactions();
         update_producers_authority();

    至此controller_impl::start_block函数分析完毕,其主要功能就是根据当前head生成新块,并将出块action打进transaction中。
    在controller_impl::start_block函数执行完毕候,控制权就交还给producer_plugin_impl::start_block了,在上文有对应的分析,producer_plugin_impl::start_block最终会把控制权交给producer_plugin_impl::schedule_production_loop,在这个函数中会启动一个定时器,在延迟一段时间之后会调用proudcer_plugin_impl::maybe_produce_block,这个函数会调用producer_plugin_impl::produce_block这在上文都有分析到,在producer_plugin_impl::produce_block中会调用:
    controller::finalize_block,controller::sign_block和controller::commit_block三个函数来完成区块生产,区块签名,区块上链过程,下面来一次分析这三个函数:

  • controller::finalize_block
    这个函数主要是完成资源更新包括生产该区块所使用的cpu资源,带宽资源;设置action merkle树根;设置transaction merkle树根,创建block summary信息:

    resource_limits.process_account_limit_updates();
    const auto& chain_config = self.get_global_properties().configuration;
    uint32_t max_virtual_mult = 1000;
    uint64_t CPU_TARGET = EOS_PERCENT(chain_config.max_block_cpu_usage, chain_config.target_block_cpu_usage_pct);
    resource_limits.set_block_parameters(
        { CPU_TARGET, chain_config.max_block_cpu_usage, config::block_cpu_usage_average_window_ms / config::block_interval_ms, max_virtual_mult, {99, 100}, {1000, 999}},
        {EOS_PERCENT(chain_config.max_block_net_usage, chain_config.target_block_net_usage_pct), chain_config.max_block_net_usage, config::block_size_average_window_ms / config::block_interval_ms, max_virtual_mult, {99, 100}, {1000, 999}}
    );
    resource_limits.process_block_usage(pending->_pending_block_state->block_num);
    
    //设置action merkle树根
    set_action_merkle();
    
    //设置transaction merkle树根
    set_trx_merkle();
    
    auto p = pending->_pending_block_state;
    p->id = p->header.id();
    
    //根据block id生成 summary信息并放到数据库中
    create_block_summary(p->id);
  • controller::sign_block
    根据当前生产者提供的私钥签名函数对当前区块进行签名,并对做一次签名验证。

    auto p = pending->_pending_block_state;
    p->sign( signer_callback );
    static_cast<signed_block_header&>(*p->block) = p->header;

    block_header_state::sign(上面p->sign)定义如下:

    auto d = sig_digest();
    header.producer_signature = signer( d );
    EOS_ASSERT( block_signing_key == fc::crypto::public_key( header.producer_signature, d ), wrong_signing_key, "block is signed with unexpected key" );
  • controller::commit_block
    在详细分析这个函数之前需要先来分析一下fork_database这个类,它的结构如下:

    
    struct by_block_id;
    struct by_block_num;
    struct by_lib_block_num;
    struct by_prev;
    
    //建立一个基于block_state_ptr的多索引容器
    //by_block_id以block id为索引
    //by_block_num 以区块高度为索引
    //by_lib_block_num以最近的区块不可逆高度为索引
    //by_prev以前一个block id为索引
    typedef multi_index_container<
        block_state_ptr,
        indexed_by<
          hashed_unique< tag<by_block_id>, member<block_header_state, block_id_type, &block_header_state::id>, std::hash<block_id_type>>,
          ordered_non_unique< tag<by_prev>, const_mem_fun<block_header_state, const block_id_type&, &block_header_state::prev> >,
          ordered_non_unique< tag<by_block_num>,
              composite_key< block_state,
                member<block_header_state,uint32_t,&block_header_state::block_num>,
                member<block_state,bool,&block_state::in_current_chain>
              >,
              composite_key_compare< std::less<uint32_t>, std::greater<bool> >
          >,
          ordered_non_unique< tag<by_lib_block_num>,
              composite_key< block_header_state,
                  member<block_header_state,uint32_t,&block_header_state::dpos_irreversible_blocknum>,
                  member<block_header_state,uint32_t,&block_header_state::bft_irreversible_blocknum>,
                  member<block_header_state,uint32_t,&block_header_state::block_num>
              >,
              composite_key_compare< std::greater<uint32_t>, std::greater<uint32_t>, std::greater<uint32_t> >
          >
        >
    > fork_multi_index_type;
    struct fork_database_impl {
      fork_multi_index_type    index;
      block_state_ptr          head; //区块头
      fc::path                 datadir; //存储路径
    }
    
    class fork_database {
    public:
      //这里列举关键函数,详细定义参见 libraries/chain/include/eosio/chain/fork_database.hpp
    
      //根据区块id获取block_state信息
      block_state_ptr get_block(const block_id_type &id) const;
      //根据区块高度从当前链中获取block_state信息
      block_state_ptr get_block_in_current_chain_by_num(uint32_t num) const;
    
      //提供一个“有效的”区块状态,有可能以此建立分支
      void set(block_state_ptr s);
    
      block_state_ptr add(signed_block_ptr b,bool trust = false);
    
      block_state_ptr add(block_state_ptr next_block);
      void remove(const block_id_type &id);
      void add(const header_confirmation &c);
      const block_state_ptr &head() const;
    
      //根据两个头block,获取两个分支(两个分支有共同的祖先,即两个头部的previous的值相同)
      pair<branch_type,branch_type> fetch_branch_from(const block_id_type &first,const block_id_type &second) const;
    
      //若该区块为invalid,将会从数据库中删除。若为valid,在发射irreversible信号后,所有比LIB大的block将会被修正
      void set_validity(const block_state_ptr &h,bool valid);
      void mark_in_current_chain(const block_state_ptr &h,bool in_current_chain);
      void prune(const block_state_ptr&);
    
      signal<void(block_state_ptr)>    irreversible;
    
    private:
      void set_bft_irreversible(block_id_type id);
      unique_ptr<for_database_impl> my;
    }
  回到controller_impl::commit_block,该接受一个bool参数,该参数表示是否需要将controller_impl::pending->_pending_block_state加入fork_database,如果是则将pending->_pending_block_state->validated设为true,然后调用fork_database::add(block_state_ptr)将该块加入数据库,然后会根据当前的block_state进行数据库数据修正(后文fork_database部分有详细分析),然后检查是否正在重演该区块,如果否则将其加入可以缓存reversible_blocks,发射accept_block信号,该信号会调用net_plugin_impl::accept_block,函数,这些信号量的设置定义在plugins/net_plugin/net_plugin.cpp 3017行:
  ```cpp
    chain::controller&cc = my->chain_plug->chain();
    {
        cc.accepted_block_header.connect( boost::bind(&net_plugin_impl::accepted_block_header, my.get(), _1));
        cc.accepted_block.connect(  boost::bind(&net_plugin_impl::accepted_block, my.get(), _1));
        cc.irreversible_block.connect( boost::bind(&net_plugin_impl::irreversible_block, my.get(), _1));
        cc.accepted_transaction.connect( boost::bind(&net_plugin_impl::accepted_transaction, my.get(), _1));
        cc.applied_transaction.connect( boost::bind(&net_plugin_impl::applied_transaction, my.get(), _1));
        cc.accepted_confirmation.connect( boost::bind(&net_plugin_impl::accepted_confirmation, my.get(), _1));
    }

commit_block关键代码如下:

    try {
        if (add_to_fork_db) {
          pending->_pending_block_state->validated = true;
          auto new_bsp = fork_db.add(pending->_pending_block_state);
          emit(self.accepted_block_header, pending->_pending_block_state);

          //更新head到最新生成的区块头
          head = fork_db.head();
          EOS_ASSERT(new_bsp == head, fork_database_exception, "committed block did not become the new head in fork database");
        }

        if( !replaying ) {
          reversible_blocks.create<reversible_block_object>( [&]( auto& ubo ) {
              ubo.blocknum = pending->_pending_block_state->block_num;
              ubo.set_block( pending->_pending_block_state->block );
          });
        }

        emit( self.accepted_block, pending->_pending_block_state );
    } catch (...) {
        // dont bother resetting pending, instead abort the block
        reset_pending_on_exit.cancel();
        abort_block();
        throw;
    }

至此controller_impl::commit_block工作完成。控制权回到producer_plugin_impl::produce_block,一次block生产调度就完成了,然后进入下一次调度。

fork_database分析:
结构如下:


    struct by_block_id;
    struct by_block_num;
    struct by_lib_block_num;
    struct by_prev;

    //建立一个基于block_state_ptr的多索引容器
    //by_block_id以block id为索引
    //by_block_num 以区块高度为索引,组合键<block_num,in_current_chain>,降序
    //by_lib_block_num以最近的区块不可逆高度为索引,组合键<dpos_irreversible_blocknum,bft_irreversible_blocknum,block_num>,升序
    //by_prev以前一个block id为索引
    typedef multi_index_container<
        block_state_ptr,
        indexed_by<
          hashed_unique< tag<by_block_id>, member<block_header_state, block_id_type, &block_header_state::id>, std::hash<block_id_type>>,
          ordered_non_unique< tag<by_prev>, const_mem_fun<block_header_state, const block_id_type&, &block_header_state::prev> >,
          ordered_non_unique< tag<by_block_num>,
              composite_key< block_state,
                member<block_header_state,uint32_t,&block_header_state::block_num>,
                member<block_state,bool,&block_state::in_current_chain>
              >,
              composite_key_compare< std::less<uint32_t>, std::greater<bool> >
          >,
          ordered_non_unique< tag<by_lib_block_num>,
              composite_key< block_header_state,
                  member<block_header_state,uint32_t,&block_header_state::dpos_irreversible_blocknum>,
                  member<block_header_state,uint32_t,&block_header_state::bft_irreversible_blocknum>,
                  member<block_header_state,uint32_t,&block_header_state::block_num>
              >,
              composite_key_compare< std::greater<uint32_t>, std::greater<uint32_t>, std::greater<uint32_t> >
          >
        >
    > fork_multi_index_type;
    struct fork_database_impl {
      fork_multi_index_type    index;
      block_state_ptr          head; //区块头
      fc::path                 datadir; //存储路径
    }

    class fork_database {
    public:
      //这里列举关键函数,详细定义参见 libraries/chain/include/eosio/chain/fork_database.hpp

      //根据区块id获取block_state信息
      block_state_ptr get_block(const block_id_type &id) const;
      //根据区块高度从当前链中获取block_state信息
      block_state_ptr get_block_in_current_chain_by_num(uint32_t num) const;

      //提供一个“有效的”区块状态,有可能以此建立分支
      void set(block_state_ptr s);

      block_state_ptr add(signed_block_ptr b,bool trust = false);

      block_state_ptr add(block_state_ptr next_block);
      void remove(const block_id_type &id);
      void add(const header_confirmation &c);
      const block_state_ptr &head() const;

      //根据两个头block,获取两个分支(两个分支有共同的祖先,即两个头部的previous的值相同)
      pair<branch_type,branch_type> fetch_branch_from(const block_id_type &first,const block_id_type &second) const;

      //若该区块为invalid,将会从数据库中删除。若为valid,在发射irreversible信号后,所有比LIB大的block将会被修正
      void set_validity(const block_state_ptr &h,bool valid);
      void mark_in_current_chain(const block_state_ptr &h,bool in_current_chain);
      void prune(const block_state_ptr&);

      signal<void(block_state_ptr)>    irreversible;

    private:
      void set_bft_irreversible(block_id_type id);
      unique_ptr<for_database_impl> my;
    }

下面一次解释每个函数的实现:

  1. void fork_database::set(block_state_ptr s)

    //将s插入多索引容器中
    auto result = my->index.insert( s );
     EOS_ASSERT( s->id == s->header.id(), fork_database_exception, 
                 "block state id (${id}) is different from block state header id (${hid})", ("id", string(s->id))("hid", string(s->header.id())) );
    
        //FC_ASSERT( s->block_num == s->header.block_num() );
    
     EOS_ASSERT( result.second, fork_database_exception, "unable to insert block state, duplicate state detected" );
    
     //更新head状态
     if( !my->head ) {
        my->head =  s;
     } else if( my->head->block_num < s->block_num ) {
        my->head =  s;
     }

transaction执行,涉及到的关键数据结构如下:


    struct action_receipt {
      account_name        receiver;                //执行该action的account
      digest_type         act_digest;
      uint64_t            global_sequence = 0;
      uint64_t            recv_sequence = 0;
      flat_map<account_name,uint64_t> auth_sequence;
      fc::unsigned_int    code_sequence;
      fc::unsigned_int    abi_sequence;
    };

    struct base_action_trace {
      action_receipt      receipt;
      action              act;
      fc::microseconds    elapsed;
      uint64_t            cpu_usage = 0;
      string              console;
      uint64_t            total_cpu_usage = 0;
      transaction_id_type trx_id;
    }

    struct action_trace : public base_action_trace {
      vector<action_trace> inline_traces;
    }

    struct transaction_trace {
      transaction_id_type                      id;
      fc::optional<transaction_receipt_header> receipt;
      fc::microseconds                         elapsed;
      uint64_t                                 net_usage;
      bool                                     scheduled = false;
      vector<action_trace>                     action_traces;
      transaction_trace_ptr                    failed_dtrx_trace;
      fc::optional<fc::exception>              except;
      std::exception_ptr                       except_ptr;
    }

一个transaction是由一个或多个action组成的,这些action如果又一个失败了,那么该transaction也就失败了,已经执行过的action需要回滚。每个transaction必须在30ms内完成,如果一个包含了多个action且这些action执行时间总和超过30ms,则整个transaction失败。

chainbase分析

database基本数据结构

和数据库相关的数据结构均派生自 struct object,结构如下:

  template<typename T>
    class oid {
    public:
        oid( int64_t i = 0 ):_id{i}{}
        oid& operator++() {
            ++_id;
            return *this;
        }

        friend bool operator < ( const oid& a,const oid& b ) {
            return a._id < b._id;
        }

        friend bool operator > ( const oid& a,const oid& b ) {
            return a._id > b._id;
        }

        friend bool operator == ( const oid& a,const oid& b ) {
            return a._id == b._id;
        }

        friend bool operator != ( const oid& a,const oid& b ) {
            return a._id != b._id;
        }

        friend std::ostream& operator << ( std::ostream& s,const oid& id ) {
            s << boost::core::demangle( typeid( oid<T> ).name() ) << '(' << id._id << ')';
            return s;
        }

        int64_t _id;
    };
    template<uint16_t TypeNumber,typename Derived>
    struct object {
      typedef oid<Derived> id_type;
      static const uint16_t type_id = TypeNumber; //类型标识
    };

数据库的索引是通过元编程来实现的,每一种数据类型都有一个唯一id作为标识。程序在运行过程中要产生27个数据表:

  1. account_object:
    保存账户信息,结构如下:

     class account_object : public chainbase::object<account_object_type,account_objct> {
       OBJECT_CTOR(account_object,(code)(abi))
       id_type              id;
       account_name         name;                  //账户名称base32编码
       uint8_t              vm_type      = 0;      // vm_type
       uint8_t              vm_version   = 0;      // vm_version
       bool                 privileged   = false;  // 是否优先
    
       time_point           last_code_update;      //上次参与权限验证的时间
       digest_type          code_version;
       block_timestamp_type creation_date;         //创建时间
    
       shared_string  code;
       shared_string  abi;
    
       void set_abi( const eosio::chain::abi_def& a ) {
         abi.resize( fc::raw::pack_size( a ) );
         fc::datastream<char*> ds( abi.data(), abi.size() );
         fc::raw::pack( ds, a );
       }
    
       eosio::chain::abi_def get_abi()const {
         eosio::chain::abi_def a;
         EOS_ASSERT( abi.size() != 0, abi_not_found_exception, "No ABI set on account ${n}", ("n",name) );
    
         fc::datastream<const char*> ds( abi.data(), abi.size() );
         fc::raw::unpack( ds, a );
         return a;
       }
     };

    其中宏OBJECT_CTOR(account_object,(code)(abi))展开如下:

     account_object() = delete; 
     public: 
     template<typename Constructor, typename Allocator> 
     account_object(Constructor&& c, chainbase::allocator<Allocator> a) : id(0) ,code(a) ,abi(a) { c(*this); }

    该结构保存了账户的信息,对应的多索引容器为:

     struct by_name;
     using account_index = chainbase::shared_multi_index_container<
         account_object,
         indexed_by<
           ordered_unique<tag<by_id>, member<account_object, account_object::id_type, &account_object::id>>,
           ordered_unique<tag<by_name>, member<account_object, account_name, &account_object::name>>
         >
     >;

    创建一个账户的函数调用在libraries/chain/eos_contract.cpp void apply_eosio_newaccount(apply_context& context)函数中.

  2. account_sequence_object
    这个结构用来存储和账户相关的序列数据,具体结构如下:

     class account_sequence_object : public chainbase::object<account_sequence_object_type, account_sequence_object>
     {
         OBJECT_CTOR(account_sequence_object);
    
         id_type      id;
         account_name name;
         uint64_t     recv_sequence = 0;
         uint64_t     auth_sequence = 0;
         uint64_t     code_sequence = 0;
         uint64_t     abi_sequence  = 0;
     };

    对应的多索引容器如下:

     struct by_name;
     using account_sequence_index = chainbase::shared_multi_index_container<
         account_sequence_object,
         indexed_by<
           ordered_unique<tag<by_id>, member<account_sequence_object, account_sequence_object::id_type, &account_sequence_object::id>>,
           ordered_unique<tag<by_name>, member<account_sequence_object, account_name, &account_sequence_object::name>>
         >
     >;
  3. permission_object
    用来存储授权相关信息,具体结构如下:

     class permission_object : public chainbase::object<permission_object_type, permission_object> {
     OBJECT_CTOR(permission_object, (auth) )
    
       id_type                           id;
       permission_usage_object::id_type  usage_id;
       id_type                           parent; ///< parent permission
       account_name                      owner; ///< the account this permission belongs to
       permission_name                   name; ///< human-readable name for the permission
       time_point                        last_updated; ///< the last time this authority was updated
       shared_authority                  auth; ///< authority required to execute this permission
    
       /**
       * @brief Checks if this permission is equivalent or greater than other
       * @tparam Index The permission_index
       * @return true if this permission is equivalent or greater than other, false otherwise
       *
       * Permissions are organized hierarchically such that a parent permission is strictly more powerful than its
       * children/grandchildren. This method checks whether this permission is of greater or equal power (capable of
       * satisfying) permission @ref other.
       */
       template <typename Index>
       bool satisfies(const permission_object& other, const Index& permission_index) const {
         // If the owners are not the same, this permission cannot satisfy other
         if( owner != other.owner )
             return false;
    
         // If this permission matches other, or is the immediate parent of other, then this permission satisfies other
         if( id == other.id || id == other.parent )
             return true;
    
         // Walk up other's parent tree, seeing if we find this permission. If so, this permission satisfies other
         const permission_object* parent = &*permission_index.template get<by_id>().find(other.parent);
         while( parent ) {
             if( id == parent->parent )
               return true;
             if( parent->parent._id == 0 )
               return false;
             parent = &*permission_index.template get<by_id>().find(parent->parent);
         }
         // This permission is not a parent of other, and so does not satisfy other
         return false;
       }
     };

    对应的多索引容器为:

       struct by_parent;
       struct by_owner;
       struct by_name;
       using permission_index = chainbase::shared_multi_index_container<
           permission_object,
           indexed_by<
             ordered_unique<tag<by_id>, member<permission_object, permission_object::id_type, &permission_object::id>>,
             ordered_unique<tag<by_parent>,
                 composite_key<permission_object,
                   member<permission_object, permission_object::id_type, &permission_object::parent>,
                   member<permission_object, permission_object::id_type, &permission_object::id>
                 >
             >,
             ordered_unique<tag<by_owner>,
                 composite_key<permission_object,
                   member<permission_object, account_name, &permission_object::owner>,
                   member<permission_object, permission_name, &permission_object::name>
                 >
             >,
             ordered_unique<tag<by_name>,
                 composite_key<permission_object,
                   member<permission_object, permission_name, &permission_object::name>,
                   member<permission_object, permission_object::id_type, &permission_object::id>
                 >
             >
           >
       >;
  4. permission_usage_object
    保存了授权的使用信息,具体结构如下:

    class permission_usage_object : public chainbase::object<permission_usage_object_type, permission_usage_object> {
     OBJECT_CTOR(permission_usage_object)
    
     id_type           id;
     time_point        last_used;   ///< when this permission was last used
    };

    对应的多索引容器为:

    struct by_account_permission;
    using permission_usage_index = chainbase::shared_multi_index_container<
       permission_usage_object,
       indexed_by<
         ordered_unique<tag<by_id>, member<permission_usage_object, permission_usage_object::id_type, &permission_usage_object::id>>
       >
    >;
  5. permission_link_object
    这个类记录了contract 和 action之间的permission_object的链接,以记录这些contract在执行的过程中所需要的权限

    class permission_link_object : public chainbase::object<permission_link_object_type, permission_link_object> {
       OBJECT_CTOR(permission_link_object)
    
       id_type        id;
       /// The account which is defining its permission requirements
       account_name    account;
       /// The contract which account requires @ref required_permission to invoke
       account_name    code; /// TODO: rename to scope
       /// The message type which account requires @ref required_permission to invoke
       /// May be empty; if so, it sets a default @ref required_permission for all messages to @ref code
       action_name       message_type;
       /// The permission level which @ref account requires for the specified message types
       permission_name required_permission;
    };

    对应的索引如下:

    struct by_action_name;
    struct by_permission_name;
    using permission_link_index = chainbase::shared_multi_index_container<
     permission_link_object,
     indexed_by<
        ordered_unique<tag<by_id>,
           BOOST_MULTI_INDEX_MEMBER(permission_link_object, permission_link_object::id_type, id)
        >,
        ordered_unique<tag<by_action_name>,
           composite_key<permission_link_object,
              BOOST_MULTI_INDEX_MEMBER(permission_link_object, account_name, account),
              BOOST_MULTI_INDEX_MEMBER(permission_link_object, account_name, code),
              BOOST_MULTI_INDEX_MEMBER(permission_link_object, action_name, message_type)
           >
        >,
        ordered_unique<tag<by_permission_name>,
           composite_key<permission_link_object,
              BOOST_MULTI_INDEX_MEMBER(permission_link_object, account_name, account),
              BOOST_MULTI_INDEX_MEMBER(permission_link_object, permission_name, required_permission),
              BOOST_MULTI_INDEX_MEMBER(permission_link_object, account_name, code),
              BOOST_MULTI_INDEX_MEMBER(permission_link_object, action_name, message_type)
           >
        >
     >
    >;
  6. key_value_object
    结构如下:

       struct key_value_object : public chainbase::object<key_value_object_type, key_value_object> {
         OBJECT_CTOR(key_value_object, (value))
    
         typedef uint64_t key_type;
         static const int number_of_keys = 1;
    
         id_type               id;
         table_id              t_id;
         uint64_t              primary_key; //主键
         account_name          payer = 0;
         shared_string         value;      //值
       };

    对应的索引:

         using key_value_index = chainbase::shared_multi_index_container<
         key_value_object,
         indexed_by<
           ordered_unique<tag<by_id>, member<key_value_object, key_value_object::id_type, &key_value_object::id>>,
           ordered_unique<tag<by_scope_primary>,
               composite_key< key_value_object,
                 member<key_value_object, table_id, &key_value_object::t_id>,
                 member<key_value_object, uint64_t, &key_value_object::primary_key>
               >,
               composite_key_compare< std::less<table_id>, std::less<uint64_t> >
           >
         >
     >;
  7. index64_object
    是基于多索引容器建立的一个二级索引,定义如下:

    typedef secondary_index<uint64_t,index64_object_type>::index_object   index64_object;
    typedef secondary_index<uint64_t,index64_object_type>::index_index    index64_index;
  8. index128_object
    同上

  9. index256_object
    同上

  10. index_double_object
    同上

  11. index_long_double_object
    同上

  12. global_property_object
    存储了初始设定的值,用来调用块参数:

    class global_property_object : public chainbase::object<global_property_object_type, global_property_object>
    {
     OBJECT_CTOR(global_property_object, (proposed_schedule))
    
     id_type                           id;
     optional<block_num_type>          proposed_schedule_block_num;
     shared_producer_schedule_type     proposed_schedule;
     chain_config                      configuration;
    };

    对应的索引:

    using dynamic_global_property_multi_index = chainbase::shared_multi_index_container<
     dynamic_global_property_object,
     indexed_by<
        ordered_unique<tag<by_id>,
           BOOST_MULTI_INDEX_MEMBER(dynamic_global_property_object, dynamic_global_property_object::id_type, id)
        >
     >
    >;
  13. dynamic_global_property_object
    记录了区块链正常操作期间所计算的值,这些值反映了区块链的当前的全局的值:

    
    class dynamic_global_property_object : public chainbase::object<dynamic_global_property_object_type, dynamic_global_property_object>
    {
       OBJECT_CTOR(dynamic_global_property_object)
    
       id_type    id;
       uint64_t   global_action_sequence = 0;
    };
   对应的索引为:

using global_property_multi_index = chainbase::shared_multi_index_container<
global_property_object,
indexed_by<
ordered_unique<tag,
BOOST_MULTI_INDEX_MEMBER(global_property_object, global_property_object::id_type, id)

;

14. block_summary_object  
block的一个简明信息,用于transaction的TaPos验证。结构如下:

class block_summary_object : public chainbase::object<block_summary_object_type, block_summary_object>
{
OBJECT_CTOR(block_summary_object)

      id_type        id;
      block_id_type  block_id;
};
```
对应的索引为:
```
struct by_block_id;
using block_summary_multi_index = chainbase::shared_multi_index_container<
    block_summary_object,
    indexed_by<
      ordered_unique<tag<by_id>, BOOST_MULTI_INDEX_MEMBER(block_summary_object, block_summary_object::id_type, id)>
//      ordered_unique<tag<by_block_id>, BOOST_MULTI_INDEX_MEMBER(block_summary_object, block_id_type, block_id)>
    >
>;
```
在controller::finalize_block函数中,会产生一个该结构的记录:
```
  set_action_merkle();
  set_trx_merkle();

  auto p = pending->_pending_block_state;
  p->id = p->header.id();

  create_block_summary(p->id); //创建一个block_summary

```
  1. transaction_object
    记录了transaction的过期时间,在该过期时间内,如果该transaction还没得倒确认,则会删除:

    class transaction_object : public chainbase::object<transaction_object_type, transaction_object>
    {
          OBJECT_CTOR(transaction_object)
    
          id_type             id;
          time_point_sec      expiration;
          transaction_id_type trx_id;
    };

    对应的索引为:

   struct by_expiration;
   struct by_trx_id;
   using transaction_multi_index = chainbase::shared_multi_index_container<
      transaction_object,
      indexed_by<
         ordered_unique< tag<by_id>, BOOST_MULTI_INDEX_MEMBER(transaction_object, transaction_object::id_type, id)>,
         ordered_unique< tag<by_trx_id>, BOOST_MULTI_INDEX_MEMBER(transaction_object, transaction_id_type, trx_id)>,
         ordered_unique< tag<by_expiration>,
            composite_key< transaction_object,
               BOOST_MULTI_INDEX_MEMBER( transaction_object, time_point_sec, expiration ),
               BOOST_MULTI_INDEX_MEMBER( transaction_object, transaction_object::id_type, id)
            >
         >
      >
   >;

在transaction执行的时候,会对收到的transaction做一个初始化工作,transaction_context::init_for_input_trx会调用该函数产生一个transaction_object记录:

    published = control.pending_block_time();
      is_input = true;
      if (!control.skip_trx_checks()) {
         control.validate_expiration(trx);
         control.validate_tapos(trx);
         control.validate_referenced_accounts(trx);
      }
      init( initial_net_usage);
      if (!skip_recording)
         record_transaction( id, trx.expiration ); /// checks for dupes
  1. generated_transaction_object
    结构如下:

    
    class generated_transaction_object : public chainbase::object<generated_transaction_object_type, generated_transaction_object>
    {
       OBJECT_CTOR(generated_transaction_object, (packed_trx) )
    
       id_type                       id;
       transaction_id_type           trx_id;
       account_name                  sender;
       uint128_t                     sender_id = 0; /// ID given this transaction by the sender
       account_name                  payer;
       time_point                    delay_until; /// this generated transaction will not be applied until the specified time
       time_point                    expiration; /// this generated transaction will not be applied after this time
       time_point                    published;
       shared_string                 packed_trx;
    
       uint32_t set( const transaction& trx ) {
          auto trxsize = fc::raw::pack_size( trx );
          packed_trx.resize( trxsize );
          fc::datastream<char*> ds( packed_trx.data(), trxsize );
          fc::raw::pack( ds, trx );
          return trxsize;
       }
    };
   对应的索引:

struct by_trx_id;
struct by_expiration;
struct by_delay;
struct by_status;
struct by_sender_id;

using generated_transaction_multi_index = chainbase::shared_multi_index_container<
generated_transaction_object,
indexed_by<
ordered_unique< tag, BOOST_MULTI_INDEX_MEMBER(generated_transaction_object, generated_transaction_object::id_type, id)>,
ordered_unique< tag, BOOST_MULTI_INDEX_MEMBER( generated_transaction_object, transaction_id_type, trx_id)>,
ordered_unique< tag,
composite_key< generated_transaction_object,
BOOST_MULTI_INDEX_MEMBER( generated_transaction_object, time_point, expiration),
BOOST_MULTI_INDEX_MEMBER( generated_transaction_object, generated_transaction_object::id_type, id)

,
ordered_unique< tag,
composite_key< generated_transaction_object,
BOOST_MULTI_INDEX_MEMBER( generated_transaction_object, time_point, delay_until),
BOOST_MULTI_INDEX_MEMBER( generated_transaction_object, generated_transaction_object::id_type, id)

,
ordered_unique< tag,
composite_key< generated_transaction_object,
BOOST_MULTI_INDEX_MEMBER( generated_transaction_object, account_name, sender),
BOOST_MULTI_INDEX_MEMBER( generated_transaction_object, uint128_t, sender_id)

;

17. producer_object  
结构如下:

class producer_object : public chainbase::object<producer_object_type, producer_object> {
OBJECT_CTOR(producer_object)

  id_type            id;
  account_name       owner;
  uint64_t           last_aslot = 0;
  public_key_type    signing_key;
  int64_t            total_missed = 0;
  uint32_t           last_confirmed_block_num = 0;


    /// The blockchain configuration values this producer recommends
    chain_config       configuration;
};
   对应的索引:

struct by_key;
struct by_owner;
using producer_multi_index = chainbase::shared_multi_index_container<
producer_object,
indexed_by<
ordered_unique<tag, member<producer_object, producer_object::id_type, &producer_object::id>>,
ordered_unique<tag, member<producer_object, account_name, &producer_object::owner>>,
ordered_unique<tag,
composite_key<producer_object,
member<producer_object, public_key_type, &producer_object::signing_key>,
member<producer_object, producer_object::id_type, &producer_object::id>

;

18. account_control_history_object
19. public_key_history_object
20. table_id_object
结构如下:

class table_id_object : public chainbase::object<table_id_object_type, table_id_object> {
OBJECT_CTOR(table_id_object)

    id_type        id;
    account_name   code;
    scope_name     scope;
    table_name     table;
    account_name   payer;
    uint32_t       count = 0; /// the number of elements in the table
};
   对应的索引:

struct by_code_scope_table;

using table_id_multi_index = chainbase::shared_multi_index_container<
table_id_object,
indexed_by<
ordered_unique<tag,
member<table_id_object, table_id_object::id_type, &table_id_object::id>

,
ordered_unique<tag,
composite_key< table_id_object,
member<table_id_object, account_name, &table_id_object::code>,
member<table_id_object, scope_name, &table_id_object::scope>,
member<table_id_object, table_name, &table_id_object::table>

;

21. resource_limits_object  
结构如下:

struct resource_limits_object : public chainbase::object<resource_limits_object_type, resource_limits_object> {

  OBJECT_CTOR(resource_limits_object)

  id_type id;
  account_name owner;
  bool pending = false;

  int64_t net_weight = -1;
  int64_t cpu_weight = -1;
  int64_t ram_bytes = -1;

};

   对应的索引:

struct by_owner;
struct by_dirty;

using resource_limits_index = chainbase::shared_multi_index_container<
resource_limits_object,
indexed_by<
ordered_unique<tag, member<resource_limits_object, resource_limits_object::id_type, &resource_limits_object::id>>,
ordered_unique<tag,
composite_key<resource_limits_object,
BOOST_MULTI_INDEX_MEMBER(resource_limits_object, bool, pending),
BOOST_MULTI_INDEX_MEMBER(resource_limits_object, account_name, owner)

;

22. resource_usage_object  
结构如下:

struct resource_usage_object : public chainbase::object<resource_usage_object_type, resource_usage_object> {
OBJECT_CTOR(resource_usage_object)

  id_type id;
  account_name owner;

  usage_accumulator        net_usage;
  usage_accumulator        cpu_usage;

  uint64_t                 ram_usage = 0;

};

   对应的索引:

using resource_usage_index = chainbase::shared_multi_index_container<
resource_usage_object,
indexed_by<
ordered_unique<tag, member<resource_usage_object, resource_usage_object::id_type, &resource_usage_object::id>>,
ordered_unique<tag, member<resource_usage_object, account_name, &resource_usage_object::owner> >

;

23. resource_limits_config_object  
结构如下:

class resource_limits_config_object : public chainbase::object<resource_limits_config_object_type, resource_limits_config_object> {
OBJECT_CTOR(resource_limits_config_object);
id_type id;

  static_assert( config::block_interval_ms > 0, "config::block_interval_ms must be positive" );
  static_assert( config::block_cpu_usage_average_window_ms >= config::block_interval_ms,
                 "config::block_cpu_usage_average_window_ms cannot be less than config::block_interval_ms" );
  static_assert( config::block_size_average_window_ms >= config::block_interval_ms,
                 "config::block_size_average_window_ms cannot be less than config::block_interval_ms" );


  elastic_limit_parameters cpu_limit_parameters = {EOS_PERCENT(config::default_max_block_cpu_usage, config::default_target_block_cpu_usage_pct), config::default_max_block_cpu_usage, config::block_cpu_usage_average_window_ms / config::block_interval_ms, 1000, {99, 100}, {1000, 999}};
  elastic_limit_parameters net_limit_parameters = {EOS_PERCENT(config::default_max_block_net_usage, config::default_target_block_net_usage_pct), config::default_max_block_net_usage, config::block_size_average_window_ms / config::block_interval_ms, 1000, {99, 100}, {1000, 999}};

  uint32_t account_cpu_usage_average_window = config::account_cpu_usage_average_window_ms / config::block_interval_ms;
  uint32_t account_net_usage_average_window = config::account_net_usage_average_window_ms / config::block_interval_ms;

};

   对应的索引:

using resource_limits_config_index = chainbase::shared_multi_index_container<
resource_limits_config_object,
indexed_by<
ordered_unique<tag, member<resource_limits_config_object, resource_limits_config_object::id_type, &resource_limits_config_object::id>>

;

24. resource_limits_state_object  

class resource_limits_state_object : public chainbase::object<resource_limits_state_object_type, resource_limits_state_object> {
OBJECT_CTOR(resource_limits_state_object);
id_type id;

  /**
   * Track the average netusage for blocks
   */
  usage_accumulator average_block_net_usage;

  /**
   * Track the average cpu usage for blocks
   */
  usage_accumulator average_block_cpu_usage;

  void update_virtual_net_limit( const resource_limits_config_object& cfg );
  void update_virtual_cpu_limit( const resource_limits_config_object& cfg );

  uint64_t pending_net_usage = 0ULL;
  uint64_t pending_cpu_usage = 0ULL;

  uint64_t total_net_weight = 0ULL;
  uint64_t total_cpu_weight = 0ULL;
  uint64_t total_ram_bytes = 0ULL;

  /**
   * The virtual number of bytes that would be consumed over blocksize_average_window_ms
   * if all blocks were at their maximum virtual size. This is virtual because the
   * real maximum block is less, this virtual number is only used for rate limiting users.
   *
   * It's lowest possible value is max_block_size * blocksize_average_window_ms / block_interval
   * It's highest possible value is 1000 times its lowest possible value
   *
   * This means that the most an account can consume during idle periods is 1000x the bandwidth
   * it is gauranteed under congestion.
   *
   * Increases when average_block_size < target_block_size, decreases when
   * average_block_size > target_block_size, with a cap at 1000x max_block_size
   * and a floor at max_block_size;
   **/
  uint64_t virtual_net_limit = 0ULL;

  /**
   *  Increases when average_bloc
   */
  uint64_t virtual_cpu_limit = 0ULL;

};

   对应的索引:

using resource_limits_state_index = chainbase::shared_multi_index_container<
resource_limits_state_object,
indexed_by<
ordered_unique<tag, member<resource_limits_state_object, resource_limits_state_object::id_type, &resource_limits_state_object::id>>

;

25. account_history_object
26. action_history_object
27. reversible_block_object  
记录还没变成不可逆的区块,结构如下:

class reversible_block_object : public chainbase::object<reversible_block_object_type, reversible_block_object> {
OBJECT_CTOR(reversible_block_object,(packedblock) )

  id_type        id;
  uint32_t       blocknum = 0;
  shared_string  packedblock;

  void set_block( const signed_block_ptr& b ) {
     packedblock.resize( fc::raw::pack_size( *b ) );
     fc::datastream<char*> ds( packedblock.data(), packedblock.size() );
     fc::raw::pack( ds, *b );
  }

  signed_block_ptr get_block()const {
     fc::datastream<const char*> ds( packedblock.data(), packedblock.size() );
     auto result = std::make_shared<signed_block>();
     fc::raw::unpack( ds, *result );
     return result;
  }

};

   对应的索引为:

struct by_num;
using reversible_block_index = chainbase::shared_multi_index_container<
reversible_block_object,
indexed_by<
ordered_unique<tag, member<reversible_block_object, reversible_block_object::id_type, &reversible_block_object::id>>,
ordered_unique<tag, member<reversible_block_object, uint32_t, &reversible_block_object::blocknum>>

;

以上数据表的初始化工作在controller_impl::add_indices()函数中:

      reversible_blocks.add_index<reversible_block_index>();

      db.add_index<account_index>();
      db.add_index<account_sequence_index>();

      db.add_index<table_id_multi_index>();
      db.add_index<key_value_index>();
      db.add_index<index64_index>();
      db.add_index<index128_index>();
      db.add_index<index256_index>();
      db.add_index<index_double_index>();
      db.add_index<index_long_double_index>();

      db.add_index<global_property_multi_index>();
      db.add_index<dynamic_global_property_multi_index>();
      db.add_index<block_summary_multi_index>();
      db.add_index<transaction_multi_index>();
      db.add_index<generated_transaction_multi_index>();

      authorization.add_indices();
      resource_limits.add_indices();

在authorization_manager::add_indices():

      _db.add_index<permission_index>();
      _db.add_index<permission_usage_index>();
      _db.add_index<permission_link_index>();

在resource_limits_manager::add_indices():

    _db.add_index<resource_limits_index>();
    _db.add_index<resource_usage_index>();
    _db.add_index<resource_limits_state_index>();
    _db.add_index<resource_limits_config_index>();

在transaction执行过程中涉及到的数据及流程如下:
调用controller_impl::push_transaction,在该函数中会生成一个transaction_context类型的变量trx_context, 然后对transaction进行初始化操作:

    transaction_context trx_context(self, trx->trx, trx->id);
         if ((bool)subjective_cpu_leeway && pending->_block_status == controller::block_status::incomplete) {
            trx_context.leeway = *subjective_cpu_leeway;
         }
         trx_context.deadline = deadline;
         trx_context.explicit_billed_cpu_time = explicit_billed_cpu_time;
         trx_context.billed_cpu_time_us = billed_cpu_time_us;
         trace = trx_context.trace;
         try {
            if( trx->implicit ) {
               trx_context.init_for_implicit_trx();
               trx_context.can_subjectively_fail = false;
            } else {
               bool skip_recording = replay_head_time && (time_point(trx->trx.expiration) <= *replay_head_time);
               trx_context.init_for_input_trx( trx->packed_trx.get_unprunable_size(),
                                               trx->packed_trx.get_prunable_size(),
                                               trx->trx.signatures.size(),
                                               skip_recording);
            }

            if( trx_context.can_subjectively_fail && pending->_block_status == controller::block_status::incomplete ) {
               check_actor_list( trx_context.bill_to_accounts ); // Assumes bill_to_accounts is the set of actors authorizing the transaction
            }


            trx_context.delay = fc::seconds(trx->trx.delay_sec);

然后对transaction进行授权检查:

   if( !self.skip_auth_check() && !trx->implicit ) {
               authorization.check_authorization(
                       trx->trx.actions,
                       trx->recover_keys( chain_id ),
                       {},
                       trx_context.delay,
                       [](){}
                       /*std::bind(&transaction_context::add_cpu_usage_and_check_time, &trx_context,
                                 std::placeholders::_1)*/,
                       false
               );
            }
    trx_context.exec();
    trx_context.finalize(); /

在此需要用到上面的global_property_object数据表,然后调用transaction_context::exec()对action进行调用:

    EOS_ASSERT( is_initialized, transaction_exception, "must first initialize" );

      if( apply_context_free ) {
         for( const auto& act : trx.context_free_actions ) {
            trace->action_traces.emplace_back();
            dispatch_action( trace->action_traces.back(), act, true );//action调用
         }
      }

      if( delay == fc::microseconds() ) {
         for( const auto& act : trx.actions ) {
            trace->action_traces.emplace_back();
            dispatch_action( trace->action_traces.back(), act ); //action调用
         }
      } else {
         schedule_transaction();
      }

在transaction_context::dispatch_action中会产生一个类型为apply_context的变量 acontext,调用apply_context::exec()进行真正的action的执行

      apply_context  acontext( control, *this, a, recurse_depth );
      acontext.context_free = context_free;
      acontext.receiver     = receiver;

      try {
         acontext.exec();
      } catch( ... ) {
         trace = move(acontext.trace);
         throw;
      }

      trace = move(acontext.trace);

在apply_context::exec()中会调用apply_context::exec_one() 调用vm借口进入合约层,进行action和数据的解析并执行。
vm会通过注册进入的借口来调用action执行,注册的借口为:

    REGISTER_INTRINSICS(transaction_api,
    (send_inline,               void(int, int)               )
    (send_context_free_inline,  void(int, int)               )
    (send_deferred,             void(int, int64_t, int, int, int32_t) )
    (cancel_deferred,           int(int)                     )
    );

transaction_api接口定义如下:

  class transaction_api : public context_aware_api {
   public:
      using context_aware_api::context_aware_api;

      void send_inline( array_ptr<char> data, size_t data_len ) {
         //TODO: Why is this limit even needed? And why is it not consistently checked on actions in input or deferred transactions
         EOS_ASSERT( data_len < context.control.get_global_properties().configuration.max_inline_action_size, inline_action_too_big,
                    "inline action too big" );

         action act;
         fc::raw::unpack<action>(data, data_len, act);
         context.execute_inline(std::move(act));
      }

      void send_context_free_inline( array_ptr<char> data, size_t data_len ) {
         //TODO: Why is this limit even needed? And why is it not consistently checked on actions in input or deferred transactions
         EOS_ASSERT( data_len < context.control.get_global_properties().configuration.max_inline_action_size, inline_action_too_big,
                   "inline action too big" );

         action act;
         fc::raw::unpack<action>(data, data_len, act);
         context.execute_context_free_inline(std::move(act));
      }

      void send_deferred( const uint128_t& sender_id, account_name payer, array_ptr<char> data, size_t data_len, uint32_t replace_existing) {
         try {
            transaction trx;
            fc::raw::unpack<transaction>(data, data_len, trx);
            context.schedule_deferred_transaction(sender_id, payer, std::move(trx), replace_existing);
         } FC_RETHROW_EXCEPTIONS(warn, "data as hex: ${data}", ("data", fc::to_hex(data, data_len)))
      }

      bool cancel_deferred( const unsigned __int128& val ) {
         fc::uint128_t sender_id(val>>64, uint64_t(val) );
         return context.cancel_deferred_transaction( (unsigned __int128)sender_id );
      }
  };

以上为系统数据表和database交互的模式。

智能合约的持久化存储和database交互

说到智能合约的持久化存储离不开Multi-Index,这个Multi-index是EOS实现的类boost::multi_index_container的功能,定义在: contracts/eosiolib/multi_index.hpp文件中,采用的是hana元编程,我们写的智能合约中的数据就是存储在这个multi_index中的。
该类实现了数据的增删改查接口:emplace,erase,modify,get,find等接口,通过这些接口和database进行交互。

  1. emplace中和database交互的关键代码:

       datastream<char*> ds( (char*)buffer, size );
           ds << obj;
    
           auto pk = obj.primary_key();
    
           //db_store_i64就是和database进行交互的接口
           i.__primary_itr = db_store_i64( _scope, TableName, payer, pk, buffer, size );
    
           if ( max_stack_buffer_size < size ) {
              free(buffer);
           }
  2. erase中和database交互的关键代码:

       eosio_assert( itr2 != _items_vector.rend(), "attempt to remove object that was not in multi_index" );
    
        _items_vector.erase(--(itr2.base()));
    
        //和database进行交互
        db_remove_i64( objitem.__primary_itr );

    其他接口和数据库交互请参看源码。
    multi_index的使用:

    
    class book_manager : public eosio::contract {
    public:
    void create()
    void delete()
    void find()
    private:
    account_name   _contract_name;
    struct book {
     uint64_t            _id;
     std::string         _name;
     EOSLIB_SERIALIZE(book,(_id)(_name));
    }
    typedef eosio::multi_index<N(book),book> _table;

}

大概类似于上面的代码,后面我会出一个详细智能合约开发的例子。EOSLIB_SERIALIZE宏用于序列化book接口,将其转为字节数组。后面我就可以基于_table对book进行管理了,增删改查也会与database进行交互,现在来看一下database提供的api接口,这些接口定义在 libraries/chain/wasm_interface.cpp中:

class database_api : public context_aware_api {
public:
using context_aware_api::context_aware_api;

  int db_store_i64( uint64_t scope, uint64_t table, uint64_t payer, uint64_t id, array_ptr<const char> buffer, size_t buffer_size ) {
     return context.db_store_i64( scope, table, payer, id, buffer, buffer_size );
  }
  void db_update_i64( int itr, uint64_t payer, array_ptr<const char> buffer, size_t buffer_size ) {
     context.db_update_i64( itr, payer, buffer, buffer_size );
  }
  void db_remove_i64( int itr ) {
     context.db_remove_i64( itr );
  }
  int db_get_i64( int itr, array_ptr<char> buffer, size_t buffer_size ) {
     return context.db_get_i64( itr, buffer, buffer_size );
  }
  int db_next_i64( int itr, uint64_t& primary ) {
     return context.db_next_i64(itr, primary);
  }
  int db_previous_i64( int itr, uint64_t& primary ) {
     return context.db_previous_i64(itr, primary);
  }
  int db_find_i64( uint64_t code, uint64_t scope, uint64_t table, uint64_t id ) {
     return context.db_find_i64( code, scope, table, id );
  }
  int db_lowerbound_i64( uint64_t code, uint64_t scope, uint64_t table, uint64_t id ) {
     return context.db_lowerbound_i64( code, scope, table, id );
  }
  int db_upperbound_i64( uint64_t code, uint64_t scope, uint64_t table, uint64_t id ) {
     return context.db_upperbound_i64( code, scope, table, id );
  }
  int db_end_i64( uint64_t code, uint64_t scope, uint64_t table ) {
     return context.db_end_i64( code, scope, table );
  }

  DB_API_METHOD_WRAPPERS_SIMPLE_SECONDARY(idx64,  uint64_t)
  DB_API_METHOD_WRAPPERS_SIMPLE_SECONDARY(idx128, uint128_t)
  DB_API_METHOD_WRAPPERS_ARRAY_SECONDARY(idx256, 2, uint128_t)
  DB_API_METHOD_WRAPPERS_FLOAT_SECONDARY(idx_double, float64_t)
  DB_API_METHOD_WRAPPERS_FLOAT_SECONDARY(idx_long_double, float128_t)

} ;


由上可见database_api调用的是apply_context提供的接口,而appy_context中有database的引用,最终所有的操作都会反映到database中去

转载自:https://github.com/123youyouer/eosex/tree/master/block_chain

C++新特性学习

C++11,14,17新特性学习

模板元编程

模板元编程概念:利用模板特化机制实现编译期条件选择结构,利用递归模板实现编译期循环结构,模板元编程则由编译器在编译器解释执行。
优势在于:1.以编译耗时为代价换来卓越的运行时性能,2.提供编译期类型计算。
劣势在于:1.代码可读性差,2.调试困难,3.编译时间长,4.可移植性差(对编译器来说)
模板元程序由元数据和元函数组成,元数据就是元编程可以操作的数据,即C++编译器在编译期可以操作的数据。元数据不是运行时变量,只能是编译期常量,不能修改,常见的元数据包括enum,静态常量,基本类型和自定义类型等。元函数是模板元编程中用于操作处理元数据的“构件”,可以在编译期被调用,因为它的功能和形式和运行时的函数类似,因而被成为元函数。元函数实际上表现为C++的一个类、模板类或模板函数。例如:

```cpp
template<int N,int M>
struct meta_func {
    enum {
        value = N+M
    };
}
```

调用元函数获取value值:std::cout << meta_func<1,2>::value << std::endl;
meta_func的执行过程是在编译期完成的,实际执行时是没有计算动作,而是直接使用编译期的计算结果的。元函数只处理元数据,元数据是编译期常量和类型。
因此模板元编程不能使用运行时的关键字,常用的编译期关键字如下:
1.enum,static const,用来定义编译期的整数常量;
2.typedef/using,用于定义元数据;
3.T、Types...,声明元数据类型;
4.template,主要用于定义元函数;
5.“::”,域位运算,用于解析类型作用域获取计算结果(元数据)
模板元编程中的条件判断,是通过type_traits来实现的,它不仅仅可以在编译期做判断,还可以做计算、查询、转换和选择。
模板元中的for等逻辑可以通过递归、重载、和模板特化(偏特化)等方法实现。

类型萃取关键词:
1.decltype 计算表达式的返回类型,并不执行表达式的计算。类似的还有std::result_of

2.编译期选择 std::conditional,它在编译期根据一个判断式选择连个类型中的一个,类似于运行时的三元表达式“?:”,用法如下:

std::conditional<true,int,float>::type 此时type的类型为int
std::conditional<false,int,float>::type 此时type为float

3.std::decay(退化),它对于普通类型来说是移除引用和cv符(const,volatile)。除了普通类型外,它还可以用于数组和函数,具体转化规则如下:
1.若T为“U的数组”或“U的数组的引用”,则成员 typedef type U*
2.若T为函数类型F或函数的引用,则成员typedef type 为 函数指针
3.否则,成员typedef type 定义为 std::remove_cv<std::remove_reference>::type,即普通类型对应的转化规则移除引用和cv符。
4.std::enable_if来实现编译期的if-else逻辑,它利用SFINAE(substitude failure is not an error)特性,根据条 件选择重载函数的元函数std::enable_if,它的原型是:
template<bool B, class T = void> struct enable_if;
根据enable_if的字面意思就可以知道,它使得函数在判断条件B仅仅为true时才有效,它的基本用法:

template <class T>
typename std::enable_if<std::is_arithmetic<T>::value, T>::type foo(T t)
{
    return t;
}
auto r = foo(1); //返回整数1
auto r1 = foo(1.2); //返回浮点数1.2
auto r2 = foo(“test”); //compile error

可以通过enable_if来实现编译期的if-else逻辑,比如下面的例子通过enable_if和条件判断式来将入参分为两大类,从而满足所有的入参类型:

template
typename std::enable_if<std::is_arithmetic::value, int>::type foo1(T t)
{
cout << t << endl;
return 0;
}

template <class T>
typename std::enable_if<!std::is_arithmetic<T>::value, int>::type foo1(T &t)
{
    cout << typeid(T).name() << endl;
    return 1;
}
对于arithmetic类型的入参则返回0,对于非arithmetic的类型则返回1,通过arithmetic将所有的入参类型分成了两大类进行处理。从上面的例子还可以看到,std::enable_if可以实现强大的重载机制,因为通常必须是参数不同才能重载,如果只有返回值不同是不能重载的,而在上面的例子中,返回类型相同的函数都可以重载。

可变模板参数的应用
template<typename... T>void f(T... args);
上面的定义中,省略号的作用:
1.声明一个参数包T... args,这个参数包中可以包含0到任意个模板参数
2...args参数包可以展开为一个个独立的参数
展开可变模板参数函数的方法有两种:一种是通过递归函数来展开,另外一种是通过逗号表达式来展开参数包,如下:
1.递归式:
需要一个递归终止函数,如:
void print()
template<typename T,class ...Types>
void print(T first,Types... args) {
std::cout << first << std::endl;
print(args...);
}
print(1,2,3,4);
递归调用的过程为:
print(1,2,3,4);
print(2,3,4);
print(3,4);
print(4);
//print();
递归终止函数还可以写成这样:
template
void print(T v) {
std::cout << v << std::endl;
}
void print(T first,Types... args) {
print(first);
print(args...);
}
递归调用的过程为:
print(1,2,3,4);
print(2,3,4);
print(3,4);
print(4);
2.逗号表达式展开:
template
void printarg(T t)
{
cout << t << endl;
}

template <class ...Args>
void expand(Args... args)
{
    int arr[] = {(printarg(args), 0)...};
}

expand(1,2,3,4);
同时还利用了初始化列表的技术。还可以利用lambda表达式:
template<class F, class... Args>void expand(const F& f, Args&&...args) 
{
    //这里用到了完美转发。
    initializer_list<int>{(f(std::forward< Args>(args)),0)...};
}
expand([](int i){cout<<i<<endl;}, 1,2,3);

3.折叠表达式:
就1中的例子来说,print函数可以改成这样:
template<typename... Types>
void print(Types const&... args) {
(std::cout << ... << args) << '\n';
}

可变参数模板类:
可变参数模板类是一个带可变模板参数的模板类,如标准库中的std::tuple。可以能的定义如下:
template<typename... Types>
class tuple;
这个可变参数模板类可以携带任意类型任意个数的模板参数:
std::tuple tp1 = std::make_tuple(1);
std::tuple<int, double> tp2 = std::make_tuple(1, 2.5);
std::tuple<int, double, string> tp3 = std::make_tuple(1, 2.5, “”);
可变参数模板的模板参数个数也可以为0,所以下面的定义也是合法的:
std::tuple<> tp;
可变参数模板类的参数包展开的方式和可变参数模板函数的展开方式不同,可变参数模板类的参数包展开需要通过模板特化和继承方式去展开,展开方式比可变参数模板函数要复杂。如下:
1.模板篇特化和递归方式展开参数包
可变参数模板类的展开一般需要定义两到三个类,包括类声明和偏特化的模板类:
//前置声明,表明这是一个可变参数模板类
template<typename... Args>
struct Sum;

//基本定义
template<typename First, typename... Rest>
struct Sum<First, Rest...>
{
    enum { value = Sum<First>::value + Sum<Rest...>::value };
};

//递归终止特化类,通过这个特化类来终止递归
template<typename Last>
struct Sum<Last>
{
    enum { value = sizeof (Last) };
};
上面的前置声明可以包含任意个数的参数,下面的声明就要求模板参数至少要有一个:
template<typename First, typename... Args>struct sum;他的展开方式为:

template<typename First, typename... Args>struct sum;//前置声明,可以省略
template<typename First, typename... Rest>//定义
struct Sum
{
    enum { value = Sum<First>::value + Sum<Rest...>::value };
};

template<typename Last>//递归终止定义
struct Sum<Last>
{
    enum{ value = sizeof(Last) };
};

递归终止模板类写法有多个:
template<typename... Args> struct sum;
template<typename First, typenameLast>
struct sum<First, Last>
{
enum{ value = sizeof(First) +sizeof(Last) };
};
在展开到两个参数时终止。还可以展开到0个参数时终止:
template<>struct sum<> { enum{ value = 0 }; };
还可以使用std::integral_constant来消除枚举定义value。利用std::integral_constant可以获得编译期常量的特性,可以将前面的sum例子改为这样:
//前置声明
template<typename First, typename... Args>
struct Sum;

//基本定义
template<typename First, typename... Rest>
struct Sum<First, Rest...> : std::integral_constant<int, Sum<First>::value + Sum<Rest...>::value>
{
};

//递归终止
template<typename Last>
struct Sum<Last> : std::integral_constant<int, sizeof(Last)>
{
};
sum<int,double,short>::value;//值为14

template<>
struct Sum<> {
    return 0;
}

2.继承方式展开参数包:
//整型序列的定义
template<int...>
struct IndexSeq{};

//继承方式,开始展开参数包
template<int N, int... Indexes>
struct MakeIndexes : MakeIndexes<N - 1, N - 1, Indexes...> {};

// 模板特化,终止展开参数包的条件
template<int... Indexes>
struct MakeIndexes<0, Indexes...>
{
    typedefIndexSeq<Indexes...> type;
};

折叠表达式:
1.left fold expression:
(...op pack) =====> (((pack1 op pack2) op pack3) ...op packN)
2.right fold expression:
(pack op...) =====> (pack op (...(packN-1 op packN-2)))
3.left init fold expression:
(init op ...op pack) =====> ((( init op pack1 ) op pack2 ) ... op packN )
4.right init fold expression
(pack op ...op init) =====> ( pack1 op ( ... ( packN op init )))

右值引用和移到语义:
右值引用相关概念:右值,纯右值,将亡值,universal references,引用折叠,移动语义和完美转发。
int i = getVar();
以上面代码为例,从函数getVar()获取一个整形值,然而这会产生几种类型的值呢?答案是会产生两种类型的值,一种是左值,一种是函数返回的临时的值,这个临时的值在表达式结束后就销毁了,而左值在表达式结束后依然存在(直到其作用域结束),这个临时的值就是右值,具体来说是一个纯右值,右值是不具名的。区分左值和右值的一个简单办法是:看能不能对表达式取地址,如果能,则为左值,否则为右值。
所有具名变量或对象都是左值,而匿名变量则是右值。如 int i = 0;这条语句,i是左值,0是字面量为右值。具体来说0是纯右值(prvalue),在C++11以上中所有的值必属于左值,将亡值,纯右值三者之一。比如,非引用返回的临时变量、运算表达式产生的临时变量、原始字面量和lambda表达式等都是纯右值。而将亡值是C++11新增的、与右值引用相关的表达式,比如,将要被移动的对象、T&&函数返回值、std::move返回值和转换为T&&的类型的转换函数的返回值等。关于将亡值我们会在后面介绍,先看下面的代码:
int j = 5;
auto f = []{return 5;};
上面的代码中5是一个原始字面量, []{return 5;}是一个lambda表达式,都是属于纯右值,他们的特点是在表达式结束之后就销毁了。

在看下面这行代码:
    T&& k = getVar();和 int i = getVar();很像,只是比后者多了“&&”,这个声明就是右值引用。我们知道左值引用是对左值的引用,对应的,右值引用就是对右值的引用,而且右值是匿名变量,我们也只能通过应用的方式来获得右值。
    这里,getVar()产生的临时值不会像后者那样,在表达是结束后就销毁了,而是被续命,他的生命周期将会通过右值引用得以延续,和变量k的声明周期一样长。
    右值引用的一个特点:通过右值引用的声明,右值又重获新生,其生命周期与右值引用类型变量的生命周期一样的长。
    右值引用的第二个特点:右值引用独立于左值和右值。即右值引用类型的变量可能是左值也可能是右值,比如:int&& var1 = 1;
    var1类型为右值引用,但var1本身是左值,因为具名变量都是左值。

    关于右值引用一个有意思的问题是:T&&是什么,一定是右值吗?如例:
    template<typename T>
    void f(T&& t){}
    f(10); //t是右值
    int x = 10;
    f(x); //t是左值
    从上面的代码中可以看到,T&&表示的值类型不确定,可能是左值又可能是右值,这一点看起来有点奇怪,这就是右值引用的一个特点。
    右值引用的第三个特点:T&& t在发生自动类型推断的时候,它是未定的引用类型(universal references),如果被一个左值初始化,它就是一个左值;如果它被一个右值初始化,它就是一个右值,它是左值还是右值取决于它的初始化。
    我们再回过头看上面的代码,对于函数template<typename T>void f(T&& t),当参数为右值10的时候,根据universal references的特点,t被一个右值初始化,那么t就是右值;当参数为左值x时,t被一个左值引用初始化,那么t就是一个左值。需要注意的是,仅仅是当发生自动类型推导(如函数模板的类型自动推导,或auto关键字)的时候,T&&才是universal references。再看看下面的例子:
    template<typename T>
    void f(T&& param); 

    template<typename T>
    class Test {
        Test(Test&& rhs); 
    };

    上面的例子中,param是universal reference,rhs是Test&&右值引用,因为模版函数f发生了类型推断,而Test&&并没有发生类型推导,因为Test&&是确定的类型了。
      正是因为右值引用可能是左值也可能是右值,依赖于初始化,并不是一下子就确定的特点,我们可以利用这一点做很多文章,比如后面要介绍的移动语义和完美转发。
      这里再提一下引用折叠,正是因为引入了右值引用,所以可能存在左值引用与右值引用和右值引用与右值引用的折叠,C++11确定了引用折叠的规则,规则是这样的:
    所有的右值引用叠加到右值引用上仍然还是一个右值引用;
    所有的其他引用类型之间的叠加都将变成左值引用。
    考虑下面的代码:
    T(T&& a) : m_val(val){ a.m_val=nullptr; }
    这行代码实际上来自于一个类的构造函数,构造函数的一个参数是一个右值引用,为什么将右值引用作为构造函数的参数呢?在解答这个问题之前我们先看一个例子。如下:
    class A
    {
    public:
        A():m_ptr(new int(0)){cout << "construct" << endl;}
        A(const A& a):m_ptr(new int(*a.m_ptr)) //深拷贝的拷贝构造函数
        {
            cout << "copy construct" << endl;
        }
        ~A(){ delete m_ptr;}
    private:
        int* m_ptr;
    };
    A getA() {
        return A;
    }
    int main() {
        A a = GetA();
        return 0;
    }
        输出:
    construct
    copy construct
    copy construct
    这个例子很简单,一个带有堆内存的类,必须提供一个深拷贝拷贝构造函数,因为默认的拷贝构造函数是浅拷贝,会发生“指针悬挂”的问题。如果不提供深拷贝的拷贝构造函数,上面的测试代码将会发生错误(编译选项-fno-elide-constructors),内部的m_ptr将会被删除两次,一次是临时右值析构的时候删除一次,第二次外面构造的a对象释放时删除一次,而这两个对象的m_ptr是同一个指针,这就是所谓的指针悬挂问题。提供深拷贝的拷贝构造函数虽然可以保证正确,但是在有些时候会造成额外的性能损耗,因为有时候这种深拷贝是不必要的。这个时候就需要一个移动构造函数:如下:
    lass A
    {
    public:
        A() :m_ptr(new int(0)){}
        A(const A& a):m_ptr(new int(*a.m_ptr)) //深拷贝的拷贝构造函数
        {
            cout << "copy construct" << endl;
        }
        A(A&& a) :m_ptr(a.m_ptr)
        {
            m_ptr = a.m_ptr;
            a.m_ptr = nullptr;
            cout << "move construct" << endl;
        }
        ~A(){ delete m_ptr;}
    private:
        int* m_ptr;
    };
    int main(){
        A a = Get(false); 
    } 
    输出:
    construct
    move construct
    move construct
std::move的理解,move实际上并不移动任何东西,他唯一的功能就是将一个左值强制转换为一个右值引用,如果是一些基本类型比如int和char[10]定长数组等类型,使用move的话仍然会发生拷贝(因为没有对应的移动构造函数)。所以,move对于含资源(堆内存或句柄)的对象来说更有意义。
完美转发:在函数模板中,完全依照模板参数的类型(即保持参数的左值,右值特征),将参数传递给模板函数中调用的另外一个函数
std::forward正是做这个事情的,他会按照参数的实际类型进行转发,例如:
void processValue(int& a){ cout << "lvalue" << endl; }
void processValue(int&& a){ cout << "rvalue" << endl; }
template <typename T>
void forwardValue(T&& val)
{
    processValue(std::forward<T>(val)); //照参数本来的类型进行转发。
}
void Testdelcl()
{
    int i = 0;
    forwardValue(i); //传入左值 
    forwardValue(0);//传入右值 
}
输出:
lvaue 
rvalue
右值引用T&&是一个universal references,可以接受左值或者右值,正是这个特性让他适合作为一个参   数的路由,然后再通过std::forward按照参数的实际类型去匹配对应的重载函数,最终实现完美转发。

转载自:https://github.com/123youyouer/eosex/blob/master/block_chain/C++%E6%96%B0%E7%89%B9%E6%80%A7%E5%AD%A6%E4%B9%A0.md

参考

https://www.jianshu.com/p/e8cdc459f363

同步区块--p2p通信--notice_message

上篇笔记写到远程节点给本地节点发送notice消息,通知本地节点同步区块。本篇笔记继续学习本地节点如何从远程节点同步区块,这次重点写notice消息的发送与处理过程。

1、notice_message消息结构

//消息结构 定义部分
struct notice_message {
  notice_message () : known_trx(), known_blocks() {}
  ordered_txn_ids known_trx;
  ordered_blk_ids known_blocks;
};
//选择查看定义,跳转到
using ordered_txn_ids = select_ids<transaction_id_type>;
using ordered_blk_ids = select_ids<block_id_type>;
//选择查看定义,跳转到
template<typename T>
struct select_ids {
  select_ids () : mode(none),pending(0),ids() {}
  id_list_modes  mode;
  uint32_t       pending;
  vector<T>      ids;        //模板类
  bool           empty () const { return (mode == none || ids.empty()); }
};

notice_message消息类型包括id_list_modes类型的变量,该类型为一个枚举类型,包括enum id_list_modes { none, catch_up, last_irr_catch_up, normal };,pending表示区块数目。

2、发送notice_message消息

远程节点给本地节点发送notice_message,通知本地节点同步到不可逆区块。

//远程节点给本地节点发送的通知消息内容 消息调用部分
if (lib_num > msg.head_num ) {
   fc_dlog(logger, "sync check state 2");
   if (msg.generation > 1 || c->protocol_version > proto_base) {
      notice_message note;
      note.known_trx.pending = lib_num;
      note.known_trx.mode = last_irr_catch_up;   //类型为不可逆区块类型
      note.known_blocks.mode = last_irr_catch_up;
      note.known_blocks.pending = head;
      c->enqueue( note );
   }
   c->syncing = true;
   return;
}

远程节点发送的notice_message消息内容比较简单,包括不可逆区块数、最新区块数、同步方式(同步到不可逆区块或者同步到最新区块),然后放入到消息队列等待发送出去。

3、接收notice_message消息

同上篇笔记所写的一样,处理notice_message消息同样在一个handle_message的重载函数里进行,参数为当前通信的远程节点的connect对象指针和消息。

void net_plugin_impl::handle_message( connection_ptr c, const notice_message &msg) {
      // peer tells us about one or more blocks or txns. When done syncing, forward on
      // notices of previously unknown blocks or txns,
      //
      peer_ilog(c, "received notice_message");
      c->connecting = false;
      request_message req;
      bool send_req = false;
      if (msg.known_trx.mode != none) {
         fc_dlog(logger,"this is a ${m} notice with ${n} blocks", ("m",modes_str(msg.known_trx.mode))("n",msg.known_trx.pending));
      }
      switch (msg.known_trx.mode) {
      case none:
         break;
      case last_irr_catch_up: {
         c->last_handshake_recv.head_num = msg.known_trx.pending;
         req.req_trx.mode = none;
         break;
      }
      case catch_up : {
         if( msg.known_trx.pending > 0) {
            // plan to get all except what we already know about.
            req.req_trx.mode = catch_up;
            send_req = true;
            size_t known_sum = local_txns.size();
            if( known_sum ) {
               for( const auto& t : local_txns.get<by_id>( ) ) {
                  req.req_trx.ids.push_back( t.id );
               }
            }
         }
         break;
      }
      case normal: {
         dispatcher->recv_notice (c, msg, false);
      }
      }

      if (msg.known_blocks.mode != none) {
         fc_dlog(logger,"this is a ${m} notice with ${n} blocks", ("m",modes_str(msg.known_blocks.mode))("n",msg.known_blocks.pending));
      }
      switch (msg.known_blocks.mode) {
      case none : {
         if (msg.known_trx.mode != normal) {
            return;
         }
         break;
      }
      case last_irr_catch_up: //同步到最新区块和同步到不可逆区块 调用的函数相同
      case catch_up: {
         sync_master->recv_notice(c,msg); //通过sync_master进行区块同步
         break;
      }
      case normal : {
         dispatcher->recv_notice (c, msg, false);
         break;
      }
      default: {
         peer_elog(c, "bad notice_message : invalid known_blocks.mode ${m}",("m",static_cast<uint32_t>(msg.known_blocks.mode)));
      }
      }
      fc_dlog(logger, "send req = ${sr}", ("sr",send_req));
      if( send_req) {
         c->enqueue(req);
      }
   }

由于远程节点发送的notice_message数据包内容为同步到不可逆区块,mode为last_irr_catch_up,所以代码中调用sync_master->recv_notice(c,msg)函数来进行区块同步。此函数同步区块分为两种情况

   void sync_manager::recv_notice (connection_ptr c, const notice_message &msg) {
      fc_ilog (logger, "sync_manager got ${m} block notice",("m",modes_str(msg.known_blocks.mode)));
      if (msg.known_blocks.mode == catch_up) {
         if (msg.known_blocks.ids.size() == 0) {
            elog ("got a catch up with ids size = 0");
         }
         else {
             //同步到最新区块
            verify_catchup(c,  msg.known_blocks.pending, msg.known_blocks.ids.back());
         }
      }
      else {
         c->last_handshake_recv.last_irreversible_block_num = msg.known_trx.pending;
         reset_lib_num (c);
         //同步到不可逆区块
         start_sync(c, msg.known_blocks.pending);
      }
   }

我们重点看同步到不可逆区块,函数为start_sync,参数为connection对象指针和消息中包含的最新区块数(不过我觉得应该是不可逆区块数)。

   void sync_manager::start_sync( connection_ptr c, uint32_t target) {
      if( target > sync_known_lib_num) {
         sync_known_lib_num = target;
      }

      if (!sync_required()) {
         uint32_t bnum = chain_plug->chain().last_irreversible_block_num();
         uint32_t hnum = chain_plug->chain().fork_db_head_block_num();
         fc_dlog( logger, "We are already caught up, my irr = ${b}, head = ${h}, target = ${t}",
                  ("b",bnum)("h",hnum)("t",target));
         return;
      }

      if (state == in_sync) {
         set_state(lib_catchup);
         sync_next_expected_num = chain_plug->chain().last_irreversible_block_num() + 1;
      }

      fc_ilog(logger, "Catching up with chain, our last req is ${cc}, theirs is ${t} peer ${p}",
              ( "cc",sync_last_requested_num)("t",target)("p",c->peer_name()));

      request_next_chunk(c);
   }

在这里,成员变量sync_known_lib_num赋值为sync_known_lib_num和target的最大值。该成员变量表示已知的当前不可逆区块数,可见上一步同步到不可逆区块的调用应该为start_sync(c, msg.known_trx.pending);,在这个函数里,主要是检查区块同步信息,然后调用request_next_chunk(c)函数进行区块同步。

   void sync_manager::request_next_chunk( connection_ptr conn ) {
      uint32_t head_block = chain_plug->chain().fork_db_head_block_num();
      ······ //省略代码
      if( sync_last_requested_num != sync_known_lib_num ) {
         uint32_t start = sync_next_expected_num;
         uint32_t end = start + sync_req_span - 1; // sync_req_span为配置文件中设置,每次同步多少个区块,默认是100个。
         if( end > sync_known_lib_num )
            end = sync_known_lib_num;
         if( end > 0 && end >= start ) {
            fc_ilog(logger, "requesting range ${s} to ${e}, from ${n}",
                    ("n",source->peer_name())("s",start)("e",end));
            source->request_sync_blocks(start, end);//同样是发送某种消息来同步区块
            sync_last_requested_num = end;
         }
      }
   }

查看request_next_chunk函数的功能,我们发现内部调用request_sync_blocks函数来发送同步消息,具体实现需要进入分析。

void connection::request_sync_blocks (uint32_t start, uint32_t end) {
      sync_request_message srm = {start,end};
      enqueue( net_message(srm));
      sync_wait();
   }

request_sync_blocks函数内部实现很简单,构造了一个sync_request_message类型的消息,然后放入到消息队列中,消息的内容为起始区块和结束区块,每次请求100个(config.ini中默认设置,可以修改)。
此时这篇笔记讨论的消息类型先到这里,下一篇笔记讨论sync_request_message类型的消息,分析本地节点是如何从远程同步区块的。

转载自:https://github.com/RootkitKiller/EosLearn/blob/master/Eos%E4%BB%A3%E7%A0%81%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%EF%BC%88%E4%BA%94%EF%BC%89%E5%90%8C%E6%AD%A5%E5%8C%BA%E5%9D%97--p2p%E9%80%9A%E4%BF%A1--notice_message.md

同步区块--p2p通信--handshake_message

上一篇整体上学习了net_plugin插件,但是具体的内容没有写。从这篇笔记开始,重点分析各节点之间是如何通过p2p插件同步区块的。首先,启动一个节点之后,当前节点向远程节点发送的第一个数据包就是handshake_message类型的(time时间戳类型除外),所以这篇笔记中分析handshake_message类型数据包的发送过程和接收过程。
运行环境:CLion编译器,并配置连接到主网节点。

1、handshake_message消息结构

struct handshake_message {
      uint16_t                   network_version = 0; ///< incremental value above a computed base
      chain_id_type              chain_id; ///< used to identify chain
      fc::sha256                 node_id; ///< used to identify peers and prevent self-connect
      chain::public_key_type     key; ///< authentication key; may be a producer or peer key, or empty
      tstamp                     time;
      fc::sha256                 token; ///< digest of time to prove we own the private key of the key above
      chain::signature_type      sig; ///< signature for the digest
      string                     p2p_address;
      uint32_t                   last_irreversible_block_num = 0;
      block_id_type              last_irreversible_block_id;
      uint32_t                   head_num = 0;
      block_id_type              head_id;
      string                     os;
      string                     agent;
      int16_t                    generation;
   };

握手包内容包括:网络版本、chain_id、node_id、p2p_address、节点名称等配置信息,以及链的状态(当前节点的不可逆区块数、不可逆区块id、最新区块id、最新区块数)。其中区块数是指区块编号(1、2、3......),区块id是指32位的hash值。

2、发送handshake_message消息

启动本地节点之后,会连接到配置文件中的p2p节点,并获取当前链信息、配置信息,然后构建handshake_message包并发送到其他p2p节点。

   void connection::send_handshake( ) {
      handshake_initializer::populate(last_handshake_sent); //填充消息内容
      last_handshake_sent.generation = ++sent_handshake_count;
      fc_dlog(logger, "Sending handshake generation ${g} to ${ep}",
              ("g",last_handshake_sent.generation)("ep", peer_name()));
      enqueue(last_handshake_sent);  //构建完成握手包之后,放入队列中
   }

发送的数据包内容如下:

//大小 348个字节  连接之后发送的报文
0040         5c 01 00 00 00 b6 04 ac a3 76 f2 06 b8 fc   2Ñ\....¶.¬£vò.¸ü
0050   25 a6 ed 44 db dc 66 54 7c 36 c6 c3 3e 3a 11 9f   %¦íDÛÜfT|6ÆÃ>:..
0060   fb ea ef 94 36 42 f0 e9 06 da b2 ea 2d 82 ce 71   ûêï.6Bðé.Ú²ê-.Îq
0070   45 c4 74 df 2f 3f 5f d9 df 11 af 8c 70 62 06 7a   EÄtß/?_Ùß.¯.pb.z
0080   5d de 3e 6b 87 10 22 18 0c 00 00 00 00 00 00 00   ]Þ>k..".........
0090   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
00a0   00 00 00 00 00 00 00 00 00 00 00 aa 8b b8 81 00   ...........ª.¸..
00b0   77 05 00 00 00 00 00 00 00 00 00 00 00 00 00 00   w...............
00c0   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
00d0   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
00e0   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
00f0   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
0100   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
0110   00 00 00 00 00 2d 6d 6f 6f 6e 69 6e 77 61 74 65   .....-mooninwate
0120   72 64 65 4d 61 63 42 6f 6f 6b 2d 50 72 6f 2e 6c   rdeMacBook-Pro.l
0130   6f 63 61 6c 3a 39 38 37 36 20 2d 20 64 61 62 32   ocal:9876 - dab2
0140   65 61 32 00 00 00 00 00 00 00 00 00 00 00 00 00   ea2.............
0150   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
0160   00 00 00 00 00 00 00 01 00 00 00 00 00 00 01 40   ...............@
0170   51 47 47 7a b2 f5 f5 1c da 42 7b 63 81 91 c6 6d   QGGz²õõ.ÚB{c..Æm
0180   2c 59 aa 39 2d 5c 2c 98 07 6c b0 03 6f 73 78 10   ,Yª9-\,..l°.osx.
0190   22 45 4f 53 20 54 65 73 74 20 41 67 65 6e 74 22   "EOS Test Agent"
01a0   01 00  

3、接收handshake_message消息

eos接收到其他节点发送过来的消息后,根据消息类型的不同,重载了不同的handle_message函数,其中handshake_message类型,主要功能包括两方面:
(1)、验证接收到的handshake包内容中的链id、节点id、网络版本等信息。
(2)、对比接收到的消息中链的状态和自身链的状态对比,然后进行区块同步。

//重载后的函数, 处理handshake_message消息
void net_plugin_impl::handle_message( connection_ptr c, const handshake_message &msg) {
      peer_ilog(c, "received handshake_message");
      if (!is_valid(msg)) {
         peer_elog( c, "bad handshake message");
         c->enqueue( go_away_message( fatal_other ));
         return;
      }
      controller& cc = chain_plug->chain();
      uint32_t lib_num = cc.last_irreversible_block_num( );
      uint32_t peer_lib = msg.last_irreversible_block_num;
      if (msg.generation == 1) {
         if( c->peer_addr.empty() || c->last_handshake_recv.node_id == fc::sha256()) {
            fc_dlog(logger, "checking for duplicate" );
            //遍历所有连接的节点,c代表当前连接中的节点 保证同一个p2p节点只存在一个连接
            ········
           }
         }
         if( msg.chain_id != chain_id) {
            elog( "Peer on a different chain. Closing connection");
            c->enqueue( go_away_message(go_away_reason::wrong_chain) );
            return;
         }
          ······
         if(  c->node_id != msg.node_id) {
            c->node_id = msg.node_id;
         }
        ········
      c->last_handshake_recv = msg;
      c->_logger_variant.reset();
      sync_master->recv_handshake(c,msg); //根据链的状态进行同步管理
   }

主要功能在于recv_handshake函数中同步区块管理。

void sync_manager::recv_handshake (connection_ptr c, const handshake_message &msg) {
      controller& cc = chain_plug->chain();
      uint32_t lib_num = cc.last_irreversible_block_num( );
      uint32_t peer_lib = msg.last_irreversible_block_num;
      reset_lib_num(c);
      c->syncing = false;

      //--------------------------------
      // sync need checks; (lib == last irreversible block)
      //
      // 0. my head block id == peer head id means we are all caugnt up block wise
      // 1. my head block num < peer lib - start sync locally
      // 2. my lib > peer head num - send an last_irr_catch_up notice if not the first generation
      //
      // 3  my head block num <= peer head block num - update sync state and send a catchup request
      // 4  my head block num > peer block num ssend a notice catchup if this is not the first generation
      //
      //-----------------------------

      uint32_t head = cc.fork_db_head_block_num( );
      block_id_type head_id = cc.fork_db_head_block_id();
      if (head_id == msg.head_id) {
         fc_dlog(logger, "sync check state 0");
         // notify peer of our pending transactions
         notice_message note;
         note.known_blocks.mode = none;
         note.known_trx.mode = catch_up;
         note.known_trx.pending = my_impl->local_txns.size();
         c->enqueue( note );
         return;
      }
      if (head < peer_lib) {
         fc_dlog(logger, "sync check state 1");
         // wait for receipt of a notice message before initiating sync
         if (c->protocol_version < proto_explicit_sync) {
            start_sync( c, peer_lib);
         }
         return;
      }
      if (lib_num > msg.head_num ) {
         fc_dlog(logger, "sync check state 2");
         if (msg.generation > 1 || c->protocol_version > proto_base) {
            notice_message note;
            note.known_trx.pending = lib_num;
            note.known_trx.mode = last_irr_catch_up;
            note.known_blocks.mode = last_irr_catch_up;
            note.known_blocks.pending = head;
            c->enqueue( note );
         }
         c->syncing = true;
         return;
      }

      if (head <= msg.head_num ) {
         fc_dlog(logger, "sync check state 3");
         verify_catchup (c, msg.head_num, msg.head_id);
         return;
      }
      else {
         fc_dlog(logger, "sync check state 4");
         if (msg.generation > 1 ||  c->protocol_version > proto_base) {
            notice_message note;
            note.known_trx.mode = none;
            note.known_blocks.mode = catch_up;
            note.known_blocks.pending = head;
            note.known_blocks.ids.push_back(head_id);
            c->enqueue( note );
         }
         c->syncing = true;
         return;
      }
      elog ("sync check failed to resolve status");
   }

这里的对比区块信息,主要分为五种情况:
(1)、接收到的最新区块id与自身最新区块id相同。
(2)、自身最新区块数小于接收到的区块的不可逆数。(自己的最新区块高度比远程节点的不可逆区块高度还要低,需要同步。)
(3)、自身不可逆区块数大于接收到的最新区块数。(与(2)相反,通知远程节点,需要同步。发送的是notice_message类型的消息,下一篇笔记中再写。)
(4)、自身最新区块数小于接收到的最新区块数。(需要同步,发送的是request_message消息,后面再写这种消息类型。)
(5)、自身最新区块数大于接收到的最新区块数。(与(4)相反,告通知远程节点,需要同步。发送的是notice_message类型的消息,下一篇笔记中在写。)
以上便是handshake_message消息的发送与接收过程。此篇笔记写到本地节点将自己的配置信息、链的状态告诉远程节点,远程节点接收到这些信息之后,发现自身链比本地节点的链更长,所以给本地节点发送消息同步区块。(消息类型为notice_message,下一篇分析本地节点接收到notice_message类型的消息之后,如何同步区块)。

转载自:https://github.com/RootkitKiller/EosLearn/blob/master/Eos%E4%BB%A3%E7%A0%81%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%EF%BC%88%E5%9B%9B%EF%BC%89%E5%90%8C%E6%AD%A5%E5%8C%BA%E5%9D%97--p2p%E9%80%9A%E4%BF%A1--handshake_message.md

同步区块--p2p通信--sync_request_message与signed_block

这篇笔记写同步区块过程中,发送的最后两种消息类型--sync_request_message与signed_block。上篇笔记写到远程节点将链的信息(不可逆区块数、最新区块数、同步方式等)作为notice_message方式发送给本地节点,本地节点接收到notice_message消息,向远程节点发送sync_request_message消息同步区块。

1、同步区块过程图示

本地节点从远程节点同步不可逆区块

2、sync_request_message消息接收处理

继续上篇笔记中的内容,远程节点接收到本地节点发送过来的sync_request_message消息之后,处理函数:

   void net_plugin_impl::handle_message( connection_ptr c, const sync_request_message &msg) {
      if( msg.end_block == 0) {   //结束区块数是否为0
         c->peer_requested.reset();
         c->flush_queues();
      } else {
         c->peer_requested = sync_state( msg.start_block,msg.end_block,msg.start_block-1);
         c->enqueue_sync_block();
      }
   }

本地节点发送的同步消息,默认为1-100的区块数,则处理函数进入enqueue_sync_block

   bool connection::enqueue_sync_block() {
      controller& cc = app().find_plugin<chain_plugin>()->chain();
      if (!peer_requested)
         return false;
      uint32_t num = ++peer_requested->last;
      bool trigger_send = num == peer_requested->start_block;
      if(peer_requested->last == peer_requested->end_block) {
         peer_requested.reset();
      }
      try {
         signed_block_ptr sb = cc.fetch_block_by_number(num);
         if(sb) {
            enqueue( *sb, trigger_send);
            return true;
         }
      } catch ( ... ) {
         wlog( "write loop exception" );
      }
      return false;
   }

num变量为peer_requested成员的last字段的值,表示当前需要同步的区块数。每次调用enqueue_sync_block函数,则该字段加一,即发送下一个区块给本地节点。然后调用fetch_block_by_number函数获取自己的第num个区块,并构造signed_block消息,然后放入到消息队列,这样就把本地节点请求的一个区块发送给了本地节点。

  boost::asio::async_write(*socket, bufs, [c](boost::system::error_code ec, std::size_t w) {
    try {
      ········//省略代码
        while (conn->out_queue.size() > 0) {
            conn->out_queue.pop_front();
        }
        conn->enqueue_sync_block();    ///同步下一个区块
        conn->do_queue_write();
      }
      ······ //省略代码
   }

在net_plugin插件处理消息队列的时候,会在异步发送消息的回调函数里,发送下一个区块给本地节点。

3、接收signed_block消息

本地节点接收远程节点发送过来的signed_block消息,消息内容为区块详细数据

   void net_plugin_impl::handle_message( connection_ptr c, const signed_block &msg) {
      controller &cc = chain_plug->chain();
      block_id_type blk_id = msg.id();
      uint32_t blk_num = msg.block_num();
      fc_dlog(logger, "canceling wait on ${p}", ("p",c->peer_name()));
      c->cancel_wait();

      try {
         //查看本地是否存在该id的区块
         if( cc.fetch_block_by_id(blk_id)) {
            sync_master->recv_block(c, blk_id, blk_num);
            return;
         }
      } catch( ...) {
         // should this even be caught?
         elog("Caught an unknown exception trying to recall blockID");
      }

      dispatcher->recv_block(c, blk_id, blk_num);
      fc::microseconds age( fc::time_point::now() - msg.timestamp);
      peer_ilog(c, "received signed_block : #${n} block age in secs = ${age}",
              ("n",blk_num)("age",age.to_seconds()));

      go_away_reason reason = fatal_other;
      try {
        //写入区块数据,accept_block函数用到了boost库里面的信号插槽signal2库,这里没有分析清楚。
         signed_block_ptr sbp = std::make_shared<signed_block>(msg);
         chain_plug->accept_block(sbp); //, sync_master->is_active(c));
         reason = no_reason;
      } catch( const unlinkable_block_exception &ex) {
         peer_elog(c, "bad signed_block : ${m}", ("m",ex.what()));
         reason = unlinkable;
      } catch( const block_validate_exception &ex) {
         peer_elog(c, "bad signed_block : ${m}", ("m",ex.what()));
         elog( "block_validate_exception accept block #${n} syncing from ${p}",("n",blk_num)("p",c->peer_name()));
         reason = validation;
      } catch( const assert_exception &ex) {
         peer_elog(c, "bad signed_block : ${m}", ("m",ex.what()));
         elog( "unable to accept block on assert exception ${n} from ${p}",("n",ex.to_string())("p",c->peer_name()));
      } catch( const fc::exception &ex) {
         peer_elog(c, "bad signed_block : ${m}", ("m",ex.what()));
         elog( "accept_block threw a non-assert exception ${x} from ${p}",( "x",ex.to_string())("p",c->peer_name()));
         reason = no_reason;
      } catch( ...) {
         peer_elog(c, "bad signed_block : unknown exception");
         elog( "handle sync block caught something else from ${p}",("num",blk_num)("p",c->peer_name()));
      }

      update_block_num ubn(blk_num);
      if( reason == no_reason ) {
         for (const auto &recpt : msg.transactions) {
            auto id = (recpt.trx.which() == 0) ? recpt.trx.get<transaction_id_type>() : recpt.trx.get<packed_transaction>().id();
            auto ltx = local_txns.get<by_id>().find(id);
            if( ltx != local_txns.end()) {
               local_txns.modify( ltx, ubn );
            }
            auto ctx = c->trx_state.get<by_id>().find(id);
            if( ctx != c->trx_state.end()) {
               c->trx_state.modify( ctx, ubn );
            }
         }
         sync_master->recv_block(c, blk_id, blk_num);
      }
      else {
         sync_master->rejected_block(c, blk_num);
      }
   }

signed_block 消息内容
signed_block 区块报文内容

0040             b9 00 00 00 07 dc f9 5c 45 00 00 00 00 00   .F¹....Üù\E.....
0050   ea 30 55 00 00 00 00 00 01 40 51 47 47 7a b2 f5   ê0U......@QGGz²õ
0060   f5 1c da 42 7b 63 81 91 c6 6d 2c 59 aa 39 2d 5c   õ.ÚB{c..Æm,Yª9-\
0070   2c 98 07 6c b0 00 00 00 00 00 00 00 00 00 00 00   ,..l°...........
0080   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
0090   00 00 00 00 00 e0 24 4d b4 c0 2d 68 ae 64 de c1   .....à$M´À-h®dÞÁ
00a0   60 31 0e 24 7b b0 4e 5c b5 99 af b7 c1 47 10 fb   `1.${°N\µ.¯·ÁG.û
00b0   f3 f4 57 6c 0e 00 00 00 00 00 00 00 20 60 15 f0   óôWl........ `.ð
00c0   39 e2 fd d0 df b2 31 6f ea 28 67 90 c6 b1 55 4f   9âýÐß²1oê(g.ƱUO
00d0   28 5a 54 e7 d2 5d b3 ea 91 ef 2e 8c 11 0a 57 e2   (ZTçÒ]³ê.ï....Wâ
00e0   8e fb ff e0 92 c0 e0 2d 92 f2 88 5e 72 48 43 b4   .ûÿà.Àà-.ò.^rHC´
00f0   a5 5f 29 0e 20 9f 87 1f 41 bb 39 3c 84 00 00      ¥_). ...A»9<...:

转载自:https://github.com/RootkitKiller/EosLearn/blob/master/Eos%E4%BB%A3%E7%A0%81%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%EF%BC%88%E5%85%AD%EF%BC%89%E5%90%8C%E6%AD%A5%E5%8C%BA%E5%9D%97--p2p%E9%80%9A%E4%BF%A1--sync_request_message%E4%B8%8Esigned_block.md