您正在查看: EOS 分类下的文章

ref_block_num 和 ref_block_prefix

先简单记一下,后面再做补充

主要作用是判断下前后交易是不是在一个链上,也就是有没有分叉。

参考

https://github.com/EOSIO/eos/issues/4659
https://eos.live/detail/17094
https://github.com/EOSIO/eosjs/issues/151
http://www.zhongruitech.com/955126221.html

一些三方推荐插件

队列插件:

将区块链数据推入队列

kafka_plugin - 卡夫卡
eos_zmq_plugin - ZeroMQ
eos-rabbitmq-plugin - RabbitMQ

数据库插件:

将区块链数据同步到数据库中

eosio_sql_plugin - 基于SOCI的SQL数据库
eos_sql_db_plugin - MySQL数据库
elasticsearch_plugin - ElasticSearch

推送插件:

通过其他协议将区块链数据通知给消费者:

eosio-watcher-plugin - 针对链操作的HTTP POST到端点
eosio_block_subscription_plugin - 用于链操作的推送通知的TCP服务器

调试插件

eosio_all_code_dump_plugin - 将所有合同代码转储到目录

Block Producer插件:

eos-producer-heartbeat-plugin - BP Heartbeat
blacklist_plugin - 与BP黑名单合同整合

转载自:https://github.com/firesWu/awesome-eosio-plugins

源码解析 区块的分叉处理

在区块链上, 分叉是一个老话题了, EOS 也会出现分叉的情况, 下面我们来讲讲在分叉情况它是如何切换到最长链的。

EOS 的生产者是根据 active_schedule 的顺序来生产区块的,但是在决定当前区块生产者的时候是根据 block_time 来的选择的。 假如当前 BP1 生产了一个 block_num 为 100 的块,而BP2和BP3都接受到了,此时根据block_time是 BP2 生产 block_num 为 101 的块, 但 BP3 因为网络原因没收到 BP2 产生的块, 导致它也开始生产 block_num 为 101 的块了, 这时候出现了分叉。 那么哪条链会被最终确认呢,换句话说 2个 block_num 为 101 的块谁会被不可逆,谁会被抛弃, 这要看两条链上的生产者数量, 生产者多的那条链增长速度会比较快, 所以区块更快被确认,比另一条链先不可逆, 这时候就会切换到长的链,而丢弃短的链了。 一般情况下,其他节点接受完BP2的块后都在等待BP3的出块,BP3的出块将BP2的分支覆盖掉了, 基本所有节点都切换到了BP3的链上并开始生产,所以 BP2 的块被丢弃的可能性很大。

先讲下分叉的大致流程,当一个节点收到同个 block_num 的会都 insert 进 fork_multi_index_type 对应的表里,根据 block_state 的 header.previous 形成多叉树(不了解多叉树的读者可以百度一下哈)。每进一个需要分叉的块都会先切换到该块对应的链上。一旦 block_num 最小的确定是哪个块不可逆后,其他分叉下相同 block_num 的块就会被删除。至于为什么不直接把其他分叉全删了。 如果该分叉还有人在生产块,那么该块在接入节点得时候就会出现 unlinkable_block 的错误了,这块不详解,感兴趣的可以自行了解。

下面分析代码,因为分叉只有接受别人的块才会出现,所以从 push_block 开始看起。

