eos原始碼賞析(九):EOS智慧合約入門之區塊打包和廣播機制
首先感謝群裡的大佬中山狼、linx、阿泥豆等各位給予的指導。
在上篇文章中我們寫到了eos中區塊產生的呼叫流程,其主要過程是從外掛中的producer_pligin去產生區塊,而實際產生區塊的過程卻是在chain中的controller.cpp中實現的。通過以前的文章我們知道,在eos區塊的產生並不僅僅是單獨產生的過程,它還需要進行區塊打包、入庫、廣播、上鍊等過程,今天我們就來談談區塊產生之後又進行了哪些操作。
-
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實現完美轉發示例
上面兩個例子的執行結果各位可以嘗試著執行一下,有助於加深對以上概念的理解。
-
區塊打包:
介紹完了這些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開發或金庸,拉你進群一起交流
個人微信帳號