上一篇已經(jīng)分析了EOS節(jié)點程序eosd
通過插件化的架構組織各種服務功能,本篇將介紹EOS所使用的石墨烯區(qū)塊鏈引擎,并且介紹使用石墨烯引擎的eosd
的插件管理和注冊機制。
石墨烯引擎
什么是石墨烯,根據(jù)官網(wǎng)介紹,
The Graphene blockchain is not a monolithic application. It is composed of a variety of libraries and executables to provide deployable nodes.
石墨烯由一組庫和可執(zhí)行程序組成,用于提供可部署的區(qū)塊鏈節(jié)點的解決方案。石墨烯架構已經(jīng)成功應用于BitShare, Steem等區(qū)塊鏈項目上。下圖是石墨烯的源碼組織方式。
- 應用層
Executables
:最下層的可執(zhí)行程序有見證節(jié)點witness_node
,獨立錢包cli_wallet
和構造創(chuàng)世區(qū)塊的工具genesis_util
。應用層是對插件庫、核心API庫以及通用工具庫的調用組合,實現(xiàn)其業(yè)務功能。 - 插件層
Plugin-Ins
:插件對核心API進行封裝以提供較為完整獨立的服務,譬如區(qū)塊鏈查詢,交易驗證執(zhí)行,打包區(qū)塊,P2P網(wǎng)絡通信等服務。 - 核心API層
API/Core
: 實現(xiàn)了基礎核心業(yè)務功能組件,譬如網(wǎng)絡、數(shù)據(jù)庫,錢包相關功能(簽名,私鑰生成,驗證),區(qū)塊打包計算。 - 通用工具庫
FC utilities
:提供業(yè)務無關的基礎功能工具。
本系列開篇簡單介紹過EOS由programs
/plugins
/librarires
和contracts
四部分組成,可以看出石墨烯的架構和EOS的架構是很相近的,EOS增加了對智能合約的支持。實際上EOS并沒有直接用石墨烯的源代碼,而是重寫了90%的代碼,不過基本架構是一樣的。
EOS插件機制
原始的石墨烯源碼就不必看了,直接從eos入手了解石墨烯框架。
插件體系
EOS插件由三層類來實現(xiàn)。
- 最頂層是抽象類abstract_plugin,定義了插件的基本接口。
- 中間層是插件模板類plugin,主要用來解決插件之間依賴調用。
- 最底層是具體插件類,專注單個插件的業(yè)務功能實現(xiàn)。
插件注冊
同本系列上篇介紹,eosd
進程啟動后第一步是注冊插件。
int main(int argc, char** argv)
{
...
// 注冊插件
app().register_plugin<net_api_plugin>();
...
app().register_plugin<faucet_testnet_plugin>();
// app初始化
if(!app().initialize<chain_plugin, http_plugin, net_plugin>(argc, argv))
return -1;
...
}
app() 返回application類靜態(tài)單例對象,調用類的模板成員函數(shù)register_plugin。
class application
{
...
template<typename Plugin>
auto& register_plugin() {
auto existing = find_plugin<Plugin>(); // 根據(jù)類名字查找已經(jīng)注冊的插件集
if(existing) // 已經(jīng)注冊過的就不再重復注冊
return *existing; // 返回插件引用
auto plug = new Plugin(); // 還沒注冊的就new一個插件對象
plugins[plug->name()].reset(plug); // 根據(jù)類名注冊到插件集中
plug->register_dependencies(); // 注冊插件的下一級依賴
return *plug; // 返回注冊插件引用
}
...
}
查找插件
注冊插件集合使用了application的map類成員plugins
,注冊key是插件類名,value是指向插件抽象對象的指針,并且使用了std::unique_ptr
防止插件對象被非法引用。插件抽象類定義了插件的必要接口,包括當前狀態(tài)、名字、初始化、啟停接口,所有的具體插件都要實現(xiàn)這些接口。
map<string, std::unique_ptr<abstract_plugin>> plugins; ///< 所有注冊的插件對象
// 插件抽象類定義了插件的必要接口
class abstract_plugin {
public:
enum state {
registered, ///< 插件已經(jīng)構建但還沒做任何事情 the plugin is constructed but doesn't do anything
initialized, ///< 插件已經(jīng)初始化所有狀態(tài),但仍處于待啟動狀態(tài) the plugin has initialized any state required but is idle
started, ///< 插件已經(jīng)啟動,在運行中 the plugin is actively running
stopped ///< 插件已經(jīng)停止 the plugin is no longer running
};
virtual ~abstract_plugin(){}
virtual state get_state()const = 0; // 插件當前狀態(tài)
virtual const std::string& name()const = 0; // 名字
virtual void set_program_options( options_description& cli, options_description& cfg ) = 0;
// 設定命令行/配置文件中允許的可配置選項
virtual void initialize(const variables_map& options) = 0; // 初始化
virtual void startup() = 0; // 啟動插件
virtual void shutdown() = 0; // 停止插件
};
// application的find_plugin模板成本函數(shù)
class application {
...
template<typename Plugin>
Plugin* find_plugin()const {
// 利用boost工具獲取插件類名,再到注冊類集合中查找
string name = boost::core::demangle(typeid(Plugin).name());
return dynamic_cast<Plugin*>(find_plugin(name));
}
...
}
插件依賴注冊
插件之間可能存在依賴關系,譬如net_api_plugin依賴net_plugin和http_plugin,即application想要使用net_api_plugin必須要保證另外兩個插件也被注冊。
具體插件通過實例化插件模板類
來定義,需要指定具體插件類作為模板參數(shù)。在模板類的register_dependencies函數(shù)里調用了子類的plugin_requires函數(shù),傳入了一個空的函數(shù)閉包。
// 插件模板類,需要指定具體插件類作為模板參數(shù)
template<typename Impl>
class plugin : public abstract_plugin {
...
virtual void register_dependencies() {
static_cast<Impl*>(this)->plugin_requires([&](auto& plug){});
}
...
}
我們看到具體插件類中,是通過宏APPBASE_PLUGIN_REQUIRES
來定義plugin_requires,這個宏的參數(shù)指定了當前插件所依賴的其他插件。
#define APPBASE_PLUGIN_REQUIRES_VISIT( r, visitor, elem ) \
visitor( appbase::app().register_plugin<elem>() );
#define APPBASE_PLUGIN_REQUIRES( PLUGINS ) \
template<typename Lambda> \
void plugin_requires( Lambda&& l ) { \
BOOST_PP_SEQ_FOR_EACH( APPBASE_PLUGIN_REQUIRES_VISIT, l, PLUGINS ) \
}
class net_api_plugin : public plugin<net_api_plugin> {
public:
// net_api_plugin依賴了net_plugin和http_plugin兩個插件
APPBASE_PLUGIN_REQUIRES((net_plugin) (http_plugin))
...
}
對宏展開如下,包含了對net_plugin和http_plugin的注冊。
class net_api_plugin : public plugin<net_api_plugin> {
public:
void plugin_requires( Lambda&& l ) {
lambda(appbase::app().register_plugin<net_plugin>());
lambda(appbase::app().register_plugin<http_plugin>());
}
...
}
lambda表達式的傳入?yún)?shù)是注冊后的插件對象引用,不過,register_dependencies
里的lambda是[&](auto& plug){}
,實際執(zhí)行體為空,所以沒有對依賴的插件做進一步處理。
插件初始化、啟停
插件模板類
除了定義register_dependencies
注冊依賴,還定義了插件初始化、啟動、停止三個方法。
initialize
和startup
方法同register_dependencies
一樣,調用具體子類的plugin_requires
,但是傳入了包含實際處理的lambda閉包,來調用所依賴的插件執(zhí)行初始化/啟動。下級插件完成處理后,執(zhí)行本插件的具體插件類的處理方法plugin_initialize
和plugin_startup
。
shutdown
方法由app統(tǒng)一調度所有已注冊過(直接或間接注冊)的插件shutdown,所以無需進一步調用依賴的插件執(zhí)行。
template<typename Impl>
class plugin : public abstract_plugin {
...
virtual void initialize(const variables_map& options) override {
if(_state == registered) {
_state = initialized;
// 對下級依賴插件調用初始化
static_cast<Impl*>(this)->plugin_requires([&](auto& plug){ plug.initialize(options); });
// 當前插件初始化
static_cast<Impl*>(this)->plugin_initialize(options);
//ilog( "initializing plugin ${name}", ("name",name()) );
app().plugin_initialized(*this); // 在application中記錄
}
assert(_state == initialized); /// if initial state was not registered, final state cannot be initiaized
}
virtual void startup() override {
if(_state == initialized) {
_state = started;
static_cast<Impl*>(this)->plugin_requires([&](auto& plug){ plug.startup(); });
static_cast<Impl*>(this)->plugin_startup();
app().plugin_started(*this);
}
assert(_state == started); // if initial state was not initialized, final state cannot be started
}
virtual void shutdown() override {
if(_state == started) {
_state = stopped;
//ilog( "shutting down plugin ${name}", ("name",name()) );
static_cast<Impl*>(this)->plugin_shutdown();
}
}
...
}
總結
EOS采用石墨烯引擎為基礎構建區(qū)塊鏈,并且實現(xiàn)了一套靈活的模塊化插件機制,在抽象插件類和具體功能類之間引入一層模板類,來將插件間依賴調用從具體類中解耦出來,有利于插件功能內聚以及新插件擴展。