EOS源码学习:石墨烯引擎 & EOS插件机制

上一篇已经分析了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/librarirescontracts四部分组成,可以看出石墨烯的架构和EOS的架构是很相近的,EOS增加了对智能合约的支持。实际上EOS并没有直接用石墨烯的源代码,而是重写了90%的代码,不过基本架构是一样的。

EOS插件机制

原始的石墨烯源码就不必看了,直接从eos入手了解石墨烯框架。

插件体系

EOS插件由三层类来实现。

  • 最顶层是抽象类abstract_plugin,定义了插件的基本接口。
  • 中间层是插件模板类plugin,主要用来解决插件之间依赖调用。
  • 最底层是具体插件类,专注单个插件的业务功能实现。
EOS插件体系

插件注册

本系列上篇介绍,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注册依赖,还定义了插件初始化、启动、停止三个方法。

initializestartup方法同register_dependencies一样,调用具体子类的plugin_requires,但是传入了包含实际处理的lambda闭包,来调用所依赖的插件执行初始化/启动。下级插件完成处理后,执行本插件的具体插件类的处理方法plugin_initializeplugin_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采用石墨烯引擎为基础构建区块链,并且实现了一套灵活的模块化插件机制,在抽象插件类和具体功能类之间引入一层模板类,来将插件间依赖调用从具体类中解耦出来,有利于插件功能内聚以及新插件扩展。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容