eos源碼賞析(九):EOS智能合約入門之區塊打包和廣播機制

首先感謝羣裏的大佬中山狼、linx、阿泥豆等各位給予的指導。

在上篇文章中我們寫到了eos中區塊產生的調用流程,其主要過程是從插件中的producer_pligin去產生區塊,而實際產生區塊的過程卻是在chain中的controller.cpp中實現的。通過以前的文章我們知道,在eos區塊的產生並不僅僅是單獨產生的過程,它還需要進行區塊打包、入庫、廣播、上鍊等過程,今天我們就來談談區塊產生之後又進行了哪些操作。

  1. C++Tips:

在文章的開始,我們先熟悉一下C++中的一些概念,有助於我們接下來的代碼分析。下面的一些定義及示例均來自於https://zh.cppreference.com,僅做參考。當然從文字上理解有些枯燥且分類較多,但對於我們理解eos源碼有很大的幫助。當然不感興趣的可以直接跳過。

左值和右值的概念

   在C++11中,左值和右值的區分可以從以下概念入手:

        具有同一性 (identity) :可以確定表達式是否與另一表達式指代同一實體,例如通過比較它們所標識的對象或函數的(直接或間接獲得的)地址;

        可被移動:移動構造函數、移動賦值運算符或實現了移動語義的其他函數重載能夠綁定於這個表達式。

        具有同一性且不可被移動的表達式被稱作左值 (lvalue) 表達式;

        具有同一性且可被移動的表達式被稱作亡值 (xvalue) 表達式;

        不具有同一性且可被移動的表達式被稱作純右值 (prvalue) 表達式;

        不具有同一性且不可被移動的表達式無法使用。

        具有同一性的表達式被稱作「泛左值表達式 (glvalue expressions) 」。左值和亡值都是泛左值表達式。簡單的來說,能取地址的變量一定是左值,有名字的變量也一定是左值,右值引用也是左值。舉例說明下:

                                                                    圖1 左值表達式包含的類型

        可被移動的表達式被稱作「右值表達式 (rvalue expressions) 」。純右值和亡值都是右值表達式。舉例說明下:

                                                                         圖2 右值表達式包含的類型

std::move

        std::move 用於指示對象 t 可以「被移動」,即允許從 t 到另一對象的有效率的資源傳遞。特別是, std::move 生成標識其參數 t 的亡值表達式。它準確地等價於到右值引用類型的 static_cast 。有時候我們希望把左值當作右值來使用,例如一個變量的值,不再使用了,希望把它的值轉移出去,C++11中的std::move就爲我們提供了將左值引用轉爲右值引用的方法。舉個例子關於std::move的用法:

                                                                             圖3 std::move使用示例

std::forward: 

   std::forward是有條件轉換。只有在它的參數綁定到一個右值時,它才轉換它的參數到一個右值。當參數綁定到左值時,轉換後仍爲左值。萬能的函數包裝器,可將帶返回值、不帶返回值、帶參和不帶參的函數委託萬能的函數包裝器執行。

完美轉發

   完美轉發是指在函數模板中,完全依照模板的參數類型(即保持參數的左值、右值特徵),將參數傳遞給函數模板中調用的另外一個函數。

