1、通信流程
客户端和服务器端的通信采用RESTful软件架构风格,服务器端的每个资源对应一个唯一的URL地址,客户端将URL地址封装成http请求发送到服务器端,请求对应的资源或者执行相应操作。
cleos与nodeos交互
2、客户端发送消息
programs\cleos\main.cpp
/**
(1)在main函数中,解析transfer命令,通过create_transfer函数将交易发送者、交易接收者、token数量等信息封装成mutable_variant_object对象,
(2)调用send_action函数,将交易信息发送到服务器端打包进区块链
*/
send_actions({create_transfer(con,sender, recipient, to_asset(amount), memo)});
//send_actions函数
void send_actions(std::vector<chain::action>&& actions, int32_t extra_kcpu = 1000, packed_transaction::compression_type compression = packed_transaction::none ) {
auto result = push_actions( move(actions), extra_kcpu, compression);
}
(3)send_actions函数处理
//(1)push_actions
fc::variant push_actions(std::vector<chain::action>&& actions, int32_t extra_kcpu, packed_transaction::compression_type compression = packed_transaction::none ) {
signed_transaction trx;
trx.actions = std::forward<decltype(actions)>(actions);
return push_transaction(trx, extra_kcpu, compression);
}
//(2)push_transaction:发送URL地址到服务器端
fc::variant push_transaction( signed_transaction& trx, int32_t extra_kcpu = 1000, packed_transaction::compression_type compression = packed_transaction::none ) {
auto info = get_info();
trx.expiration = info.head_block_time + tx_expiration;
// Set tapos, default to last irreversible block if it's not specified by the user
block_id_type ref_block_id = info.last_irreversible_block_id;
// 发送 ”/V1/chain/push_transaction” URL地址到服务器端
try {
fc::variant ref_block;
if (!tx_ref_block_num_or_id.empty()) {
ref_block = call(get_block_func, fc::mutable_variant_object("block_num_or_id", tx_ref_block_num_or_id));
ref_block_id = ref_block["id"].as<block_id_type>();
}
//(3)
template<typename T>
fc::variant call( const std::string& url,
const std::string& path,
const T& v ) {
try {
eosio::client::http::connection_param *cp = new eosio::client::http::connection_param(context, parse_url(url) + path,
no_verify ? false : true, headers);
return eosio::client::http::do_http_call( *cp, fc::variant(v), print_request, print_response );
}
catch(b
//(4)programs\cleos\httpc.cpp
fc::variant do_http_call( const connection_param& cp,
const fc::variant& postdata,
bool print_request,
bool print_response ) {
std::string postjson;
if( !postdata.is_null() ) {
postjson = print_request ? fc::json::to_pretty_string( postdata ) : fc::json::to_string( postdata );
}
const auto& url = cp.url;
boost::asio::streambuf request;
std::ostream request_stream(&request);
auto host_header_value = format_host_header(url);
// 将请求的URL封装成http包
request_stream << "POST " << url.path << " HTTP/1.0\r\n";
request_stream << "Host: " << host_header_value << "\r\n";
request_stream << "content-length: " << postjson.size() << "\r\n";
request_stream << "Accept: */*\r\n";
request_stream << "Connection: close\r\n";
request_stream << "\r\n";
....
//和服务器建立连接
do_connect(socket.next_layer(), url);
socket.handshake(boost::asio::ssl::stream_base::client);
//发送http报文,并获取返回结果
re = do_txrx(socket, request, status_code);
//try and do a clean shutdown; but swallow if this fails (other side could have already gave TCP the ax)
try {socket.shutdown();} catch(...) {}
3、服务器接收消息流程
eos/programs/nodeos/main.cpp
//plugins/http_plugin/http_plugin.cpp
http_plugin::http_plugin():my(new http_plugin_impl()){}
void http_plugin::plugin_startup() {
if(my->listen_endpoint) {
try {
//(1)注册http请求处理函数
my->create_server_for_endpoint(*my->listen_endpoint, my->server);
ilog("start listening for http requests");
my->server.listen(*my->listen_endpoint);
my->server.start_accept();
} catch ( const fc::exception& e ){
;
}
}
if(my->https_listen_endpoint) {
try {
my->create_server_for_endpoint(*my->https_listen_endpoint, my->https_server);
my->https_server.set_tls_init_handler([this](websocketpp::connection_hdl hdl) -> ssl_context_ptr{
return my->on_tls_init(hdl);
});
ilog("start listening for https requests");
//(2)监听socket通信端口
my->https_server.listen(*my->https_listen_endpoint);
//(3)等待建立客户端远程连接
my->https_server.start_accept();
} catch ( const fc::exception& e ){
//(4)http请求处理函数从http报文中解析出URL地址(resource)、消息内容(body)
template<class T>
void create_server_for_endpoint(const tcp::endpoint& ep, websocketpp::server<detail::asio_with_stub_log<T>>& ws) {
try {
...
ws.set_http_handler([&](connection_hdl hdl) {
handle_http_request<T>(ws.get_con_from_hdl(hdl));
});
} ca
}
map<string,url_handler> url_handlers;
template<class T>
void handle_http_request(typename websocketpp::server<detail::asio_with_stub_log<T>>::connection_ptr con) {
try {
bool is_secure = con->get_uri()->get_secure();
const auto& local_endpoint = con->get_socket().lowest_layer().local_endpoint();
auto local_socket_host_port = local_endpoint.address().to_string() + ":" + std::to_string(local_endpoint.port());
auto& req = con->get_request();
const auto& host_str = req.get_header("Host");
if (host_str.empty() || !host_is_valid(host_str, local_socket_host_port, is_secure)) {
con->set_status(websocketpp::http::status_code::bad_request);
return;
}
if( !access_control_allow_origin.empty()) {
con->append_header( "Access-Control-Allow-Origin", access_control_allow_origin );
...
con->append_header( "Content-type", "application/json" );
auto body = con->get_request_body();
auto resource = con->get_uri()->get_resource();
//(5)在url_handlers集合中查找URL对应的回调函数
auto handler_itr = url_handlers.find( resource );
if( handler_itr != url_handlers.end()) {
con->defer_http_response();
//(6)通过handler_itr->second调用处理函数
handler_itr->second( resource, body, [con]( auto code, auto&& body ) {
con->set_body( std::move( body ));
con->set_status( websocketpp::http::status_code::value( code ));
con->send_http_response();
} );
} else {
wlog( "404 - not found: ${ep}", ("ep", resource));
error_results results{websocketpp::http::status_code::not_found,
"Not Found", error_results::error_info(fc::exception( FC_LOG_MESSAGE( error, "Unknown Endpoint" )), verbose_http_errors )};
con->set_body( fc::json::to_string( results ));
con->set_status( websocketpp::http::status_code::not_found );
}
} catch( ... ) {
handle_exception<T>( con );
}
}
4、注册URL处理函数说明
//(1)url_handlers是一个URL和处理函数的键值对map集合,由class http\_plugin\_impl管理
//plugins/http_plugin/http_plugin.cpp
class http_plugin_impl {
map<string,url_handler> url_handlers;
//(2)其它插件模块通过add_api函数注册URL回调函数。
//plugins/http_plugin/include/eosio/http_plugin/http_plugin.hpp
class http_plugin : public appbase::plugin<http_plugin>
{
public:
void add_handler(const string& url, const url_handler&);
void add_api(const api_description& api) {
for (const auto& call : api)
add_handler(call.first, call.second);
}
//实现:plugins/http_plugin/include/eosio/http_plugin/http_plugin.cpp
void http_plugin::add_handler(const string& url, const url_handler& handler) {
ilog( "add api url: ${c}", ("c",url) );
app().get_io_service().post([=](){
my->url_handlers.insert(std::make_pair(url,handler));
});
}
/**(3)eg:chain_api_plugin插件在启动函数中注册了以下URL回调函数,包括查询区块信息、处理交易数据:
plugins/chain_api_plugin/chain_api_plugin.cpp */
#define CHAIN_RO_CALL(call_name) CALL(history, ro_api, history_apis::read_only, call_name)
void chain_api_plugin::plugin_startup() {
ilog( "starting chain_api_plugin" );
my.reset(new chain_api_plugin_impl(app().get_plugin<chain_plugin>().chain()));
auto ro_api = app().get_plugin<chain_plugin>().get_read_only_api();
auto rw_api = app().get_plugin<chain_plugin>().get_read_write_api();
app().get_plugin<http_plugin>().add_api({
CHAIN_RO_CALL(get_info, 200l),
CHAIN_RO_CALL(get_block, 200),
CHAIN_RO_CALL(get_block_header_state, 200),
CHAIN_RO_CALL(get_account, 200),
CHAIN_RO_CALL(get_code, 200),
CHAIN_RO_CALL(get_abi, 200),
CHAIN_RO_CALL(get_raw_code_and_abi, 200),
CHAIN_RO_CALL(get_table_rows, 200),
CHAIN_RO_CALL(get_currency_balance, 200),
CHAIN_RO_CALL(get_currency_stats, 200),
CHAIN_RO_CALL(get_producers, 200),
CHAIN_RO_CALL(get_producer_schedule, 200),
CHAIN_RO_CALL(get_scheduled_transactions, 200),
CHAIN_RO_CALL(abi_json_to_bin, 200),
CHAIN_RO_CALL(abi_bin_to_json, 200),
CHAIN_RO_CALL(get_required_keys, 200),
CHAIN_RW_CALL_ASYNC(push_block, chain_apis::read_write::push_block_results, 202),
CHAIN_RW_CALL_ASYNC(push_transaction, chain_apis::read_write::push_transaction_results, 202),
CHAIN_RW_CALL_ASYNC(push_transactions, chain_apis::read_write::push_transactions_results, 202)
});
}
5、生产区块流程
客户端发送 ”/V1/chain/push_transaction” URL地址和交易信息到服务器端,然后服务器调用URL对应的回调函数push_transaction将交易信息写入到一个待打包的区块(_pending_block)中。
位置:libraries/chain/controller.cpp
/**
* This is the entry point for new transactions to the block state. It will check authorization and
* determine whether to execute it now or to delay it. Lastly it inserts a transaction receipt into
* the pending block.
*/
transaction_trace_ptr push_transaction( const transaction_metadata_ptr& trx,
fc::time_point deadline,
bool implicit,
uint32_t billed_cpu_time_us)
{
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);
if ((bool)subjective_cpu_leeway && pending->_block_status == controller::block_status::incomplete) {
trx_context.leeway = *subjective_cpu_leeway;
}
trx_context.deadline = deadline;
trx_context.billed_cpu_time_us = billed_cpu_time_us;
trace = trx_context.trace;
try {
if( implicit ) {
trx_context.init_for_implicit_trx();
trx_context.can_subjectively_fail = false;
} else {
trx_context.init_for_input_trx( trx->packed_trx.get_unprunable_size(),
trx->packed_trx.get_prunable_size(),
trx->trx.signatures.size());
}
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);
if( !self.skip_auth_check() && !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(); // Automatically rounds up network and CPU usage in trace and bills payers if successful
auto restore = make_block_restore_point();
if (!implicit) {
transaction_receipt::status_enum s = (trx_context.delay == fc::seconds(0))
? transaction_receipt::executed
: transaction_receipt::delayed;
trace->receipt = push_receipt(trx->packed_trx, s, trx_context.billed_cpu_time_us, trace->net_usage);
pending->_pending_block_state->trxs.emplace_back(trx);
} else {
transaction_receipt_header r;
r.status = transaction_receipt::executed;
r.cpu_usage_us = trx_context.billed_cpu_time_us;
r.net_usage_words = trace->net_usage / 8;
trace->receipt = r;
}
fc::move_append(pending->_actions, move(trx_context.executed));
// call the accept signal but only once for this transaction
if (!trx->accepted) {
emit( self.accepted_transaction, trx);
trx->accepted = true;
}
emit(self.applied_transaction, trace);
if ( read_mode != db_read_mode::SPECULATIVE && pending->_block_status == controller::block_status::incomplete ) {
//this may happen automatically in destructor, but I prefere make it more explicit
trx_context.undo();
} else {
restore.cancel();
trx_context.squash();
}
if (!implicit) {
unapplied_transactions.erase( trx->signed_id );
}
return trace;
} catch (const fc::exception& e) {
trace->except = e;
trace->except_ptr = std::current_exception();
}
if (!failure_is_subjective(*trace->except)) {
unapplied_transactions.erase( trx->signed_id );
}
return trace;
} FC_CAPTURE_AND_RETHROW((trace))
} /// push_transaction
5.2 区块数据结构
libraries\chain\include\eosio\chain\block.hpp
libraries\chain\include\eosio\chain\block_header.hpp
libraries\chain\block_header.cpp