上面讲到了,fork_database拥有一个公有成员irreversible信号。这个信号在controller_impl结构体的宏SET_APP_HANDLER中被使用:
fork_db.irreversible.connect( [&]( auto b ) { on_irreversible(b); });这段代码其实是boost的信号槽机制,信号有一个connect操作,其参数是一个slot插槽,可将插槽连接到信号上,最终返回一个connection对象代表这段连接关系,可以灵活控制连接开关。插槽的类型可以是任意对象,这段代码中是一个lambda表达式,调用了on_irreversible函数。
接下来,去fork_database查询该信号的触发位置,出现在prune函数中的一段代码,
在table中查询入参区块,查找到以后,会触发信号irreversible并携带区块源数据发射。然后执行fork_database的删除操作将目标区块从分叉库中删除。
irreversible信号携带区块被发射后,由于上面宏的作用,会调用controller_impl的on_irreversible函数,并按照lambda表达式的规则将区块传入。该函数会将入参区块变为不可逆,处理成功以后,下面截取了这部分相关代码:
这两行是该函数对fork_db的全部操作,将fork_db的属性in_current_chain和validated置为true。在on_irreversible函数的最后,它也发射了一个自己的信号,注意发射方式采用了关键字emit,也携带了操作的区块数据。
信号触发可以有两种方式,使用关键字emit(signal,param)和直接调用signal(param)。
这个信号本来是与这一小节的内容不相干,但既然分析到这了,还是希望能有个闭环,那么来看一下该信号的连接槽位置,如图所示。
可以看到,区块不可逆的信号在net_plugin,chain_plugin,mongo_db_plugin,producer_plugin四个插件代码中得到了运用,也说明这四个插件是非常关心区块不可逆的状态变化的。至于他们具体是如何运用的,在相关部分会有详细介绍。
3. initialize_fork_db初始化fork_db,主要工作是从创世块状态设置fork_db的头块。头块的数据结构是区块状态对象,构造头块时,要先构造区块头状态对象,包括:
active_schedule,活动的出块安排,默认为初始出块安排。
pending_schedule,等待中的出块安排,默认为初始出块安排。
pending_schedule_hash,等待中的出块安排的单向哈希值。
header.timestamp,等于创世块配置文件genesis中的timestamp值。
header.action_mroot,action的Merkel树根,创世块的值为链id值,该值是通过加密算法计算出的。
id,块id。
block_num,块号。
构建好区块头以后,接着构建区块体,构建完成以后,将完整头块插入到空的fork_db中。
4. commit_block -> add_to_fork_db提交区块函数,无论提交是否成功,都不再保留活动的pending块。该函数有一个参数add_to_fork_db,是否加入fork_db。在producer_plugin生产者生产区块的逻辑中,提交区块调用controller对象的commit_block函数:
void controller::commit_block() { validate_db_available_size(); // 校验db数据库的大小 validate_reversible_available_size(); // 校验reversible数据库的大小 my->commit_block(true); // 调用controller_impl结构体中的的commit_block函数,并且传入true }从这条逻辑过来的提交区块,会执行add_to_fork_db,而commit_block函数的另一处调用是在应用区块部分,没有触发add_to_fork_db。至于commit_block函数的内容不在此处展开,只看fork_db相关的内容:
if (add_to_fork_db) { pending->_pending_block_state->validated = true; // 将pending区块对象的状态属性validated置为true,标记已校验。 auto new_bsp = fork_db.add(pending->_pending_block_state); // 将pending区块添加至fork_db。 emit(self.accepted_block_header, pending->_pending_block_state); // 发射controller的accepted_block_header信号,携带pending区块状态对象。 head = fork_db.head(); // 将当前节点的头块设置为fork_db的头块。 // 校验pending区块是否最终成功同时变为fork_db以及主节点的头块。 EOS_ASSERT(new_bsp == head, fork_database_exception, "committed block did not become the new head in fork database"); } 5. maybe_switch_forks