void push_block( const signed_block_ptr& b, controller::block_status s ) {
   // ...

      if ( read_mode != db_read_mode::IRREVERSIBLE ) {
         maybe_switch_forks( s );
      }

   // ...   
   } FC_LOG_AND_RETHROW( )
}
void maybe_switch_forks( controller::block_status s = controller::block_status::complete ) {
   auto new_head = fork_db.head();

   // 无分叉情况,正常叠加块
   if( new_head->header.previous == head->id ) {
      try {
         apply_block( new_head->block, s );
         fork_db.mark_in_current_chain( new_head, true );
         fork_db.set_validity( new_head, true );
         head = new_head;
      } catch ( const fc::exception& e ) {
         fork_db.set_validity( new_head, false ); // Removes new_head from fork_db index, so no need to mark it as not in the current chain.
         throw;
      }
   // 头 id 不同,出现分叉情况
   } else if( new_head->id != head->id ) {
      ilog("switching forks from ${current_head_id} (block number ${current_head_num}) to ${new_head_id} (block number ${new_head_num})",
           ("current_head_id", head->id)("current_head_num", head->block_num)("new_head_id", new_head->id)("new_head_num", new_head->block_num) );
      // 获取两个分支上对应的块
      auto branches = fork_db.fetch_branch_from( new_head->id, head->id );

      // 将前一分支的块都标记成不在当前使用分支。
      for( auto itr = branches.second.begin(); itr != branches.second.end(); ++itr ) {
         fork_db.mark_in_current_chain( *itr , false );
         pop_block();
      }
      EOS_ASSERT( self.head_block_id() == branches.second.back()->header.previous, fork_database_exception,
                 "loss of sync between fork_db and chainbase during fork switch" ); // _should_ never fail
      // 切换分支, apply 当前分支的块
      for( auto ritr = branches.first.rbegin(); ritr != branches.first.rend(); ++ritr) {
         optional<fc::exception> except;
         try {
            apply_block( (*ritr)->block, (*ritr)->validated ? controller::block_status::validated : controller::block_status::complete );
            head = *ritr;
            fork_db.mark_in_current_chain( *ritr, true );
            (*ritr)->validated = true;
         }
         catch (const fc::exception& e) { except = e; }
         if (except) {
            elog("exception thrown while switching forks ${e}", ("e",except->to_detail_string()));

            // ritr currently points to the block that threw
            // if we mark it invalid it will automatically remove all forks built off it.
            fork_db.set_validity( *ritr, false );

            // pop all blocks from the bad fork
            // ritr base is a forward itr to the last block successfully applied
            auto applied_itr = ritr.base();
            for( auto itr = applied_itr; itr != branches.first.end(); ++itr ) {
               fork_db.mark_in_current_chain( *itr , false );
               pop_block();
            }
            EOS_ASSERT( self.head_block_id() == branches.second.back()->header.previous, fork_database_exception,
                       "loss of sync between fork_db and chainbase during fork switch reversal" ); // _should_ never fail

            // re-apply good blocks
            for( auto ritr = branches.second.rbegin(); ritr != branches.second.rend(); ++ritr ) {
               apply_block( (*ritr)->block, controller::block_status::validated /* we previously validated these blocks*/ );
               head = *ritr;
               fork_db.mark_in_current_chain( *ritr, true );
            }
            // 抛出一个切换分支的异常。
            throw *except;
         } // end if exception
      } /// end for each block in branch
      ilog("successfully switched fork to new head ${new_head_id}", ("new_head_id", new_head->id));
   }
} /// push_block
void fork_database::prune( const block_state_ptr& h ) {
   auto num = h->block_num;

   auto& by_bn = my->index.get<by_block_num>();
   auto bni = by_bn.begin();
   while( bni != by_bn.end() && (*bni)->block_num < num ) {
      prune( *bni );
      bni = by_bn.begin();
   }

   auto itr = my->index.find( h->id );
   if( itr != my->index.end() ) {
      irreversible(*itr);
      my->index.erase(itr);
   }

   // 当 num 对应的块不可逆化, 其他分支下同等 block_num 就会被删除
   auto& numidx = my->index.get<by_block_num>();
   auto nitr = numidx.lower_bound( num );
   while( nitr != numidx.end() && (*nitr)->block_num == num ) {
      auto itr_to_remove = nitr;
      ++nitr;
      auto id = (*itr_to_remove)->id;
      remove( id );
   }
} 

其实分叉处理就是一个塑造出一个多叉树, 当多叉树上有节点成为不可逆的时候就可以裁剪。
转载自:https://eos.live/detail/16153

【EOSIO Explorer】EOSIO开发者浏览器功能体验

启动EOSIO Explorer

使用如下命令启动EOSIO Explorer,会同时启动后台服务和前端页面:

cd eosio-explorer
yarn eosio-explorer start

EOSIO Explorer功能介绍

启动后的主界面如下图所示:

Header栏展示了所有功能,分为两部分:INSPECT(检查)和INTERACT(交互);

INSPECT(检查)的功能相当于区块浏览器,有以下功能:

  • INFO:即主页展示的区块链信息,包括连接的Nodeos和MongoDB地址、区块链基本信息等;
  • BLOCKS:区块列表;
  • TRANSACTIONS:交易列表;
  • ACTIONS:Action列表;
  • ACCOUNTS:账户列表;
  • SMART CONTRACT:智能合约列表;

INTERACT(交互)包含如下功能:

  • MANAGE ACCOUNTS:管理账户;
  • DEPLOY CONTRACTS:部署合约;
  • PUSH ACTIONS:调用Action;

下面详细介绍这些功能。

账户管理

在账户管理页面可以直接创建账户,会自动生成一对公私钥进行创建:

回到账户管理页面查看账户列表:

区块信息查询

上面创建账户的操作,实际是调用eosio::newaccount Action,调用信息可以在INSPECT的各个tab中查看。
查看交易列表:

查看Action列表:

查看Action详情:

查询账户信息:

编译/部署合约

DEPLOY CONTRACTS界面提供一个编辑器,可以把合约源文件上传(支持拖拽)到编辑器中:

填写好cpp源文件根路径,点击“GENERATE ABI”生成ABI,如果代码存在错误,会在右侧Compiler Errors框提示:

ABI和WASM生成好后选择一个账户来部署合约:

成功部署合约后,右侧会展示部署信息:

同时可以在SMART CONTRACT界面查询合约信息:

调用合约

在PUSH ACTIONS界面可以调用合约的Action:

小结

使用EOSIO Explorer,不需要使用命令,就执行了公私钥的生成、账户创建、合约的编译/部署/调用等操作,省去大量繁琐的命令操作时间,让开发者可以把注意力集中在智能合约逻辑的开发上;

EOSIO Explorer对于熟练的EOS智能合约开发人员是很好的工具,但对于合约开发初学者,还是建议先学习命令行操作,等理解原理后再使用可视化开发工具。

转载地址:https://bihu.com/article/1900672350

查看EOS主网BP配置

cleos -u https://api.eoslaomao.com get table eosheartbeat eosheartbeat hbstruct