EOS 源码学习之项目结构分析(plugins) 整体的代码位置主要位于plugins、libraries。
eos项目采用了插件管理的结构 。
入口 programs的文件夹结构如下,在eos官方wiki 中可以查看具体每个目录的含义及作用。这里仅备注几个示例。
1 2 3 4 5 6 7 8 9 10 11 12 .. eos/programs: ├── CMakeLists.txt ├── cleos //命令行工具,如果把eos比作操作系统的话,cleos就类似终端作用 ├── debug_node ├── eosio-abigen ├── eosio-applesedemo ├── eosio-launcher ├── genesis ├── keosd //钱包 ├── nodeos //节点核心进程 └── snapshot
要启动一个节点,需要用到的便是nodeos,cleos中可使用的REST API便是nodeos中暴露出来的。那么,我们看看nodeos。
源代码main.cpp中,代码仅有105行。主要看看main方法吧(不想看代码可以略过代码部分 = =),通过代码可以看到,main方法作用包括设置版本、工作路径等基本信息,以及log的初始化,另外就是startup方法看起来就是关键!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 int main(int argc, char** argv) { try { app().set_version(eosio::nodeos::config::version); auto root = fc::app_path(); app().set_default_data_dir(root / "eosio/nodeos/data" ); app().set_default_config_dir(root / "eosio/nodeos/config" ); if(!app().initialize<chain_plugin, http_plugin, net_plugin, producer_plugin>(argc, argv)) return -1; initialize_logging(); ilog("nodeos version ${ver}", ("ver", eosio::utilities::common::itoh(static_cast<uint32_t>(app().version())))); ilog("eosio root is ${root}", ("root", root.string())); //重要的代码!!! app().startup(); app().exec(); } catch (const fc::exception& e) { } .....(此处省略多个catch) return 0; }
app()位置位于
1 2 ./libraries/appbase/application.cpp //app()方法返回application的实例(对c++的类的机制不了解,这个实例像是静态对象,反正就是通过返回的实例可以调用相关方法吧,包括startup这个看起来就跟关键的方法)
进入到application.cpp中,例举相关方法
initialize_..: 初始化插件,功能包括调用各个插件设置命令行参数,以及初始化方法,还有app自身对应初始化操作
startup: 循环各插件,调用各插件的startup方法
exec: 表示完全看不懂= =!!。猜测一下,应该是监听io事件,例如结束进程系列..
从这里可以看出,主要的具体的重要的内容都在各插件内部。
插件 eos项目采用了插件管理的结构。插件的目录如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 .. eos/plugins: ├── CMakeLists.txt ├── account_history_api_plugin ├── account_history_plugin ├── chain_api_plugin ├── chain_plugin ├── eosio-make_new_plugin.sh ├── faucet_testnet_plugin ├── http_plugin ├── mongo_db_plugin ├── net_api_plugin ├── net_plugin ├── producer_plugin ├── template_plugin ├── txn_test_gen_plugin ├── wallet_api_plugin └── wallet_plugin
完全可以通过命名来理解各插件的含义。要分析的话,直接从相应plugins入手。逐一观察..
eos/nodeos学习 main.cpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 //设置版本 app().set_version(eosio::nodeos::config::version); auto root = fc::app_path(); //设置工作路径 app().set_default_data_dir(root / "eosio/nodeos/data" ); app().set_default_config_dir(root / "eosio/nodeos/config" ); //单节点需要的插件包括以下四个 if(!app().initialize<chain_plugin, http_plugin, net_plugin, producer_plugin>(argc, argv)) return -1; initialize_logging(); ilog("nodeos version ${ver}", ("ver", eosio::nodeos::config::itoh(static_cast<uint32_t>(app().version())))); ilog("eosio root is ${root}", ("root", root.string())); app().startup(); app().exec();
主要是配置和app的一些调用。然后转到app中。
1 ./libraries/appbase/application.cpp
app: 返回application的实例(实例只是声明,木有初始化,对c++类机制不太了解,需进一步了解,目前就理解成这个实例就可访问application::method方法)
initialize_..: 初始化插件,功能包括调用各个插件设置命令行参数,以及初始化方法,还有app自身对应初始化操作
startup: 循环各插件,调用各插件的startup方法
exec: 表示完全看不懂= =!!。猜测一下,应该是监听io事件,例如结束进程系列..
那么针对单个节点启动必要的4个插件,进行逐个学习。
首先是插件父方法:(在application中调用的方法名直接是start_up,没有plugin前缀,这个问题有待解决..)
Chain_plugin Producer_plugin 1 2 3 4 5 6 7 8 // dpos共识 地方 -> get_scheduled_producer的定义在chain_controller auto scheduled_producer = chain.get_scheduled_producer( slot ); // we must control the producer scheduled to produce the next block. if( _producers.find( scheduled_producer ) == _producers.end() ) { capture("scheduled_producer", scheduled_producer); return block_production_condition::not_my_turn; }
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 update_signing_producer(const producer_object& signing_producer, const signed_block& new_block) update_or_create_producers( const producer_schedule_type& producers) _calculate_producer_schedule() { producer_schedule_type schedule = get_global_properties().new_active_producers; const auto& hps = _head_producer_schedule(); schedule.version = hps.version; if ( hps != schedule ) ++schedule.version; return schedule; } _update_producers_authority() head_block_producer() get_producer(const account_name& ownername) get_scheduled_producer(uint32_t slot_num) { const dynamic_global_property_object& dpo = get_dynamic_global_properties(); uint64_t current_aslot = dpo.current_absolute_slot + slot_num; ... auto index = current_aslot % (number_of_active_producers * config::producer_repetitions); } producer_participation_rate() 关键 update_last_irreversible_block
producer源码学习 代码位置为..eos/plugins/producer_plugin/
文件夹结构如下,producer_plugin.hpp为方法的声明, producer_plugin.cpp为具体实现
1 2 3 4 5 6 ├── CMakeLists.txt ├── include │ └── eosio │ └── producer_plugin │ └── producer_plugin.hpp └── producer_plugin.cpp
虽然producer_plugin.cpp的代码也只有不到400行,但节省时间和资源,就挑出块相关的说吧。方法调用如下
1 plugin_startup -> schedule_production_loop(到没到出块时间,到了就出块,这个方法不断循环) -> block_production_loop -> maybe_produce_block
maybe_produce_block方法是最后决定是不是出块的。以下主要解释maybe_produce_block方法
首先会检查区块是否同步到最新。
接下来这段代码会判断是否到达出块时间。
1 2 3 4 5 6 uint32_t slot = chain.get_slot_at_time( now ); if( slot == 0 ) { capture("next_time", chain.get_slot_time(1)); return block_production_condition::not_time_yet; }
然后就选择出块producer了。
1 auto scheduled_producer = chain.get_scheduled_producer( slot );
完了判断这个scheduled_producer是不是自己,是的话,再检查点别的(例如私钥啊等),最后就出块了。
那么,关键代码就是get_scheduled_producer方法。get_scheduled_producer的追踪要到chain_controller.cpp文件中了。位置位于./eos/libriaries/chain/chain_controller.cpp。以下代码未做特殊说明,都位于chain_controller.cpp中。
首先看get_scheduled_producer的代码,可以看到,③通过从db中读出global_property_object对象gpo(下面会有介绍),gpo.active_producers为当前活跃的producers,然后④⑤为算得当前索引下标index,最后根据index在gpo.active_producers中取出相应producer即为返回值。下标计算的方式就是数学算式index=当前区块位置 % (producers总数 * 每个producer重复出多少块)。最后再除以每个producer重复出多少块。比较不理解的是当前区块位置(代码中的current_aslot)的计算方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 //具体代码 /* * 1. slot_num始终对应于未来的某一时刻 * 2.对于slot_num的值,相对当前多少区块的间隔, * 例如,slot_num == 1 则为下一个区块的producer, * slot_num == 2,则为在一个区块间隔后的下一个producer。slot_num即为区块间隔数 * 3.使用方法get_slot_time和get_slot_at_time作为slot_num和时间戳的转换 * 4.slot_num == 0,返回EOS_NULL_PRODUCER */ account_name chain_controller::get_scheduled_producer(uint32_t slot_num)const { const dynamic_global_property_object& dpo = get_dynamic_global_properties(); //① uint64_t current_aslot = dpo.current_absolute_slot + slot_num; //② const auto& gpo = _db.get<global_property_object>(); //③ auto number_of_active_producers = gpo.active_producers.producers.size(); auto index = current_aslot % (number_of_active_producers * config::producer_repetitions); //④ index /= config::producer_repetitions; //⑤ FC_ASSERT( gpo.active_producers.producers.size() > 0, "no producers defined" ); return gpo.active_producers.producers[index].producer_name; }
关于global_property_object主要属性有active_producers、new_active_producers、pending_active_producers。
active_producers是当前轮的producers, pending_active_producers为满足排名等要求的producers, new_active_producers为所有可投票(或者是所有备选)的producer。
在controller_chain.cpp中有如下几个方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 //从投票排名中取出前m个producer,并排除block_signing_key是null的那些,m为配置中的producer_count。然而代码似乎没全. _calculate_producer_schedule() {...} //更新global_properties update_global_properties(){...} //上面提到过的有可能更新active_producers的方法 update_last_irreversible_block(){...} /* * After applying all transactions successfully we can update * the current block time, block number, producer stats, etc */ _finalize_block(){...}
主要通过上面列举的方法介绍active_producers、new_active_producers、pending_active_producers是如何关联的。下面一段如果觉得各个方法调用比较蒙的话,可以直接看三个加粗句子,可以了解到active_producers、new_active_producers、pending_active_producers之间的关联。
在_finalize_block调用的时候,首先调用update_global_properties,在update_global_properties方法中调用了_calculate_producer_schedule方法,calculate_producer_schedule方法实现的是从new_active_producers中选出符合条件的producers ,然后回到update_global_properties,根据选出的producers,更新pending_active_producers ,然后回到_finalize_block方法,调用update_global_properties之后调用update_last_irreversible_block方法,通过pending_active_producers适当更新active_producers 。
然后,new_active_producers是通过wasm的api进行更新。这样就缕通了,通过投票客户端,进行投票,并控制更新new_active_producers参数选手,和相应票数。然后根据new_active_producers和票数决定blocker producers。
那么,blocker producers的顺序呢??? 不知道是被我疏忽了还是真的没看到。
producer源码中,可以看到,上层逻辑在 producer_plugin.cpp中,基本的dpos等逻辑还是在chain_controller.cpp中。