controller::block_status,区块状态枚举类,包括:

irreversible = 0,该区块已经被当前节点应用,并且被认为是不可逆的。
validated = 1,这是由一个有效生产者签名的完整区块,并且之前已经被当前节点应用,因此该区块已被验证但未成为不可逆。
complete = 2,这是一个由有效生产者签名的完整区块,但是还没有成为不可逆,也没有被当前节点应用。
incomplete = 3,这是一个未完成的区块,未被生产者签名也没有被某个节点生产。
实际上块的状态就是,1未签名未应用 2已签名未应用 3 已签名和应用,未变成不可逆 4 变成不可逆

controller的私有成员:

apply_context,应用上下文,处理节点应用区块的上下文环境,其中包含了迭代器缓存iterator_cache<key_value_object>
transaction_context,事务上下文环境。包括controller,db的session,signed_transaction等。
mutable_db(),返回一个chainbase::database的引用
controller_impl结构体的实例的唯一指针my。controller核心功能都是通过impl实现调用的。从fork_db比如获取区块信息

controller的信号

controller 包括了以下几个信号:
signal<void(const signed_block_ptr&)> pre_accepted_block; // 预承认区块(承认其他节点广播过来的区块是正确的)
signal<void(const block_state_ptr&)> accepted_block_header; // 承认区块头(对区块头做过校验)
signal<void(const block_state_ptr&)> accepted_block; // 承认区块
signal<void(const block_state_ptr&)> irreversible_block; // 不可逆区块
signal<void(const transaction_metadata_ptr&)> accepted_transaction; // 承认事务
signal<void(const transaction_trace_ptr&)> applied_transaction; // 应用事务(承认其他节点数据要先校验,通过以后可以应用在本地节点)
signal<void(const header_confirmation&)> accepted_confirmation; // 承认确认
signal<void(const int&)> bad_alloc; // 内存分配错误信号
所有信号的发射时机都是在controller中。
分别介绍每个信号发射和接收时机

  1. pre_accepted_block
    发射时机:push_block函数,在producer_plugin中 on_incoming_block中会对已签区块校验,包括不能有pending块,不能push空块,区块状态不能是incomplete。通过校验后,调用push_block会发射该信号,携带该区块。
    on_incoming_block函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    void producer_plugin::on_incoming_block(const signed_block_ptr& block) {
    //判断是否在fork_db中
    auto existing = chain.fetch_block_by_id( id );
    if( existing ) { return; }
    // 开始创建一个blockstate
    auto bsf = chain.create_block_state_future( block );
    // 清除 pending状态,将交易放到unappliedtrx map中
    chain.abort_block();
    // 当抛出异常时,重启loop
    auto ensure = fc::make_scoped_exit([this](){
    schedule_production_loop();
    });
    // push the new block
    bool except = false;
    try {
    chain.push_block( bsf );
    } catch ( const guard_exception& e ) {
    chain_plug->handle_guard_exception(e);
    return;
    } catch( const fc::exception& e ) {
    elog((e.to_detail_string()));
    except = true;
    } catch ( boost::interprocess::bad_alloc& ) {
    chain_plugin::handle_db_exhaustion();
    return;
    }

on_incoming_block做了这样几个事,
1 判断forkdb是否有该新来的块,
2 其次根据该块投递到线程池生成blockstate,
3 清除之前的pending状态,pending中的交易取出放到unapplied map中.(以后会通过schedule_production_loop调用producer_plugin::start_block处理unapplied map,内部调用controller::start_block重新组织pending)
4 为防止producer异常退出,设置schedule_production_loop重启。
5 调用controller的push_block

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void controller::push_block( std::future<block_state_ptr>& block_state_future ) {
controller::block_status s = controller::block_status::complete;
EOS_ASSERT(!pending, block_validate_exception, "it is not valid to push a block when there is a pending block"); auto reset_prod_light_validation = fc::make_scoped_exit([old_value=trusted_producer_light_validation, this]() {
trusted_producer_light_validation = old_value;
});
try {
block_state_ptr new_header_state = block_state_future.get();
auto& b = new_header_state->block;
emit( self.pre_accepted_block, b ); fork_db.add( new_header_state, false ); if (conf.trusted_producers.count(b->producer)) {
trusted_producer_light_validation = true;
};
emit( self.accepted_block_header, new_header_state ); if ( read_mode != db_read_mode::IRREVERSIBLE ) {
maybe_switch_forks( s );
} } FC_LOG_AND_RETHROW( )
}

在push_block中发送了pre_accepted_block信号
插件捕捉处理: chain_plugin连接该信号,在plugin_initialize中绑定了信号的处理
但是该channel没有订阅者。

1
2
3
4
5
6
7
8
9
10
11
12
13
// relay signals to channels
my->pre_accepted_block_connection = my->chain->pre_accepted_block.connect([this](const signed_block_ptr& blk) {
auto itr = my->loaded_checkpoints.find( blk->block_num() );
if( itr != my->loaded_checkpoints.end() ) {
auto id = blk->id();
EOS_ASSERT( itr->second == id, checkpoint_exception,
"Checkpoint does not match for block number ${num}: expected: ${expected} actual: ${actual}",
("num", blk->block_num())("expected", itr->second)("actual", id)
);
} my->pre_accepted_block_channel.publish(priority::medium, blk);
});
  1. accepted_block_header

发射时机1: commit_block函数,

1
2
3
4
5
6
7
8
9
void commit_block( bool add_to_fork_db ) {
if (add_to_fork_db) {
pending->_pending_block_state->validated = true;
auto new_bsp = fork_db.add(pending->_pending_block_state, true);
emit(self.accepted_block_header, pending->_pending_block_state);
head = fork_db.head();
EOS_ASSERT(new_bsp == head, fork_database_exception, "committed block did not become the new head in fork database");
}
}

add_to_fork_db为true,将_pending_block_state->validated设置为true,_pending_block_state放入fork_db,然后发送accepted_block_header

发射时机2: push_block函数,获取区块的可信状态,发射完pre_accepted_block以后,添加可信状态至fork_db,然后发射accepted_block_header信号,携带fork_db添加成功后返回的状态区块。

插件捕捉处理: chain_plugin连接该信号,

1
2
3
4
5
6
void chain_plugin::plugin_initialize(const variables_map& options) {
my->accepted_block_header_connection = my->chain->accepted_block_header.connect(
[this]( const block_state_ptr& blk ) {
my->accepted_block_header_channel.publish( priority::medium, blk );
} );
}

由信号槽转播到channel,accepted_block_header_channel发布该区块,bnet_plugin订阅该channel

1
2
3
4
5
6
void bnet_plugin::plugin_startup() {
my->_on_accepted_block_header_handle = app().get_channel<channels::accepted_block_header>()
.subscribe( [this]( block_state_ptr s ){
my->on_accepted_block_header(s);
});
}

绑定了回调函数,回调函数内部调用bnet_plugin_impl的on_accepted_block_header(s);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void on_accepted_block_header( const block_state_ptr& s ) {
...
const auto& id = s->id;
if( fc::time_point::now() - s->block->timestamp < fc::seconds(6) ) {
auto itr = _block_status.find( id );
//_remote_request_irreversible_only(对端请求可逆)且(之前状态集合中没有该块或没收到过该块)
if( !_remote_request_irreversible_only && ( itr == _block_status.end() || !itr->received_from_peer ) ) {
//加入通知集合,通知对方防止对方重复发送区块头
_block_header_notices.insert( id );
}
//加入状态集合
if( itr == _block_status.end() ) {
_block_status.insert( block_status(id, false, false) );
}
}
}
  1. accepted_block
    发射时机: commit_block函数,fork_db以及重播的处理结束后,发射承认区块的信号,携带pending状态区块数据。
    插件捕捉处理
    1 net_plugin连接该信号,绑定处理函数,打印日志的同时调用dispatch_manager::bcast_block,传入区块数据。send_all向所有连接发送广播
    2 chain_plugin连接该信号,由信号槽转播到channel,accepted_block_channel发布该区块

    1
    2
    3
    4
    5
    void chain_plugin::plugin_initialize(const variables_map& options) {
    my->accepted_block_connection = my->chain->accepted_block.connect( [this]( const block_state_ptr& blk ) {
    my->accepted_block_channel.publish( priority::high, blk );
    } );
    }

bnet_plugin订阅该channel

1
2
3
4
5
6
void bnet_plugin::plugin_startup() {
my->_on_accepted_block_handle = app().get_channel<channels::accepted_block>()
.subscribe( [this]( block_state_ptr s ){
my->on_accepted_block(s);
});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void on_accepted_block( const block_state_ptr& s ) {
verify_strand_in_this_thread(_strand, __func__, __LINE__);
const auto& id = s->id; _local_head_block_id = id;
_local_head_block_num = block_header::num_from_id(id); if( _local_head_block_num < _last_sent_block_num ) {
_last_sent_block_num = _local_lib;
_last_sent_block_id = _local_lib_id;
} purge_transaction_cache(); for( const auto& receipt : s->block->transactions ) {
if( receipt.trx.which() == 1 ) {
const auto& pt = receipt.trx.get<packed_transaction>();
const auto& tid = pt.id();
auto itr = _transaction_status.find( tid );
if( itr != _transaction_status.end() )
_transaction_status.erase(itr);
}
} maybe_send_next_message(); /// attempt to send if we are idle
}

on_accepted_block函数,删除缓存中的所有事务,遍历接收到的区块的事务receipt,获得事务的打包对象,事务id,在多索引表_transaction_status中查找该id,如果找到了则删除。接下来如果在空闲状态下,尝试发送下一条pingpong心跳连接信息。
3 mongo_db_plugin连接该信号,绑定其mongo_db_plugin_impl::accepted_block函数,传入区块内容。

1
2
3
my->accepted_block_connection.emplace( chain.accepted_block.connect( [&]( const chain::block_state_ptr& bs ) {
my->accepted_block( bs );
} ));

accepted_block_connection.emplace 插入信号连接器,当accepted_block从controller发出后,mongodb同样捕获到该信号,调用my->accepted_block( bs );

插件捕捉处理④: producer_plugin连接该信号,执行其on_block函数,传入区块数据。

1
my->_accepted_block_connection.emplace(chain.accepted_block.connect( [this]( const auto& bsp ){ my->on_block( bsp ); } ));

on_block该函数主要是
函数首先做了校验,包括时间是否大于最后签名区块的时间以及大于当前时间,还有区块号是否大于最后签名区块号。校验通过以后,活跃生产者账户集合active_producers开辟新空间,插入计划出块生产者。接下来利用set_intersection取本地生产者与集合active_producers的交集(如果结果为空,说明本地生产者没有出块权利不属于活跃生产者的一份子)。
将结果存入一个迭代器,迭代执行内部函数,如果交集生产者不等于接收区块的生产者,说明是校验别人生产的区块,如果是相等的不必做特殊处理。校验别人生产的区块,首先要在活跃生产者的key中找到匹配的key(本地生产者账户公钥),否则说明该区块不是合法生产者签名抛弃不处理。接下来,获取本地生产者私钥,组装生产确认数据字段,包括区块id,区块摘要,生产者,签名。更新producer插件本地标志位_last_signed_block_time和_last_signed_block_num。最后发射信号confirmed_block,携带以上组装好的数据。但经过搜索,项目中目前没有对该信号设置槽connection。在区块创建之前要为该区块的生产者设置水印用来标示该区块的生产者是谁。

  1. irreversible_block
    发射时机
    on_irreversible函数,更改区块状态为irreversible的函数,操作成功最后发射该信号。
    插件捕捉处理

    1
    2
    3
    my->irreversible_block_connection = my->chain->irreversible_block.connect( [this]( const block_state_ptr& blk ) {
    my->irreversible_block_channel.publish( priority::low, blk );
    } );

chain_plugin连接该信号,由信号槽转播到channel,irreversible_block_channel发布该区块。bnet_plugin订阅该channel,

1
2
3
4
my->_on_irb_handle = app().get_channel<channels::irreversible_block>()
.subscribe( [this]( block_state_ptr s ){
my->on_irreversible_block(s);
});

依然线程池遍历会话,执行on_new_lib函数,当本地库领先时可以清除历史直到满足当前库,或者直到最后一个被远端节点所知道的区块。最后如果空闲,尝试发送下一条pingpong心跳连接信息。
3 mongo_db_plugin连接该信号,执行applied_irreversible_block函数,仍旧参照mongo配置项的值决定是否储存区块、状态区块以及事务数据,然后将区块数据塞入队列等待消费。
4 producer_plugin连接该信号,绑定执行函数on_irreversible_block,设置producer成员_irreversible_block_time的值为区块的时间。

  1. accepted_transaction

发射时机:
1 push_scheduled_transaction函数,推送计划事务时,将事务体经过一系列转型以及校验,接着发射该信号,承认事务。
2 push_transaction函数,新事务到大状态区块,要经过身份认证以及决定是否现在执行还是延期执行,最后要插入到pending区块的receipt接收事务中去。当检查事务未被承认时,发射一次该信号。最后全部函数处理完毕,再次发射该信号。
插件捕捉处理
1 chain_plugin连接该信号,由信号槽转播到channel,accepted_transaction_channel发布该事务。

1
2
3
4
my->accepted_transaction_connection = my->chain->accepted_transaction.connect(
[this]( const transaction_metadata_ptr& meta ) {
my->accepted_transaction_channel.publish( priority::low, meta );
} );

bnet_plugin订阅该channel,线程池遍历会话,执行函数on_accepted_transaction。

1
2
3
4
my->_on_appled_trx_handle = app().get_channel<channels::accepted_transaction>()
.subscribe( [this]( transaction_metadata_ptr t ){
my->on_accepted_transaction(t);
});

在可能是多个的投机块中一个事务被承认,当一个区块包含该承认事务或者切换分叉时,该事务状态变为“receive now”,被添加至数据库表中,作为发送给其他节点的证据。当该事务被发送给其他节点时,根据这个状态可以保证之后不会重复发送。每一次事务被“accepted”,都会延时5秒钟。每次一个区块被应用,所有超过5秒未被应用的但被承认的事务都将被清除。
2 mongo_db_plugin连接该信号,执行函数accepted_transaction,校验加入队列待消费。

  1. applied_transaction

发射时机
1 push_scheduled_transaction函数,事务过期时间小于pending区块时间处理后发射该信号。反之大于等于处理后发射该信号。当事务的sender发送者不为空且没有主观失败的处理后发射该信号。基于生产和校验的主观修改,主观处理后发射该信号,非主观处理发射该信号。
2 push_transaction函数,发射两次该信号,逻辑较多,这段包括以上那个函数的可读性很差,注释几乎没有。
插件捕捉处理
1 net_plugin连接该信号,绑定函数applied_transaction,打印日志。
2 chain_plugin连接该信号,由信号槽转播到channel,原理基本同上,不再重复。
3 mongo_db_plugin同上。

  1. accepted_confirmation

发射时机:
1 push_confirmation函数,推送确认信息,在此阶段不允许有pending区块存在,接着fork_db添加确认信息,发射该信号。
插件捕捉处理
2 net_plugin连接该信号,绑定函数accepted_confirmation,打印日志。
插件捕捉处理
1: chain_plugin连接该信号,由信号槽转播到channel,基本同上。

谢谢关注我的公众号

eos源码剖析之controller的更多相关文章

  1. SpringMVC源码剖析(四)- DispatcherServlet请求转发的实现

    SpringMVC完成初始化流程之后,就进入Servlet标准生命周期的第二个阶段,即“service”阶段.在“service”阶段中,每一次Http请求到来,容器都会启动一个请求线程,通过serv ...

  2. SpringMVC源码剖析(二)- DispatcherServlet的前世今生

    上一篇文章<SpringMVC源码剖析(一)- 从抽象和接口说起>中,我介绍了一次典型的SpringMVC请求处理过程中,相继粉墨登场的各种核心类和接口.我刻意忽略了源码中的处理细节,只列 ...

  3. fasttext源码剖析

    fasttext源码剖析   目的:记录结合多方资料以及个人理解的剖析代码: https://heleifz.github.io/14732610572844.html http://www.cnbl ...

  4. SpringMVC源码剖析1——执行流程

    SpringMVC源码剖析1——执行流程 00.SpringMVC执行流程file:///C:/Users/WANGGA~1/AppData/Local/Temp/enhtmlclip/Image.p ...

  5. 源码剖析@ApiImplicitParam对@RequestParam的required属性的侵入性

    问题起源 使用SpringCloud构建项目时,使用Swagger生成相应的接口文档是推荐的选项,Swagger能够提供页面访问,直接在网页上调试后端系统的接口, 非常方便.最近却遇到了一个有点困惑的 ...

  6. 源码剖析Springboot自定义异常

    博主看到新服务是封装的自定义异常,准备入手剖析一下,自定义的异常是如何进行抓住我们请求的方法的异常,并进行封装返回到.废话不多说,先看看如何才能实现封装异常,先来一个示例: @ControllerAd ...

  7. Linux MMC 驱动子系统简述(源码剖析)

    1. Linux MMC 驱动子系统 块设备是Linux系统中的基础外设之一,而 MMC/SD 存储设备是一种典型的块设备.Linux内核设计了 MMC子系统,用于管理 MMC/SD 设备. MMC ...

  8. jQuery之Deferred源码剖析

    一.前言 大约在夏季,我们谈过ES6的Promise(详见here),其实在ES6前jQuery早就有了Promise,也就是我们所知道的Deferred对象,宗旨当然也和ES6的Promise一样, ...

  9. Nodejs事件引擎libuv源码剖析之:高效线程池(threadpool)的实现

    声明:本文为原创博文,转载请注明出处. Nodejs编程是全异步的,这就意味着我们不必每次都阻塞等待该次操作的结果,而事件完成(就绪)时会主动回调通知我们.在网络编程中,一般都是基于Reactor线程 ...

随机推荐

  1. Vue 使用细节收集

    JSX 中 on 开头的属性名 在用 elementui 中的 el-upload 的时候,他们组件中有一个属性 on-change ,也不知道谁想出来的属性名,太扯淡了,非要 on 开头,我开始的代 ...

  2. 新员工入门 - for测试

    23456人员介绍 XXX 测试工作 [软件] Chrome 浏览器.jsonviewer.Firefox.FireBug HTTP协议与抓包 - fildder.wireshirk等 DB查询工具 ...

  3. gulp.src()内部实现探究

    写在前面 本来是想写个如何编写gulp插件的科普文的,突然探究欲又发作了,于是就有了这篇东西...翻了下源码看了下gulp.src()的实现,不禁由衷感慨:肿么这么复杂... 进入正题 首先我们看下g ...

  4. [ERROR] Failed to execute goal net.alchim31.maven:scala-maven-plugin:3.2.2:compile (scala-compile-first) on project spark-tags_2.11: Execution scala-compile-first of goal net.alchim31.maven:scala-mave

    build/mvn -Pkubernetes -Phadoop-2.7 -Dhadoop.version=2.7.3 -Phive -Phive-thriftserver -DskipTests cl ...

  5. 一种C#泛型方法在lua中表示的设计

    在进行lua方法注册的时候, 大多数解决方案直接否定了泛型方法, 因为在lua侧难以表达出泛型, 以及lua的函数重载问题, 函数重载问题可以通过一些特殊方法解决, 而泛型问题是主要问题, 以Unit ...

  6. PAT甲题题解-1037. Magic Coupon (25)-贪心,水

    题目说了那么多,就是给你两个序列,分别选取元素进行一对一相乘,求得到的最大乘积. 将两个序列的正和负数分开,排个序,然后分别将正1和正2前面的相乘,负1和负2前面的相乘,累加和即可. #include ...

  7. PAT甲题题解-1078. Hashing (25)-hash散列

    二次方探测解决冲突一开始理解错了,难怪一直WA.先寻找key%TSize的index处,如果冲突,那么依此寻找(key+j*j)%TSize的位置,j=1~TSize-1如果都没有空位,则输出'-' ...

  8. 11.3 Daily Scrum

    今天的会议上,我们重新规划了一下每个人的分工.大家的安卓开发环境已经配置完毕,于是我们便正式开始进入代码编写的阶段. 由于修改了一下分工,之前发布的任务作废,以新发布的任务为准.   Today’s ...

  9. Linux第一章读书笔记

    一.Linux历史 Unix强大的根本原因: 1.简洁,仅仅提供几百个系统调用并且有一个非常明确的设计目的 2.文件对待所有东西,通过一套相同的系统调用接口来进行对数据和设备的操作 3.由于用C语言编 ...

  10. 【读书笔记】Linux内核设计与实现(第五章)

    5.1 内核通信 系统调用在用户空间和硬件设备之间添加了一个中间层. 该层主要作用: 1.为用户空间提供了一种硬件的抽象接口. 2.保证了系统的稳定和安全. 3.每个进程都运行在虚拟系统中. 在Lin ...