• 评论
  • 0人喜欢
  • 分享到
  • 微信
    打开微信“扫一扫”,打开网页后点击屏幕右上角“分享”按钮
  • 空间
  • 微博
  • twitter
  • facebook

EOS 代码架构及分析(二)

EOS 通信机制分析

客户端和服务器端的通信采用 RESTful 软件架构风格,服务器端的每个资源对应一个唯一的 URL 地址,客户端将 URL 地址封装成 http 请求发送到服务器端,请求对应的资源或者执行相应操作。

REST

客户端发送消息流程

以转账为例,说明 EOS 消息处理流程。通过 cleos 客户端发起转账命令,在 main 函数中,解析 transfer 命令,通过 create_transfer 函数将交易发送者、交易接收者、token 数量等信息封装成 mutable_variant_object 对象,然后调用 send_action 函数,将交易信息发送到服务器端打包进区块链。

./cleos transfer sender recipient amount memo

programs/cleos/main.cpp

main()

{

send_actions({create_transfer(sender, recipient, amount, memo)});

}

void send_actions {

    auto result = push_actions( move(actions), extra_kcpu, compression);

    …

}

fc::variant push_actions {

   signed_transaction trx;

trx.actions = std::forward(actions);

return push_transaction(trx, extra_kcpu, compression);

}

fc::variant push_transaction{

trx.set_reference_block(ref_block_id);

// 发送 ”/V1/chain/push_transaction” URL 地址到服务器端

if (!tx_dont_broadcast) {

     return call(push_txn_func, packed_transaction(trx, compression));

 }

}

fc::variant call{

   try {

          return eosio::client::http::do_http_call( url, path,

fc::variant(v) );

    }

}

fc::variant do_http_call {

   // 将请求的 URL 封装成 http 包

   request_stream << “POST ” << path_prefix + path << ” HTTP/1.0\r\n”;

request_stream << “Host: ” << server << “\r\n”;

request_stream << “content-length: ” << postjson.size() << “\r\n”;

request_stream << “Accept: /\r\n”;

request_stream << “Connection: close\r\n\r\n”;

request_stream << postjson;

   // 和服务器建立连接

   do_connect(socket, server, port);

   // 发送 http 报文,并获取返回结果

   re = do_txrx(socket, request, status_code);

}

服务器接收消息流程

nodeos 服务器先通过 http_plugin 插件接收客户端发过来的 http 请求报文,然后解析出请求的 URL 地址和数据信息,然后调用对应的回调函数处理,并将结果返回给 cleos 客户端。

# ./nodeos -e -p eosio 消息通信流程图HTTP 消息处理流程

在 nodeos 的 main 函数中启动 http_plugin 插件,注册处理 http 请求的回调函数(handle_http_request),然后监听 socket 通信端口,等待建立客户端远程连接。

void http_plugin::plugin_startup() {

   // 注册 http 请求处理函数

   my->create_server_for_endpoint(*my->https_listen_endpoint,

my->https_server);

// 监听 socket 通信端口

my->https_server.listen(*my->https_listen_endpoint);

// 等待建立客户端远程连接

my->https_server.start_accept();

}

void create_server_for_endpoint{

   ws.set_http_handler([&](connection_hdl hdl) {

handle_http_request(ws.get_con_from_hdl(hdl));

   });

}

http 请求处理函数从 http 报文中解析出 URL 地址(resource)、消息内容(body),然后在 url_handlers 集合中查找 URL 对应的回调函数,最后通过 handler_itr->second 调用处理函数。

void handle_http_request {

   …

   auto body = con->get_request_body();

auto resource = con->get_uri()->get_resource();

auto handler_itr = url_handlers.find(resource);

if(handler_itr != url_handlers.end()) {

          handler_itr->second(resource, body, [con](int code, string body)

{

                 con->set_body(body);

con->set_status(websocketpp::http::status_code::value(code));

});

   }

   …

}

注册 URL 处理函数

url_handlers 是一个 URL 和处理函数的键值对 map 集合,由 class http_plugin_impl 管理,其它插件模块通过 add_api 函数注册 URL 回调函数。

plugins/http_plugin/http_plugin.cpp

class http_plugin_impl {

   map  url_handlers;

}

void add_api(const api_description& api) {

   for (const auto& call : api)

add_handler(call.first, call.second);

}

