美文网首页
EOS解读--插件通信

EOS解读--插件通信

作者: 脆果咒 | 来源:发表于2019-01-18 20:38 被阅读0次

    整体插件

    image.png

    文件目录说明如下:

    1、contracts目录

    这个目录包含了EOS基础合约和示例合约,以及这些合约使用的库:eosiolib库、libc++库、musl库;

    其中eosiolib库是EOS官方编写的库,libc++库、musl库是外部库。

    2、plugins目录

    EOS采用插件化设计,这个目录包含了EOS项目所用到的插件。

    3、libraries目录

    这个目录包含了具体实现代码需要引用的基础类库:

    • abi-generator:生成ABI的类库

    • appbase:application基础类库

    • chain、chainbase:区块链基础类库

    • fc:通用函数库

    • wasm-jit:WebAssembly JIT编译库

    4、programs目录

    这个目录包含EOS最终构建的应用程序(eos/build/programs目录下)的源码:

    • cleos:命令行客户端

    • nodeos:服务端守护进程

    • keosd:钱包守护进程

    • eosio-abigen:ABI生成器

    • eosio-launcher:多节点启动器

    • snapshot:配置创世区块和创始相关工具的Web程序

    5、governance目录

    这个目录包含的不是源代码,而是EOS社区治理文件,觉得比较重要,提到这里:

    • bp_agreement.md:区块生产者需要遵守的协议

    • constitution.md:EOS宪法

    6、externals目录

    这个目录包含的是外部项目:

    EOS采用插件化设计,插件是一种高度解耦的设计模式,它把不同的程序功能模块独立开来,需要时再进行自由组合;

    插件的原理是:

    • 所有插件继承自同一个基类,这个基类会定义一系列生命周期函数,插件子类需要实现这些函数;

    • 一般会有插件管理器来管理插件,比如注册到主程序中,或从主程序中注销;

    • 主程序也会定义一系列生命周期函数,这些函数内部一般是对注册了的插件进行遍历,调用它们公共接口的函数;

    • 这样主程序和插件就绑定到了一起,主程序一个生命周期函数的调用,会让注册了的插件的对应生命周期函数都得到调用;

    • 不需要使用插件时可以注销,这样就实现了即插即用的灵活设计。

    3、所有插件(plugin)都派生自同一个基类:appbase::abstract_plugin,它的位置在:eos/libraries/appbase/include/appbase/plugin.hpp;

    4、appbase::abstract_plugin是一个抽象类,有一个继承它的实现类:appbase::plugin,位于eos/libraries/appbase/include/appbase/application.hpp文件中;

    5、其他所有插件都会继承appbase::plugin类,它们的继承关系如下:

    image

    plugin的生命周期

    每个插件都会经历如下生命周期:

    注册(register_plugin)

    • 配置(set_program_options)

    • 初始化(plugin_initialize)

    • 启动(plugin_startup)

    • 关闭(plugin_shutdown)

    插件如何通信

    一、执行命令行创建账号

         1、创建钱包,导入秘钥
    
         2、创建账号
    

    cleos create account eosio test EOS5VZk1B9HqPNypN9XczvrtT6AkaDgZafyAcD6xovh63uC133stj
    
    

    二、代码流程

    堆栈图:

    image.png

    1、cleos解析命令行,组织并发送http协议(参见https://blog.csdn.net/weichanghu_/article/details/81414529)。

    cleos create account eosio test EOS5VZk1B9HqPNypN9XczvrtT6AkaDgZafyAcD6xovh63uC133stj
    
    
    
    此处简单介绍一下代码流程:
    
    main → send_actions → push_actions → push_transaction → call → do_http_call → (httpc.cpp)do_connect → do_txrx
    
    
            使用Clion调试时会发现cleos最终发送的是push_transaction的POST请求。
    
            2、nodeos加载http_plugin插件并注册处理函数:
    
    main → app().startup() → plugin->startup() → (chain_api_plugin.cpp) plugin_startup → (http_plugin.hpp) 
    add_api → add_handler → url_handlers.insert()
    
             其中add_api对push_transaction消息的处理函数是read_write::push_transaction,代码如下:
    
    CHAIN_RW_CALL_ASYNC(push_transaction, chain_apis::read_write::push_transaction_results, 202)
             3、http_plugin插件监听端口,接收消息并调用处理函数
    
    (http_plugin.cpp)plugin_startup → create_server_for_endpoint → handle_http_request → handle_http_request →  
    handler_itr->second()
             所以,nodeos收到push_transaction请求后就调用了read_write::push_transaction,该函数代码如下:
    void read_write::push_transaction(const read_write::push_transaction_params& params, next_function<read_write::push_transaction_results> next) {
    
     
    
       try {
    
          auto pretty_input = std::make_shared<packed_transaction>();
    
          auto resolver = make_resolver(this, abi_serializer_max_time);
    
          try {
    
             abi_serializer::from_variant(params, *pretty_input, resolver, abi_serializer_max_time);
    
          } EOS_RETHROW_EXCEPTIONS(chain::packed_transaction_type_exception, "Invalid packed transaction")
    
     
    
          app().get_method<incoming::methods::transaction_async>()(pretty_input, true, [this, next](const fc::static_variant<fc::exception_ptr, transaction_trace_ptr>& result) -> void{
    
             if (result.contains<fc::exception_ptr>()) {
    
                next(result.get<fc::exception_ptr>());
    
             } else {
    
                auto trx_trace_ptr = result.get<transaction_trace_ptr>();
    
     
    
                try {
    
                   fc::variant pretty_output;
    
                   pretty_output = db.to_variant_with_abi(*trx_trace_ptr, abi_serializer_max_time);
    
     
    
                   chain::transaction_id_type id = trx_trace_ptr->id;
    
                   next(read_write::push_transaction_results{id, pretty_output});
    
                } CATCH_AND_CALL(next);
    
             }
    
          });
    
     
    
     
    
       } catch ( boost::interprocess::bad_alloc& ) {
    
          raise(SIGUSR1);
    
       } CATCH_AND_CALL(next);
    
    }
    
    
       函数先将交易数据封装成packed_transaction对象,然后通过信号槽的异步方法app().get_method<incoming::methods::transaction_async>()将该对象传给了producer_plugin,该插件在初始化的时候会注册处理函数on_incoming_transaction_async()接收交易数据,
     my->_incoming_transaction_async_provider = app().get_method<incoming::methods::transaction_async>().register_provider([this](const packed_transaction_ptr& trx, bool persist_until_expired, next_function<transaction_trace_ptr> next) -> void {
    
          return my->on_incoming_transaction_async(trx, persist_until_expired, next );
    
       });
    
    
    并在这一步将交易数据传给了controller.cpp里的push_transaction函数,
    
    auto trace = chain.push_transaction(std::make_shared<transaction_metadata>(*trx), deadline);
           4、controller.cpp里的push_transaction函数进一步处理该请求,接收的transaction_metadata_ptr就是交易数据:
    
     transaction_trace_ptr push_transaction( const transaction_metadata_ptr& trx,
    
                                               fc::time_point deadline,
    
                                               uint32_t billed_cpu_time_us,
    
                                               bool explicit_billed_cpu_time = false )
    
       {
    
          EOS_ASSERT(deadline != fc::time_point(), transaction_exception, "deadline cannot be uninitialized");
    
     
    
          transaction_trace_ptr trace;
    
          try {
    
             transaction_context trx_context(self, trx->trx, trx->id);
    
     
    
            。。。。。。
    
     
    
                trx_context.exec();
    
     
    
            。。。。。。
    
     
    
                emit(self.applied_transaction, trace);
    
     
    
            。。。。。。
    
     
    
             emit( self.accepted_transaction, trx );
    
             emit( self.applied_transaction, trace );
    
     
    
             return trace;
    
          } FC_CAPTURE_AND_RETHROW((trace))
    
       } /// push_transaction
    

    该函数进入trx_context.exec(),并由此函数内的dispatch_action()函数将action分发下去进入acontext.exec()函数,该函数执行 trace = exec_one(),代码如下:

    action_trace apply_context::exec_one()
    
    {
    
       auto start = fc::time_point::now();
    
     
    
       const auto& cfg = control.get_global_properties().configuration;
    
       try {
    
          const auto& a = control.get_account( receiver );
    
          privileged = a.privileged;
    
          auto native = control.find_apply_handler( receiver, act.account, act.name );
    
          if( native ) {
    
             if( trx_context.can_subjectively_fail && control.is_producing_block()) {
    
                control.check_contract_list( receiver );
    
                control.check_action_list( act.account, act.name );
    
             }
    
             (*native)( *this );
    
          }
    
        。。。。。。
    
       }
    
       。。。。。。。
    
    }
    
    

    最终在该函数内查找对应的处理函数find_apply_handler(),

    const apply_handler* controller::find_apply_handler( account_name receiver, account_name scope, action_name act ) const
    
    {
    
       auto native_handler_scope = my->apply_handlers.find( receiver );
    
       if( native_handler_scope != my->apply_handlers.end() ) {
    
          auto handler = native_handler_scope->second.find( make_pair( scope, act ) );
    
          if( handler != native_handler_scope->second.end() )
    
             return &handler->second;
    
       }
    
       return nullptr;
    
    }
    
    

    apply_handlers定义和上面提到的url_handlers类似,也是个map:

    map< account_name, map<handler_key, apply_handler> > apply_handlers;
    该map在初始化的时候也插入了一堆处理函数,并且按接受者、合约、动作分类,如下所示:

    #define SET_APP_HANDLER( receiver, contract, action) \
    
       set_apply_handler( #receiver, #contract, #action, &BOOST_PP_CAT(apply_, BOOST_PP_CAT(contract, BOOST_PP_CAT(_,action) ) ) )
    
     
    
       SET_APP_HANDLER( eosio, eosio, newaccount );
    
       SET_APP_HANDLER( eosio, eosio, setcode );
    
       SET_APP_HANDLER( eosio, eosio, setabi );
    
       SET_APP_HANDLER( eosio, eosio, updateauth );
    
       SET_APP_HANDLER( eosio, eosio, deleteauth );
    
       SET_APP_HANDLER( eosio, eosio, linkauth );
    
       SET_APP_HANDLER( eosio, eosio, unlinkauth );
    
    /*
    
       SET_APP_HANDLER( eosio, eosio, postrecovery );
    
       SET_APP_HANDLER( eosio, eosio, passrecovery );
    
       SET_APP_HANDLER( eosio, eosio, vetorecovery );
    
    */
    
     
    
       SET_APP_HANDLER( eosio, eosio, canceldelay );
    
    void set_apply_handler( account_name receiver, account_name contract, action_name action, apply_handler v ) {
    
          apply_handlers[receiver][make_pair(contract,action)] = v;
    
       }
    
    

    最终调用的处理函数就是eosio智能合约里面的名叫newaccount的action,也就是apply_eosio_newaccount函数:

    void apply_eosio_newaccount(apply_context& context) {
    
       auto create = context.act.data_as<newaccount>();
    
       try {
    
       context.require_authorization(create.creator);
    
    //   context.require_write_lock( config::eosio_auth_scope );
    
       auto& authorization = context.control.get_mutable_authorization_manager();
    
     
    
       EOS_ASSERT( validate(create.owner), action_validate_exception, "Invalid owner authority");
    
       EOS_ASSERT( validate(create.active), action_validate_exception, "Invalid active authority");
    
     
    
       auto& db = context.db;
    
     
    
       auto name_str = name(create.name).to_string();
    
     
    
       EOS_ASSERT( !create.name.empty(), action_validate_exception, "account name cannot be empty" );
    
       EOS_ASSERT( name_str.size() <= 12, action_validate_exception, "account names can only be 12 chars long" );
    
     
    
       // Check if the creator is privileged
    
       const auto &creator = db.get<account_object, by_name>(create.creator);
    
       if( !creator.privileged ) {
    
          EOS_ASSERT( name_str.find( "eosio." ) != 0, action_validate_exception,
    
                      "only privileged accounts can have names that start with 'eosio.'" );
    
       }
    
     
    
       auto existing_account = db.find<account_object, by_name>(create.name);
    
       EOS_ASSERT(existing_account == nullptr, account_name_exists_exception,
    
                  "Cannot create account named ${name}, as that name is already taken",
    
                  ("name", create.name));
    
     
    
       const auto& new_account = db.create<account_object>([&](auto& a) {
    
          a.name = create.name;
    
          a.creation_date = context.control.pending_block_time();
    
       });
    
     
    
       db.create<account_sequence_object>([&](auto& a) {
    
          a.name = create.name;
    
       });
    
     
    
       for( const auto& auth : { create.owner, create.active } ){
    
          validate_authority_precondition( context, auth );
    
       }
    
     
    
       const auto& owner_permission  = authorization.create_permission( create.name, config::owner_name, 0,
    
                                                                        std::move(create.owner) );
    
       const auto& active_permission = authorization.create_permission( create.name, config::active_name, owner_permission.id,
    
                                                                        std::move(create.active) );
    
     
    
       context.control.get_mutable_resource_limits_manager().initialize_account(create.name);
    
     
    
       int64_t ram_delta = config::overhead_per_account_ram_bytes;
    
       ram_delta += 2*config::billable_size_v<permission_object>;
    
       ram_delta += owner_permission.auth.get_billable_size();
    
       ram_delta += active_permission.auth.get_billable_size();
    
     
    
       context.trx_context.add_ram_usage(create.name, ram_delta);
    
     
    
    } FC_CAPTURE_AND_RETHROW( (create) ) }
    
    

    相关文章

      网友评论

          本文标题:EOS解读--插件通信

          本文链接:https://www.haomeiwen.com/subject/dljodqtx.html