EOS源碼學習:石墨烯引擎 & EOS插件機制

上一篇已經(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/librarirescontracts四部分組成,可以看出石墨烯的架構和EOS的架構是很相近的,EOS增加了對智能合約的支持。實際上EOS并沒有直接用石墨烯的源代碼,而是重寫了90%的代碼,不過基本架構是一樣的。

EOS插件機制

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

插件體系

EOS插件由三層類來實現(xiàn)。

  • 最頂層是抽象類abstract_plugin,定義了插件的基本接口。
  • 中間層是插件模板類plugin,主要用來解決插件之間依賴調用。
  • 最底層是具體插件類,專注單個插件的業(yè)務功能實現(xiàn)。
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類靜態(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注冊依賴,還定義了插件初始化、啟動、停止三個方法。

initializestartup方法同register_dependencies一樣,調用具體子類的plugin_requires,但是傳入了包含實際處理的lambda閉包,來調用所依賴的插件執(zhí)行初始化/啟動。下級插件完成處理后,執(zhí)行本插件的具體插件類的處理方法plugin_initializeplugin_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)了一套靈活的模塊化插件機制,在抽象插件類和具體功能類之間引入一層模板類,來將插件間依賴調用從具體類中解耦出來,有利于插件功能內聚以及新插件擴展。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,119評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,382評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,038評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,853評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,616評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,112評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,192評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,355評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,869評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,727評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,928評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,467評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,165評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,570評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,813評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,585評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,892評論 2 372

推薦閱讀更多精彩內容