本文用于记录对EOS源代码的阅读记录,对其相关内容从源代码中追踪问迹,希望从源代码入手,对其设计原理有一个深入的认识,也用于本人学习研究之用.
如果对EOS不太了解, 还需要对EOS顶层的内容有一些了解.这里推荐EOS的白皮书进行了解学习.
对任何区块链相关技术的学习,首先应该从其区块的结构开始学起,因此,本文首先从区块源代码入手开始了解.
EOS区块链相关数据结构
block header
block header的定义,来自于"chain\webassembly\block_header.hpp"文件中,其主要结构如下:
字段 | 名称 | 大小(Byte) | 含义 |
---|---|---|---|
timestamp | 时间戳 | 4 | 生产区块的时间 |
producer | 生产者 | 8 | 生产区块的超级节点 |
confirmed | 确认数 | 2 | 区块的确认数 |
transaction_mroot | 交易的Merkle Root | 32 | 区块中全部交易的默克尔数的哈希值 |
action_mroot | action的Merkle Root | 32 | 区块中全部action的默克尔数的哈希值 |
schedule_version | 版本号 | 4 | 见证人排序版本号 |
new_producers | 下一个见证人 | - | 区块的下一个见证人,可以为空 |
header_extensions | 扩展类型 | - | vector<std::pair<uint16_t,vector>> |
- | - | - | - |
具体对于block_header的结构代码,可以参看下述代码。
struct block_header
{
block_timestamp_type timestamp;
account_name producer;
uint16_t confirmed = 1;
block_id_type previous;
checksum256_type transaction_mroot; /// mroot of cycles_summary
checksum256_type action_mroot; /// mroot of all delivered action receipts
uint32_t schedule_version = 0;
optional<producer_schedule_type> new_producers;
extensions_type header_extensions;
digest_type digest()const; // 求区块的摘要
block_id_type id() const; // 获取区块id
uint32_t block_num() const { return num_from_id(previous) + 1; } //当前区块号 = 前一个区块号+1
static uint32_t num_from_id(const block_id_type& id);
};
上述区块是没有对区块进行签名的区块,对区块进行签名之后,区块数据结构是signed_block_header,具体代码如下:
struct signed_block_header : public block_header
{
signature_type producer_signature;// 区块生产者的签名
};
block数据结构
EOS中block的数据结构如下,里面只有两个数据结构,即保存交易的vector和block_extensions。
block不能进行引用,只能进行复制。
struct signed_block : public signed_block_header
{
private:
signed_block(const signed_block &) = default;
public:
signed_block() = default;
explicit signed_block(const signed_block_header &h) : signed_block_header(h) {}
signed_block(signed_block &&) = default;
signed_block &operator=(const signed_block &) = delete;
signed_block clone() const { return *this; }
vector<transaction_receipt> transactions; /// new or generated transactions
extensions_type block_extensions;
};
Transaction_header数据结构
transaction_header包含了固定大小的与每一笔交易都相关的一些数据,与transaction_body独立开来,这样在解析交易的时候不需要进行动态内存分配,这就加快了交易的解析过程。
所有的transaction都有一个截止时间,超过这个截止时间之后不会记录在链上,一旦某个区块的时间戳大于某个交易的截止时间,那么这个交易就永远不会包含在区块中。
其数据结构如下:
struct transaction_header {
time_point_sec expiration; ///< the time at which a transaction expires
uint16_t ref_block_num = 0U; ///< specifies a block num in the last 2^16 blocks. 2bytes
uint32_t ref_block_prefix = 0UL; ///< specifies the lower 32 bits of the blockid at get_ref_blocknum 区块号低4位的前缀 4bytes
fc::unsigned_int max_net_usage_words = 0UL; /// upper limit on total network bandwidth (in 8 byte words) billed for this transaction 4bytes
uint8_t max_cpu_usage_ms = 0; /// upper limit on the total CPU time billed for this transaction 1bytes
fc::unsigned_int delay_sec = 0UL; /// number of seconds to delay this transaction for during which it may be canceled.4bytes
};
transaction
每一个transaction都包括了一系列的action,action中要么全部被执行,要么全部不执行,一个transaction的结构如下。
struct transaction : public transaction_header {
vector<action> context_free_actions;
vector<action> actions;
extensions_type transaction_extensions;
};
signed_transaction
每一个signed_transaction继承于transaction,多了签名和一些其他数据,其数据结构如下:
struct signed_transaction : public transaction
{
vector<signature_type> signatures;// 一笔交易可能需要多个签名
vector<bytes> context_free_data; ///< for each context-free action, there is an entry here
};
EOS中的操作(action)
EOS区块链中的交易是由一个个操作组成的,操作可以理解成一个能够更改区块链全局状态的方法,操作的顺序是确定的,一个交易内的操作要么全部执行成功,要么都不执行,这与交易的本意(transaction)是一致的。操作是区块链的最底层逻辑,相当于区块链这个大脑的神经元,区块链的智能最终也是通过一个个操作的组合来实现的。
操作的设计思路
操作的来源
一个操作可以通过两种途径产生:
- 由一个账号产生,通过签名来授权,即显性方式。
- 由代码生成,即隐形方式。
操作的底层逻辑
操作的设计遵循React Flux设计模式,简单的说就是每一个操作将会被赋予一个名称,然后被分发给一个或者多个handler。在EOS环境中,每个操作对应的handler是通过scope和name来定义的,默认的handler也可以再次将操作分发给下一级的多个handler。所以,每个EOS应用可以实现自己的handler,当操作被分发到这个应用时,相应的handler的代码就会被执行。
操作的设计思路中另一重要概念是授权。每一个操作的执行都必须确保具备了指定账户的授权。授权通过许可(permission)的方式声明,对于一个授权动作,账户可以要求任意数量的许可,许可的验证是独立于代码执行的,只有所有规定的许可被成功验证之后,对应的代码才能够被执行。
操作的数据结构
struct action {
account_name account;// 操作的来源
action_name name; // 操作的名称
vector<permission_level> authorization;// 执行操作的许可列表
bytes data;// 执行action需要用到的数据
...
};
action中有一个permission_level,指的是权限级别一个账户可以将权限授予任意多个账户,同时每个账户可以有不同的权限名。权限级别的数据结构如下。
struct permission_level {
account_name actor; // 授予的账户名称
permission_name permission; // 授予账户的权限
};
关于许可的相关内容,可以参看账户和权限
链控制器
上述内容是内核的基本组成部分,如同一个机器的零部件,然而,要让机器运转起来,还需要把这些零部件串起来的控制中心,而链控制器(chain_controller)就是这个控制中心。下面我们来详细介绍EOS的链控制器。
链控制器的基本功能
首先,我们需要理解链控制器存在的主要目的是作为外部世界与区块链内核之间的交互界面,所以它有着承上启下的作用,承上为支撑区块链与外部的交互,启下为管理区块链内部的状态变更。所以,从理解的不同角度,链控制器可以被理解为以下两个概念:
-
从外部视角,链控制器是一个数据库,这与链外对区块链的理解是一致的,从狭义的角度看,区块链就像一个不可更改的数据库,而链控制器可以看做这个数据库的管家,外部世界不用直接与区块链底层通信,而是通过这个管家来与区块链交互。
-
从内部视角,链控制器是一个协调者,区块链内部各个部件均独立运作,这与上述的设计原则是一致的,这样的话,各个部件之间如何调度与协调,就需要一个有全局视角的角色来统一管理,这就是链控制器的第二个作用.
用作者的原话来说,链控制器的目的是以一种可以扩展的方式来跟踪区块链的状态变化。
链控制器的基本要素
为了维护可靠的区块链状态,链控制器管理着两个不用类型的数据库:
- 区块日志。它的作用是存储已经经过验证的不可逆转的区块链主干。
- 块数据库 这是一个带索引的可以增删改查的数据库。它的作用是维护和管理未经验证的区块链主干与分支两个子数据库,主干数据库存储的是当前长度最长的一条链,而分支存储的是因为不同分叉而造成的分支链,随着时间的推移,主链是有可能被分支替代的,每当一个新的区块要写入时,EOS都会重新计算各分支和主干的长度,并且决定要不要切换主干。最后,执行错误或者验证不通过的区块最终会被抛弃,并从数据库中清理出去。
除了存储部分,链控制器还需精确维护一个消息机制,以保证对区块链中的其他成员广播最新的区块状态变更,这个消息机制是区块链网络能够正常和自主运行的基础。
事务处理机制
熟悉数据库的读者一定知道,对于数据库的写入操作,维持一个事务的原子性是很关键的,也就是说,对于一批写入操作,要么全都成功执行,要么都不执行,一定不能出现只执行了其中一部分的情况,因为这样将导致数据的不一致性,这样的错误是致命的。EOS中对于块数据库的写操作采用同样的设计原则,事务的使用主要体现在两个层面:
- 区块. 整个区块的内容是一个整体,要么全都写入,要么都不写入
- 交易. 一个交易是一个整体,其中的操作要么都执行,要么都不执行
当事务中的某个步骤出现错误时,整个事务就会启动回滚机制,整个区块链将会恢复到这个事务发生之前的状态。
链控制器的主要过程
- 链控制器的初始化
链控制器的初始化是区块的起点,它的核心过程如下所示:
controller_impl( const controller::config& cfg, controller& s )
:self(s),
db( cfg.state_dir,
cfg.read_only ? database::read_only : database::read_write,
cfg.state_size ),
reversible_blocks( cfg.blocks_dir/config::reversible_blocks_dir_name,
cfg.read_only ? database::read_only : database::read_write,
cfg.reversible_cache_size ),
blog( cfg.blocks_dir ),
fork_db( cfg.state_dir ),
wasmif( cfg.wasm_runtime ),
resource_limits( db ),
authorization( s, db ),
conf( cfg ),
chain_id( cfg.genesis.compute_chain_id() ),
read_mode( cfg.read_mode ),
thread_pool( cfg.thread_pool_size )
{
#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 );
fork_db.irreversible.connect( [&]( auto b ) {
on_irreversible(b);
});
}