EOS系列 - 源码解读心得 - 出块与同步过程

EOS源码解读心得

主要知识点

1.EOS出块过程

在这里插入图片描述
生产、同步区块的核心代码逻辑:

区块的创造过程

  1. 检查区块生产者的合法性
  2. 如果当前主干已经不是最长链,需要切换到最长的分叉链上切换过程如下
    1. 弹出一定数量的区块,直到碰到分叉处
    2. 将分叉中的新区块写入主干数据库
    3. 切换后的分叉成为主干
    4. 如果上述过程失败,则恢复状态
  3. 如果不需要切换分支,则往主干数据库中写入区块
  4. 返回结果,以表明这次写入是否造成了分支切换

交易的写入

  1. 遍历交易中的所有操作,对于每个操作:
    1. 获取操作的相关信息,并且执行操作
    2. 记录这次操作执行
  2. 更新因这次操作而消耗的资源统计数据
  3. 记录交易的内容
  4. 返回结果

一笔交易的生命周期
在这里插入图片描述

2.EOS区块的签名与验签

区块头摘要

区块头摘要就是对整个区块数据做Hash运算得到的结果,由于Hash的结果长度固定,而且单向不可逆,完全符合摘要的要求。
区块id实际是由两部分组成:区块内容的Hash值 拼接 区块序号

digest_type block_header::digest()const
{
	return digest_type::hash(*this);
}

生成签名摘要

digest_type   block_header_state::sig_digest()const {
     auto header_bmroot = digest_type::hash( std::make_pair( header.digest(), blockroot_merkle.get_root() ) );
     return digest_type::hash( std::make_pair(header_bmroot, pending_schedule_hash) );
}

签名

void sign_block( const std::function<signature_type( const digest_type& )>& signer_callback  ) {
      auto p = pending->_pending_block_state;
      p->sign( signer_callback );
      static_cast<signed_block_header&>(*p->block) = p->header;
}

void block_header_state::sign( const std::function<signature_type(const digest_type&)>& signer ) {
     auto d = sig_digest();
     header.producer_signature = signer( d ); //signer调用签名函数
     EOS_ASSERT( block_signing_key == fc::crypto::public_key( header.producer_signature, d ), wrong_signing_key, "block is signed with unexpected key" );
}

签名算法 ECDSA-secp256k1 和 ECDSA-r1

cleos create key命令生成基于secp256k1算法的公私钥
平时使用的EOS公钥都是基于secp256k1算法的
base58Check在Base58编码的基础上增加了校验码,比特币和EOS都使用了Base58Check编码

//private_key.cpp
signature private_key::sign( const sha256& digest, bool require_canonical ) const
{
   return signature(_storage.visit(sign_visitor(digest, require_canonical)));
}

signature::storage_type operator()(const KeyType& key) const
{
    return signature::storage_type(key.sign(_digest, _require_canonical));
}

//signature.cpp
static signature::storage_type parse_base58(const std::string& base58str)

//elliptic.hpp
signature_type sign( const sha256& digest, bool require_canonical = true ) const
{
   return signature_type(private_key::regenerate(_data).sign_compact(digest, require_canonical));
}

从签名中提取公钥

public_key_type block_header_state::signee()const {
    return fc::crypto::public_key( header.producer_signature, sig_digest(), true );
}

签名算法 ECDSA-secp256k1 和 ECDSA-r1

//public_key.cpp
public_key::public_key( const signature& c, const sha256& digest, bool check_canonical )
   :_storage(c._storage.visit(recovery_visitor(digest, check_canonical)))
{
}

//elliptic.hpp
public_key_type recover(const sha256& digest, bool check_canonical) const {
  return public_key_type(public_key(_data, digest, check_canonical).serialize());
}

//elliptic_secp256k1.cpp         
public_key::public_key( const compact_signature& c, const fc::sha256& digest, bool check_canonical )

验签

result.verify_signee( result.signee() );

void block_header_state::verify_signee( const public_key_type& signee )const {
     EOS_ASSERT( block_signing_key == signee, wrong_signing_key, "block not signed by expected key",
                 ("block_signing_key", block_signing_key)( "signee", signee ) );
}

3.EOS交易的签名与验签

交易的数据结构

交易ID: 通过对交易内容本身经过Hash运算得出