void http_plugin::add_handler {

   

   my->url_handlers.insert(std::make_pair(url,handler);

}

例如,chain_api_plugin 插件在启动函数中注册了以下 URL 回调函数,包括查询区块信息、处理交易数据:

void chain_api_plugin::plugin_startup() {

   app().get_plugin().add_api({

CHAIN_RO_CALL(get_info, 200),

CHAIN_RO_CALL(get_block, 200),

          …

          CHAIN_RW_CALL(push_transaction, 202),

CHAIN_RW_CALL(push_transactions, 202)

});

}

生产区块流程

客户端发送 ”/V1/chain/push_transaction”
URL 地址和交易信息到服务器端,然后服务器调用 URL 对应的回调函数 push_transaction 将交易信息写入到一个待打包的区块(_pending_block)中。

chain_controller::push_transaction {

   if( !_pending_block ) {

_start_pending_block();

   }

   …

   return _push_transaction(trx);

}

chain_controller::_push_transaction {

   _pending_block->input_transactions.emplace_back(packed_trx);

}

一个区块可以包含很多个 transaction,通过 input_transactions 将这些 transaction 管理起来,随后由 producer_plugin 插件将区块打包进区块链中,然后向其它 nodeos 节点广播区块信息。

struct signed_block : public signed_block_summary {

   …

   vector   input_transactions;

}

producer_plugin 插件启动后,通过 schedule_production_loop 函数循环生产区块。EOS 采用 DPoS (委托股权证明)算法,先由 EOS 持有者(股东)选出 21 个区块生产者(董事会成员),区块通过这 21 个生产者轮流产生,每 3 秒出一个区块,类似操作系统的时间片概念,每个时间片对应一个唯一的生产者,当时间片到来时才能打包区块。![DPoS](http://cdn.8btc.com
/wp-content/uploads/2018/05/201805111325146196.png)

DPoS 算法和比特币的 POW 算法有很大区别,在 POW 算法中,矿工只要发现交易信息就开始打包区块,而且需要消耗巨大的算力,而且交易确认时间很长。而 DPoS 算法则通过提前选举出可信节点,避免了信任证明的开销,同时生产者数量的减少(21 个)也极大提升了交易确认效率,防止性能差的节点拖慢整个区块链生产速度。DPoS 的时间片机制能够保证可信区块链的长度始终比恶意分叉的区块链长(恶意节点数量不大于 1/3 总节点数量),例如,节点 B 想自己构造一个分叉链,但是由于每 9 秒才能产生一个区块,所以始终没有主链长。

DPoS 分叉一

void producer_plugin::plugin_startup()

{

my->schedule_production_loop();

}

计算出现在距离下一个区块时间片的时间间隔 time_to_next_block_time,然后启动一个定时器,当下一个时间片到来时调用 block_production_loop 函数生产区块。

void producer_plugin_impl::schedule_production_loop() {

   int64_t time_to_next_block_time = (config::block_interval_us) –

(now.time_since_epoch().count() % (config::block_interval_us) );

_timer.expires_from_now(
boost::posix_time::microseconds(time_to_next_block_time) );

_timer.async_wait( &{
block_production_loop(); } );

}

调用 maybe_produce_block 函数生产区块,从函数名的 maybe 可知,不一定能够生产出区块,只是进行尝试,然后处理结果,最后递归调用 schedule_production_loop 函数进入下一次循环。

producer_plugin_impl::block_production_loop() {

   result = maybe_produce_block(capture);

   schedule_production_loop();

   return result;

}

获取当前时间对应的生产者,然后调用 chain.generate_block 函数生产区块,完成后通过 broadcast_block 函数向其它节点广播区块信息。

producer_plugin_impl::maybe_produce_block {

   uint32_t slot = chain.get_slot_at_time( now );

   …

   auto scheduled_producer = chain.get_scheduled_producer( slot );

   …

   auto block = chain.generate_block(

scheduled_time,

scheduled_producer,

private_key_itr->second,

_production_skip_flags

);

app().get_plugin().broadcast_block(block);

return block_production_condition::produced;

}

chain_controller::generate_block(

   …

   return _generate_block( when, producer, block_signing_private_key );

}

    将生产者信息更新到之前的待打包区块 _pending_block 中,例如,区块时间戳、区块编号、生产者状态等等,最后将区块写入本地区块链中。

chain_controller::_generate_block {

    _pending_block->timestamp   = when;

_pending_block->producer = producer_obj.owner;

_pending_block->previous = head_block_id();

if( !(skip & skip_producer_signature) )

_pending_block->sign( block_signing_key );

_finalize_block( *_pending_block_trace, producer_obj );

}

至此,一次完整的区块处理流程就完成了,后面不断重复打包过程,随着时间推移,形成一个不可逆转的区块链。

相关资讯
EOS Asia & 思否区块链达成战略合作
深度分析:央行数字货币(CBDC)的前景及全球分布式加密货币带来的挑战
Fabric基础架构原理(3):通道
想抓住 EOS 的机会,从这四个方向入手吧!
区块链智能手机存在的可能性分析!
浅析EOS系统资源
分析:BTC日元交易量占比第一或因计入衍生品交易额
欧洲银行管理局发布分布式账本技术(DLT) 分析报告
硬核访谈丨“寻找代码的圣杯”—程序能证明自己没有 bug 吗?