上一篇已经分析了EOS节点程序eosd
通过插件化的架构组织各种服务功能,本篇将介绍EOS所使用的石墨烯区块链引擎,并且介绍使用石墨烯引擎的eosd
的插件管理和注册机制。
石墨烯引擎
什么是石墨烯,根据官网介绍,
The Graphene blockchain is not a monolithic application. It is composed of a variety of libraries and executables to provide deployable nodes.
石墨烯由一组库和可执行程序组成,用于提供可部署的区块链节点的解决方案。石墨烯架构已经成功应用于BitShare, Steem等区块链项目上。下图是石墨烯的源码组织方式。
- 应用层
Executables
:最下层的可执行程序有见证节点witness_node
,独立钱包cli_wallet
和构造创世区块的工具genesis_util
。应用层是对插件库、核心API库以及通用工具库的调用组合,实现其业务功能。 - 插件层
Plugin-Ins
:插件对核心API进行封装以提供较为完整独立的服务,譬如区块链查询,交易验证执行,打包区块,P2P网络通信等服务。 - 核心API层
API/Core
: 实现了基础核心业务功能组件,譬如网络、数据库,钱包相关功能(签名,私钥生成,验证),区块打包计算。 - 通用工具库
FC utilities
:提供业务无关的基础功能工具。
本系列开篇简单介绍过EOS由programs
/plugins
/librarires
和contracts
四部分组成,可以看出石墨烯的架构和EOS的架构是很相近的,EOS增加了对智能合约的支持。实际上EOS并没有直接用石墨烯的源代码,而是重写了90%的代码,不过基本架构是一样的。
EOS插件机制
原始的石墨烯源码就不必看了,直接从eos入手了解石墨烯框架。
插件体系
EOS插件由三层类来实现。
- 最顶层是抽象类abstract_plugin,定义了插件的基本接口。
- 中间层是插件模板类plugin,主要用来解决插件之间依赖调用。
- 最底层是具体插件类,专注单个插件的业务功能实现。
插件注册
同本系列上篇介绍,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类静态单例对象,调用类的模板成员函数register_plugin。
class application
{
...
template<typename Plugin>
auto& register_plugin() {
auto existing = find_plugin<Plugin>(); // 根据类名字查找已经注册的插件集
if(existing) // 已经注册过的就不再重复注册
return *existing; // 返回插件引用
auto plug = new Plugin(); // 还没注册的就new一个插件对象
plugins[plug->name()].reset(plug); // 根据类名注册到插件集中
plug->register_dependencies(); // 注册插件的下一级依赖
return *plug; // 返回注册插件引用
}
...
}
查找插件
注册插件集合使用了application的map类成员plugins
,注册key是插件类名,value是指向插件抽象对象的指针,并且使用了std::unique_ptr
防止插件对象被非法引用。插件抽象类定义了插件的必要接口,包括当前状态、名字、初始化、启停接口,所有的具体插件都要实现这些接口。
map<string, std::unique_ptr<abstract_plugin>> plugins; ///< 所有注册的插件对象
// 插件抽象类定义了插件的必要接口
class abstract_plugin {
public:
enum state {
registered, ///< 插件已经构建但还没做任何事情 the plugin is constructed but doesn't do anything
initialized, ///< 插件已经初始化所有状态,但仍处于待启动状态 the plugin has initialized any state required but is idle
started, ///< 插件已经启动,在运行中 the plugin is actively running
stopped ///< 插件已经停止 the plugin is no longer running
};
virtual ~abstract_plugin(){}
virtual state get_state()const = 0; // 插件当前状态
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模板成本函数
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必须要保证另外两个插件也被注册。
具体插件通过实例化插件模板类
来定义,需要指定具体插件类作为模板参数。在模板类的register_dependencies函数里调用了子类的plugin_requires函数,传入了一个空的函数闭包。
// 插件模板类,需要指定具体插件类作为模板参数
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,这个宏的参数指定了当前插件所依赖的其他插件。
#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表达式的传入参数是注册后的插件对象引用,不过,register_dependencies
里的lambda是[&](auto& plug){}
,实际执行体为空,所以没有对依赖的插件做进一步处理。
插件初始化、启停
插件模板类
除了定义register_dependencies
注册依赖,还定义了插件初始化、启动、停止三个方法。
initialize
和startup
方法同register_dependencies
一样,调用具体子类的plugin_requires
,但是传入了包含实际处理的lambda闭包,来调用所依赖的插件执行初始化/启动。下级插件完成处理后,执行本插件的具体插件类的处理方法plugin_initialize
和plugin_startup
。
shutdown
方法由app统一调度所有已注册过(直接或间接注册)的插件shutdown,所以无需进一步调用依赖的插件执行。
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采用石墨烯引擎为基础构建区块链,并且实现了一套灵活的模块化插件机制,在抽象插件类和具体功能类之间引入一层模板类,来将插件间依赖调用从具体类中解耦出来,有利于插件功能内聚以及新插件扩展。