transaction_id_type transaction::id() const {
   digest_type::encoder enc;
   fc::raw::pack( enc, *this );
   return enc.result();
}
+-----------+---------------+------------------+---------------------+------------------+-----------+----------+----------+----+------------+
| expiration| ref_block_num | ref_block_prefix | max_net_usage_words | max_cpu_usage_ms | delay_sec | action 1 | action 2 |... | signatures |
+-----------+---------------+------------------+---------------------+------------------+-----------+----------+----------+----+------------+
+---------------------------------------------head  ------------------------------------------------+-----------------body------------------+

结构中没有id字段,它是运算出来的
一个交易在纳入区块之前必须含有签名,用以验证交易的合法性,除此之外,交易体也包含了签名的摘要,同样用于交易的验证机制。


生成签名

在这里插入图片描述

生成签名摘要

digest_type transaction::sig_digest( const chain_id_type& chain_id, const vector<bytes>& cfd )const {
   digest_type::encoder enc;
   fc::raw::pack( enc, chain_id );
   fc::raw::pack( enc, *this );
   if( cfd.size() ) {
      fc::raw::pack( enc, digest_type::hash(cfd) );
   } else {
      fc::raw::pack( enc, digest_type() );
   }
   return enc.result();
}

签名

const signature_type& signed_transaction::sign(const private_key_type& key, const chain_id_type& chain_id) {
   signatures.push_back(key.sign(sig_digest(chain_id, context_free_data)));
   return signatures.back();
}

signature private_key::sign( const sha256& digest, bool require_canonical ) const
{
   return signature(_storage.visit(sign_visitor(digest, require_canonical)));
}

从签名中提取公钥

fc::microseconds transaction::get_signature_keys( const vector<signature_type>& signatures,
      const chain_id_type& chain_id, fc::time_point deadline, const vector<bytes>& cfd,
      flat_set<public_key_type>& recovered_pub_keys, bool allow_duplicate_keys)const{
...
const digest_type digest = sig_digest(chain_id, cfd);
...

* 遍历交易的所有签名
* 通过签名和签名摘要(digest)抽取公钥	
	recov = public_key_type( sig, digest )
* 把公钥和签名组成的配对放入缓存,以便下次快捷访问

}

检测交易所需要的权限

authorization.check_authorization(
       trn.actions,
       recovered_keys,
       {},
       trx_context.delay,
       [&trx_context](){ trx_context.checktime(); },
       false
);

4.同步区块

P2P 网络介绍

如何发现节点

  1. 节点会记住它最近成功连接的网络节点,当重新启动后它可以迅速与先前的对等节点网络重新建立连接。
  2. 节点会在失去已有连接时尝试发现新节点。
  3. 当建立一个或多个连接后,节点将一条包含自身 IP 地址消息发送给其相邻节点。相邻节点再将此消息依次转发给它们各自的相邻节点,从而保证节点信息被多个节点所接收、保证连接更稳定。
  4. 新接入的节点可以向它的相邻节点发送获取地址 getaddr 消息,要求它们返回其已知对等节点的 IP 地址列表。节点可以找到需连接到的对等节点。
  5. 在节点启动时,可以给节点指定一个正活跃节点 IP, 如果没有,客户端也维持一个列表,列出了那些长期稳定运行的节点。这样的节点也被称为种子节点(其实和 BT 下载的种子文件道理是一样的),就可以通过种子节点来快速发现网络中的其他节点。

节点通信

节点通常采用 TCP 协议、使用指定端口与相邻节点建立连接,建立连接时也会有认证 “握手” 的通信过程(handshake_message)。握手包内容包括:网络版本、chain_id、node_id、p2p_address、节点名称等配置信息,以及链的状态(当前节点的不可逆区块数、不可逆区块id、最新区块id、最新区块数)。

当节点收到相邻节点的handshake_message,接着就开始对比区块信息。如果其自身的本地区块链比其他节点的区块链更长,并告诉其他节点需要补充区块(notice_message),其他节点发送 sync_request_message 消息来请求区块,其他节点收到区块数据后并验证后更新到本地区块链中。如果自身的本地区块链比其他节点的短,则向其他节点发送 sync_request_message 去请求区块。


从另一个节点同步区块

在这里插入图片描述

同步时的状态比较

区块同步的更详细的说明:

发布了58 篇原创文章 · 获赞 66 · 访问量 8171

猜你喜欢

转载自blog.csdn.net/wcc19840827/article/details/97135112