EOS多节点同步代码分析
EOS version: 1.0.7
一. 配置文件的修改
EOS的节点同步流程是通过p2p来完成,在nodeos的配置文件config.ini中填写,其默认路径为~/.local/share/eosio/nodeos/config目录下,配置项及其格式如下:
p2p-peer-address = 10.186.11.223:
p2p-peer-address = 10.186.11.220:
p2p-peer-address = 10.186.11.141:
可以填写多个p2p站点地址。
二.节点同步的chain_id
每一个节点都唯一分配一个chain_id,如果两个节点的chian_id不相等的话,是无法进行同步的,代码中处理如下:
void net_plugin_impl::handle_message( connection_ptr c, const handshake_message &msg) {
...
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;
}
...
}
那么这个chain_id是如何开成的?
chain_id在chain_plugin中定义,在net_plugin中使用,在chain_plugin中如下定义
//controller.cpp
chain_id( cfg.genesis.compute_chain_id() )
//genesis_state.cpp
chain::chain_id_type genesis_state::compute_chain_id() const {
digest_type::encoder enc;
fc::raw::pack( enc, *this );
return chain_id_type{enc.result()};
}
这里相当于把整个genesis的数据做了一个类似hash的操作,默认情况下genesis的数据在代码中填写:
chain_config initial_configuration = {
.max_block_net_usage = config::default_max_block_net_usage,
.target_block_net_usage_pct = config::default_target_block_net_usage_pct,
.max_transaction_net_usage = config::default_max_transaction_net_usage,
.base_per_transaction_net_usage = config::default_base_per_transaction_net_usage,
.net_usage_leeway = config::default_net_usage_leeway,
.context_free_discount_net_usage_num = config::default_context_free_discount_net_usage_num,
.context_free_discount_net_usage_den = config::default_context_free_discount_net_usage_den,
.max_block_cpu_usage = config::default_max_block_cpu_usage,
.target_block_cpu_usage_pct = config::default_target_block_cpu_usage_pct,
.max_transaction_cpu_usage = config::default_max_transaction_cpu_usage,
.min_transaction_cpu_usage = config::default_min_transaction_cpu_usage,
.max_transaction_lifetime = config::default_max_trx_lifetime,
.deferred_trx_expiration_window = config::default_deferred_trx_expiration_window,
.max_transaction_delay = config::default_max_trx_delay,
.max_inline_action_size = config::default_max_inline_action_size,
.max_inline_action_depth = config::default_max_inline_action_depth,
.max_authority_depth = config::default_max_auth_depth,
};
还可以通过nodeos命令行参数--genesis-json加载一个指定的配置文件genesis.json,其内容一般如下格式:
{
"initial_timestamp": "2018-03-02T12:00:00.000",
"initial_key": "EOS8Znrtgwt8TfpmbVpTKvA2oB8Nqey625CLN8bCN3TEbgx86Dsvr",
"initial_configuration": {
"max_block_net_usage": ,
"target_block_net_usage_pct": ,
"max_transaction_net_usage": ,
"base_per_transaction_net_usage": ,
"net_usage_leeway": ,
"context_free_discount_net_usage_num": ,
"context_free_discount_net_usage_den": ,
"max_block_cpu_usage": ,
"target_block_cpu_usage_pct": ,
"max_transaction_cpu_usage": ,
"min_transaction_cpu_usage": ,
"max_transaction_lifetime": ,
"deferred_trx_expiration_window": ,
"max_transaction_delay": ,
"max_inline_action_size": ,
"max_inline_action_depth": ,
"max_authority_depth": ,
"max_generated_transaction_count":
},
"initial_chain_id": ""
}
所以,节点之间能同步的条件是参数配置需要完全相当的。
四.区块同步数据流
数据同步涉及几个消息:
handshake_message, //hello握手信息,
chain_size_message, //暂未看到使用
go_away_message //停止同步消息
time_message, // 时间戳相关
notice_message, //区块和事务状态同步
request_message, //请求发送区块同步,带有区块的num数据
sync_request_message, //在request_message基础上加了一个定时器做超时处理
signed_block, // 具体的区块数据
packed_transaction //事务同步处理
现在假设有一个节点M,它的p2p-peer-address对就有三个地址a、b、c,现在数据同步的流程基本上有下面几个步骤.
1.handshake_message处理流程
首先,M结点会向a、b、c循环发起连接并发送一条握手信息,这条信息是一个名为struct handshake_message,定义如下:
struct handshake_message {
uint16_t network_version = ; //net version, require M == a == b == c chain_id_type chain_id; // M == a == b == c 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 = ;
block_id_type last_irreversible_block_id;
uint32_t head_num = ;
block_id_type head_id;
string os;
string agent;
int16_t generation;
};
包括了对通信的基本要求的参数,该消息初始化后会将其放入名为write_queue的消息队列中,最后消息是使用asio::async_write进行发送,发送消息的成功与否是通过回调来处理的。
void connection::do_queue_write() {
...
while (write_queue.size() > ) {
auto& m = write_queue.front();
bufs.push_back(boost::asio::buffer(*m.buff));
out_queue.push_back(m);
write_queue.pop_front();
}
boost::asio::async_write(*socket, bufs, [c](boost::system::error_code ec, std::size_t w) {
try {
for (auto& m: conn->out_queue) {
m.callback(ec, w);
}
while (conn->out_queue.size() > ) {
conn->out_queue.pop_front();
}
conn->enqueue_sync_block();
conn->do_queue_write();
}
...
}
对端收到handshake_message的消息后处理如下代码:
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.head_block_num( );
block_id_type head_id = cc.head_block_id();
if (head_id == msg.head_id) {
...
}
...
}
梳理流程:
- 两个节点历史区块id相等,不进行同步;
- A节点区块的head_block_num小于B节点不可逆区块的head_block_num,则B给A发送消息notice_message,消息中包含A节点所需要同步的区块范围,每次同步块数为sync_req_span,此参数在genesis.json中设置或者是程度初始的;
- A节点不可逆区块的head_block_num大于B节点区块的head_block_num,则A给B发送消息notice_message,消息中包含可逆与不可逆区块的block_num;
- A节点区块的head_block_num小于B节点的head_block_num,A节点会产生一个request_message消息发送给B;
2.go_away_message
一般在某些异常情况下节点A会断开与其它节点的同步,会发送一个go_away_message,会带有一个错误码:
enum go_away_reason {
no_reason, ///< no reason to go away
self, ///< the connection is to itself
duplicate, ///< the connection is redundant
wrong_chain, ///< the peer's chain id doesn't match
wrong_version, ///< the peer's network version doesn't match
forked, ///< the peer's irreversible blocks are different
unlinkable, ///< the peer sent a block we couldn't use
bad_transaction, ///< the peer sent a transaction that failed verification
validation, ///< the peer sent a block that failed validation
benign_other, ///< reasons such as a timeout. not fatal but warrant resetting
fatal_other, ///< a catch-all for errors we don't have discriminated
authentication ///< peer failed authenicatio
};
3.time_message
这个消息应该是发送一个带有几个时间标志的keeplive消息包,目前设置的是每32秒发送一次。
4.notice_message
这个消息定义如下:
struct notice_message {
notice_message () : known_trx(), known_blocks() {}
ordered_txn_ids known_trx;
ordered_blk_ids known_blocks;
};
它包含了区块的信息和交易信息,也即对可逆区块,可逆事务,不可逆区块,不可逆事务都可以通过这个消息处理。比如,节点A把本地节点最新区块和事务信息(block_num)发送给节点B,节点B收到后会将本地的区块和事务信息(block_num)进行比较,根据比较的结果决定谁给谁传输数据。
5.request_message
A节点请求端分为四种,节点B做为接收端,分别给予的应答如下:
对于区块:
- catch_up:B节点把本地的所有可逆的区块打包发给节点A;
- normal:根据A节点vector里面的区块id,在本地(B节点)不可逆的区块中进行查找,如果找到了就把该区块就发给A;
对于事务:
- catch_up:B节点把A节点所需要的可逆的transaction id 并且自己本地有的数据发送给A;
- normal: B节点把A节点所需要的不可逆的transaction id 并且自己本地有的数据发送给A;
6.sync_request_message
此消息是在request_message实现基础上加了一个5S的定时器,同步消息在5S内没有得到应答会取消当前同步后再重新要求同步;
7.signed_block
这里发送的是具体的区块数据,一般是收到request_message或者 sync_request_message消息后把本节点的区块发给对方;
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(num == 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;
}
8.packed_transaction
节点A把多个transacton放在一起进行打包发送,收到packed_transaction消息的节点会对其进行各种校验,如果校验结果正确,会把数据缓存到本地,然后再对本端所有p2p-peer-address的地址进行广播。所以对于多个transaction的数据,在这里就实现了在多个地址之间相互快速传播的功能。
void net_plugin_impl::handle_message( connection_ptr c, const packed_transaction &msg) {
fc_dlog(logger, "got a packed transaction, cancel wait");
peer_ilog(c, "received packed_transaction");
if( sync_master->is_active(c) ) {
fc_dlog(logger, "got a txn during sync - dropping");
return;
}
transaction_id_type tid = msg.id();
//收到数据后取异步定时器
c->cancel_wait();
if(local_txns.get<by_id>().find(tid) != local_txns.end()) {
fc_dlog(logger, "got a duplicate transaction - dropping");
return;
}
//将数据保存到本地的缓存中
dispatcher->recv_transaction(c, tid);
uint64_t code = ;
//对数据进行校验,然后把结果传递给回调函数
chain_plug->accept_transaction(msg, [=](const static_variant<fc::exception_ptr, transaction_trace_ptr>& result) {
if (result.contains<fc::exception_ptr>()) {
auto e_ptr = result.get<fc::exception_ptr>();
if (e_ptr->code() != tx_duplicate::code_value && e_ptr->code() != expired_tx_exception::code_value)
elog("accept txn threw ${m}",("m",result.get<fc::exception_ptr>()->to_detail_string()));
peer_elog(c, "bad packed_transaction : ${m}", ("m",result.get<fc::exception_ptr>()->what()));
} else {
auto trace = result.get<transaction_trace_ptr>();
if (!trace->except) {
fc_dlog(logger, "chain accepted transaction");
//对其它p2p-peer-address进行广播,数据互传
dispatcher->bcast_transaction(msg);
return;
}
peer_elog(c, "bad packed_transaction : ${m}", ("m",trace->except->what()));
}
//数据校给失败,本地缓存数据回滚
dispatcher->rejected_transaction(tid);
});
}
EOS多节点同步代码分析的更多相关文章
- EOS多节点组网:商业场景分析以及节点启动时序
区块链公链都是基于p2p网络,本篇文章将建立一个多节点不同职责参与的EOS的测试网络,根据路上发现的可做文章的技术点大做文章. 关键字:EOS组网,全节点,交易确认,boot sequence,sta ...
- EOS节点远程代码执行漏洞细节
这是一个缓冲区溢出越界写漏洞 漏洞存在于在 libraries/chain/webassembly/binaryen.cpp文件的78行, Function binaryen_runtime::ins ...
- 完整全面的Java资源库(包括构建、操作、代码分析、编译器、数据库、社区等等)
构建 这里搜集了用来构建应用程序的工具. Apache Maven:Maven使用声明进行构建并进行依赖管理,偏向于使用约定而不是配置进行构建.Maven优于Apache Ant.后者采用了一种过程化 ...
- 【转载】word2vec原理推导与代码分析
本文的理论部分大量参考<word2vec中的数学原理详解>,按照我这种初学者方便理解的顺序重新编排.重新叙述.题图来自siegfang的博客.我提出的Java方案基于kojisekig,我 ...
- 《图说VR入门》——googleVR入门代码分析
本文章由cartzhang编写,转载请注明出处. 所有权利保留. 文章链接:http://blog.csdn.net/cartzhang/article/details/53013843 作者:car ...
- 深入理解mmap--内核代码分析及驱动demo示例
mmap是一个很常用的系统调用,无论是分配内存.读写大文件.链接动态库文件,还是多进程间共享内存,都可以看到其身影.本文首先介绍了进程地址空间和mmap,然后分析了内核代码以了解其实现,最后通过一个简 ...
- About 静态代码块,普通代码块,同步代码块,构造代码块和构造函数的纳闷
构造函数用于给对象进行初始化,是给与之对应的对象进行初始化,它具有针对性,函数中的一种.特点:1:该函数的名称和所在类的名称相同.2:不需要定义返回值类型.3:该函数没有具体的返回值.记住:所有对象创 ...
- 常用 Java 静态代码分析工具的分析与比较
常用 Java 静态代码分析工具的分析与比较 简介: 本文首先介绍了静态代码分析的基 本概念及主要技术,随后分别介绍了现有 4 种主流 Java 静态代码分析工具 (Checkstyle,FindBu ...
- [Asp.net 5] DependencyInjection项目代码分析4-微软的实现(3)
这个系列已经写了5篇,链接地址如下: [Asp.net 5] DependencyInjection项目代码分析 [Asp.net 5] DependencyInjection项目代码分析2-Auto ...
随机推荐
- php常用函数htmlspecialchars、strip_tags、addslashes解析
本文章向大家介绍php开发中经常使用到的字符串函数htmlspecialchars.strip_tags.addslashes的使用方法及他们之间的区别,需要的朋友可以参考一下. 1.函数strip_ ...
- C++ 0X 新特性实例(比较常用的) (转)
转自:http://www.cnblogs.com/mrblue/p/3141456.html //array #include <array> void Foo1() { array&l ...
- 说几个JS优化技巧吧
JavaScript一种直译式脚本语言,是一种动态类型.弱类型.基于原型的语言,内置支持类型.它的解释器被称为JavaScript引擎,为浏览器的一部分,广泛用于客户端的脚本语言,最早是在HTML(标 ...
- wcf win7上使用net.tcp出现不支持协议问题解决
第一:iis绑定 net.tcp 808:* 第二:iis 应用中高级开启协议,添加net.tcp多协议逗号隔开 第三:开启各项服务 第四:执行 ServiceModReg.exe -r
- nodejs调试:node-inspector
基于Chrome浏览器的调试器 既然我们可以通过V8的调试插件来调试,那是否也可以借用Chrome浏览器的JavaScript调试器来调试呢?node-inspector模块提供了这样一种可能.我们需 ...
- MongoDB3.2.8创建初始用户
启动MongoDB前需要关闭配置文件中的auth选项,否则不能创建用户 首先创建用户管理用户 use admin db.createUser({user:'admin',pwd:'123456', r ...
- MySQL数据库服务器参数优化mycnf,16G内存8核CPU,
业务场景: 后台支持手机在线更新系统,db服务器内存16G,8核,dell的pc服务器. qps: 200个左右 tps: 1个左右 一分钟50几个 sort_buffer_size = 32M 大了 ...
- Python模块-requests(一)
requests不是python自带的,使用前需要安装 发送请求 HTTP请求类型有GET,POST,PUT,DELETE,HEAD和OPTIONS 使用requests发送请求的方法如下: > ...
- [原创] 新人分享--ORA-01012:not logged on的解决办法 [复制链接]
转自:http://f.dataguru.cn/thread-82530-1-1.html
- python 基础 操作文件和目录
获得当前目录路径 :os.getcwd() 返回指定目录下的所有文件和目录名:os.listdir() 删除一个文件:os.remove(filename) 删除多个空目录 :os.removefir ...