下面是使用std::forward實現完美轉發的一個例子:

                                                 圖4 std::forward實現完美轉發示例

        上面兩個例子的運行結果各位可以嘗試着運行一下,有助於加深對以上概念的理解。

  1. 區塊打包:

        介紹完了這些C++小知識,讓我們回到正題,生成的區塊是如何進行打包並廣播出去的。我們在上篇文章中也提到區塊產生之後的pending會送到push_transactions中,具體的push_transaction截圖如下:

                                                                             圖5 push_transaction源碼

        我們知道在start_block中產生的區塊是未經多節點確認過的,因此這裏傳入的implicit是爲true,即這個塊或者說這次交易是未確認的狀態,此處我們使用init_for_implicit_trx對該區塊進行初始化。而後,將本次交易的回執信息如是否執行成功、CPU的使用情況、net的使用情況等寫入到本次交易的回執trace->receipt中。這裏需要注意區分trace、trx、trx_context之間的區別與聯繫。trx則是包含了本次區塊產生的交易信息,trx_context則是將trx的信息寫入到trx_context類中方便接下來的使用,而trace爲trx_context中的一個變量類型爲action_trace的值。接下來我們可以看到這三者的區別。

                                                                             圖6 push_transaction源碼

        在圖6的標註1中我們可以看到,本次交易的回執信息填充結束之後,調用fc::move_append將trx_context使用move的方式轉化爲右值引用,即move到區塊的action中去。那麼這個move_append又是實現了什麼功能呢?

                                                                   圖7 eos源碼中move_append

        move_append中同樣使用了move,在目標容器爲空的情況下講trx_context中的內容全部放心去。當目標容器不爲空的情況下,則從目標容器末端開始循環插入trx_context中的信息。而這個目標容器就是pending->_action,就將其打包到區塊的_action中去,這個_action爲包含有交易回執信息的區塊信息。以上操作完成了區塊的生產和區塊打包的過程,接下來該做些什麼呢?當然是把區塊信息發佈到網絡上或者說廣播出去,讓節點們去驗證該區塊的存在。

在eos中是如何將區塊信息廣播出去的呢?我們可以在圖6中看到,使用了emit將trx區塊內容信息或者將trace區塊跟蹤信息廣播出去。emit的具體實現如下圖:

                                                                              圖8 eos源碼中emit的實現

        這就是我們上面所提到的std::forward的功能,在函數模板的情況下,完全依照模板的參數類型(即保持參數的左值、右值特徵),將參數傳遞給函數模板中調用的另外一個函數。這裏trx和trace均爲左值,因其可以賦值且可取址,而後通過完美轉發將參數傳遞給了Signal。這樣Signal中便存在着可以使用的右值。恰如emit( self.accepted_transaction, trx)和emit(self.applied_transaction, trace)。

        熟悉信號槽的人看到emit不免會想,這是不是就是信號槽機制?沒錯,這正是boost中的signal-slot的機制。信號會在某個特定情況或動作下被觸發,槽是等同與接受並處理信號的函數。做過qt開發的人對信號槽機制並不會陌生,拿最簡單的on_pushButton_clicked()函數來講,當某一個特定事件發生時(clicked),一個信號被髮送(emit),與信號相關聯(connect)的槽(slot)則會響應信號並完成相應的處理。而在boost中也存在類似的機制,我們結合eos源碼中關於區塊廣播來分析下信號槽的實現。在圖4中我們知道,通過std::forward將左值trx或trace進行了完美轉發變成了信號量Signal,通過跟蹤可以找到這些Signal對應的slot,均存在於net_plugin中,如下圖:

                                                        圖9 net_plugin啓動是綁定信號和槽

和大多數信號槽機制一樣在net_plugin啓動的時候,會去綁定信號和槽之間的關係。通過cc可以獲取當前鏈上的絕大多數信息,而後使用connect的方式綁定了以下信號量,在區塊廣播出去的過程中並不存在confirm因此通過代碼跟蹤或者日誌打印,一個區塊產生、打包、廣播出去的過程中只包含了accepted_transaction、applied_transaction、irreversible_block、accepted_block_header、accepted_block,需要注意的是,這裏的五個過程是有先後順序的。

                                                                    圖10 eos中區塊產生時的信號量

    在on_ irreversible中廣播區塊的是否可逆信息

                                                                               圖11 on_ irreversible

    在commit_block中廣播區塊的相關信息。

                                                                      圖12 commit_block

    最終在net_plugin裏面接收到的消息如下打印:

                                                                           圖13 日誌打印結果

   區塊的廣播機制大致如此。當然,又遠不如此,如accepted_block從哪裏來,會不會是已經上鍊的區塊呢?這些區塊信息廣播出去之後會做怎樣或者怎樣被操作呢,咱們下篇文章,關於eos區塊上鍊機制再聊。

            如果你對eos開發感興趣,長按以下二維碼,關注本公衆號,一起學習eos開發.

 

                                                                                          微信公衆號

         有任何疑問或者指教請添加本人個人公衆號,當然有對eos開發感興趣或者金庸粉的也可以添加,備註eos開發或金庸,拉你進羣一起交流

 

 

                                                                                          個人微信帳號