導讀:上一篇文章Polkadot 茶溪岸啤(XCMP),乾杯!主要介紹了XCMP一些基本內容,還有XCMP訊息路由方式。
「總體而言,XCMP主要分為2部分:XCMP訊息的分發、XCMP訊息的存取」,本文主要就是介紹XCMP訊息的存取。
文末答題有驚喜:剩下3道題目準備好接招了嗎?
如果上篇文章的內容你大致都能看懂,恭喜你,已經很厲害了!!
但是,請做好心理準備,因為接下去的內容將更加複雜,我們將用盡可能清晰的方式來解釋。
XCMP訊息的存取
當接收連結收到訊息後,接下去要進行的流程是:處理訊息,然後將訊息及相關證明放進新塊構建好後交給驗證人,並最終確認出塊。這個流程是比較複雜的,涉及到的新資料結構也很多。
先來看看一些接下去要用到名詞的解釋:
Para X
表示平行鏈X或者平行執行緒X。
因為平行鏈要接入Polkadot需要購買一個專用插槽Slot,Slot數量少且貴,所以更精益的方式是使用平行執行緒。平行執行緒接入Polkadot不需要專門購買一個Slot,而是按塊付費。平行執行緒與平行鏈在開發上基本上一樣,而且也能使用Polkadot的各種功能。
平行鏈
當我們說平行鏈的時候,其實是指由平行鏈收集人節點構成的區塊鏈網路。如果提到了驗證人,一般會專門指出這是驗證人節點。
好,接下去我們藉助一張看起來有點複雜、但其實比較清晰的圖來講解XCMP訊息的存取。
上圖是我們自己總結的XCMP訊息存取的全景圖,基本上概括了XCMP訊息存取的全部內容。
圖中描述了Para B要給Para A傳送訊息 m2020。雖然Para還可能表示平行執行緒,但接下去以平行鏈為例(平行執行緒也是類似的)來解釋這張圖,闡述XCMP的訊息存取流程。
在平行鏈B要向平行鏈A傳送XCMP訊息前,首先要開一個鏈B通向鏈A的通道(Channel),然後接下去的流程是:
平行鏈B發出訊息 m2020(放至出口佇列),並在新出的塊 (假設為#3號塊) 中包含了此訊息。
平行鏈B作為傳送鏈,會維護對每個接收鏈的一條MQHC(Message Queue Hash Chain),可以看成是一種連結串列(如圖中,平行鏈B到A、C、D、E都建立了通道並行送過跨鏈訊息,所以平行鏈B有4條MQHC)。
MQHC鏈的每個元素對應於一條跨鏈訊息,元素的結構是一個三元組(parent_hash、message_hash、block_number)。parent_hash是該元素的父元素的雜湊,message_hash跨鏈訊息的雜湊,block_number是父元素訊息發出時的中繼鏈區塊號(可以當做全域性時間來用)。
平行鏈B的所有MQHC(這裡是4條)的連結串列頭元素會作為Merkle樹的葉節點從而構成一棵樹,樹根是Message Root,簡稱MR。MR具有非常牛逼的作用。如果你有了MR,再加上到某個MQHC元素的Merkle路徑,就能驗證MQHC上的元素,再加上傳送鏈的訊息佇列裡的訊息原文,你就能驗證所有已經傳送、但沒有被處理的所有訊息。
MR會儲存到平行鏈B的區塊頭裡(這裡剛好是#3號塊的區塊頭)。#3號塊的區塊頭除了有MR,還有bitfield、watermark這兩個也是和XCMP相關的資料。
bitfield是一個位域陣列,每一位的含義是傳送鏈對該位表示的接收鏈在本塊中是否傳送了跨鏈訊息。假設bitfield 的1-4位元表示鏈B對鏈A、C、D、E的跨鏈訊息傳送情況,則區塊頭裡的bitfield =1111說明這個區塊包含了對這四條鏈的跨鏈訊息。
watermark可以理解為跨鏈訊息的序號,不過這個序號不是一維的,而是二維的, watermark的結構為(block_number,para_id),block_number中繼鏈的區塊號,para_id是平行鏈的id。兩個watermark組成的區間就可以確定一個訊息集合,比如,若w1=(100,A),w2=(300,A),則區間[w1,w2) 表示在中繼鏈#100號區塊到#300號區塊這段時間裡,鏈B對para_id=A的這條鏈傳送的所有訊息。
當平行鏈B將訊息m2020包含進區塊並最終被驗證人驗證確認後,其區塊頭會儲存到中繼鏈的區塊中(如本例中B的#3號區塊頭)。這樣一來,中繼鏈就拿到了鏈B的#3號塊的MR,bitfield、watermark三個關鍵資料結構。根據這三個資料結構,中繼鏈就會做一些有趣的事情。
中繼鏈上有一個資料結構叫做CST(Channel State Table),中繼鏈於是會使用該新提交的平行鏈區塊頭裡的MR去更新自己的CST裡相應的一行。
CST的作用是跟蹤某條傳送鏈對某條接收鏈所傳送的訊息的狀態,從而提供某個通道的MQHC的連結串列頭的證明(proof)。
CST作為一張表,其行標是傳送鏈的ID,列標是接收鏈的ID,每個表項其實可以表示一個通道,表項的結構是一個二元組(sender_mr,block_number),易知通過表項的block_number +列對應的para_id可以得出watermark。sender_mr表示該通道的最新mr,block_number為該表項上次更新時的中繼鏈區塊號。
CST其實是按行來進行儲存的,一行中會有很多表項;因為是同一行所以這些表項的傳送鏈都相同;而因為傳送鏈都相同,同行的表項裡的最新MR也都相同。因此當新的平行鏈區塊頭到來時,CST是按行進行更新的。
CST其實除了一張表外,還有第二部分是對映(para_id => row_root)。row_root是CST的行裡的表項構成的Merkle樹的樹根。這樣,某一行只要有一個表項變化了,其row_root就會變化。
row_root會作為葉子繼續構出一棵Merkle樹,其根為XCMP_Root,其會關聯至中繼鏈的狀態根(State Root)。
鏈A需向中繼鏈獲取:以A為傳送鏈的MR的light client proof,以A為接收鏈的所有通道的watermark的light client proof,而且這些proof需要同時構建,這樣這些proof都能基於中繼鏈的同一個State Root;light client proof是通過Merkle proof轉化而成的。
因為中繼鏈狀態根(State Root)路徑到CST的表項其實要分2部分,先從State Root到row_root,再從row_root到表項。所以從State Root到表項裡的MR的整個Merkle proof被稱為「Nested Proof」。
某個通道的最新訊息根MR可以通過中繼鏈能夠獲取,MR的作用在前面提到過,MR 、MQHC連結串列頭的Merkle root、MQHC、對應訊息的原文這四樣東西能驗證這個通道目前未被處理的訊息。這樣,本次構建區塊就會把目前還沒有處理的訊息都處理下。
因此,鏈A還需要所有訊息的從MR到MQHC元素的merkle root,以及訊息的原文。
這樣,包含訊息雜湊的MQHC元素——訊息根MR ——MR構成的CST表項—— CST的row_root ——State Root,就能整合成一個總的proof,從中繼鏈狀態根State Root到跨鏈訊息的雜湊。
最後PoV區塊裡需要包含內容有:所有的訊息原文,及其對應的MQHC元素,及其整個的proof。
另外還有一些細節:
1)平行鏈的狀態裡不光要儲存MQHC的連結串列頭的Merkle proof,還要儲存MQHC所有元素的Merkle proof(它們也曾當過連結串列頭)。
2)如果確認訊息被執行,則MQHC裡的連結串列可以丟棄已經執行過的訊息的對應元素(但不一定要求立即丟棄,因為可以多存一會起到冗餘的作用)。
對上面第5點——平行鏈PoV區塊的構造,Polkadot官網上有一個簡例。
以上面這張圖中的鏈B為例。鏈B給鏈A傳送了跨鏈訊息,且通過Channel State Table可知,鏈A上一次處理鏈B發過來的訊息的時間是中繼鏈塊號位rc_1的時候,上一次的watermark1為(rc_1, A),而當前的watermark2為(rc_6, A)。
假設鏈A要出新塊,其要把[watermark1,watermark2) 之間的跨鏈訊息都進行處理。鏈A出塊需要的資料有(以下的1、2、3就是構成整個的proof):
中繼鏈State Root到CST row_root的Merkle proof(紅色部分);
CST row_root到 (MR,block_number) 構成的表項的Merkle proof(藍色部分);
MR到MQHC連結串列頭的Merkle proof(橙色部分);
所有訊息對應MQHC的元素(圖中左下方綠色矩形);
所有訊息的原文(圖中左下方綠色信封);
最後當平行鏈構造完PoV區塊後,驗證人會對其進行驗證,驗證通過則完成出塊,然後區塊頭會上到中繼鏈,中繼鏈更新狀態,而傳送鏈通過查詢中繼鏈也能知道自己發出的訊息最終被處理完成了。
總結
本次Polkadot XCMP的乾貨文章到此就結束了。這杯茶(X)溪(C)岸(M)啤(P)的味道如何?
XCMP的內容,尤其是本篇中講到的XCMP訊息存取部分,還是比較複雜的,用到了很多新創造的概念,比如訊息雜湊連結串列(MQHC),以及各種型別的Merkle樹(訊息樹、CST Item構成的樹、CST Row構成樹、狀態樹等等)。確實很難一次性看懂,建議大家多看幾遍(劃重點:可以仔細看看我們畫的那張XCMP訊息存取的圖)。
之所以設計得這麼複雜,是為了XCMP的設計目標:快速、有序、可驗證、無遺漏。
XCMP的設計主要分為XCMP訊息分發和XCMP訊息存取,現在我們回顧下,Polkadot是否實現了其設計目標:
訊息分發:通過平行鏈直接直接發訊息,而不中繼鏈達到了快速√。
訊息存取:通過watermark及其相關機制達到有序√、無遺漏√的目的;通過MQHC、各種Merkle樹及Proof做到了可驗證√。
綜上,Polkadota XCMP的設計大致是完成其目標的。但也因為XCMP設計比較複雜,目前還在實現中,未來可能會有一些變動,讓我們共同關注。
課後小習題
一共準備了5個小題目
(都是單選,包括了上篇文章的2個題目,把你的答案集齊5個給桔子,前五名全對的小夥伴有驚喜好禮哦😯)
【A】快速
【B】消除跨鏈訊息的「飢餓」現象
【C】高效
【D】可驗證
【W】傳送鏈將跨鏈訊息傳送給一個自己的全節點,該全節點轉發至接收鏈
【X】接收鏈的收集人去找傳送鏈的釣魚人拿跨鏈訊息,然後在本鏈的網路中gossip這個訊息
【Y】傳送鏈將跨鏈訊息傳送給一箇中繼鏈的全節點,該中繼鏈全節點轉發至接收鏈
【Z】接收鏈的驗證人主動去找傳送鏈的驗證人拿跨鏈訊息,然後在本鏈的網路中gossip這個訊息
【H】 MQHC會儲存在平行鏈的區塊頭裡,而且因為平行區塊頭上中繼鏈,所以中繼鏈也會儲存MQHC
【I】 MQHC是指Message Queue Hash Chain,MR是指Message Root
【J】MQHC的連結串列頭作為葉節點構成的樹的樹根為稱為訊息根Message Root
【K】 當擁有MR、MQHC連結串列頭的Merkle Proof、MQHC連結串列頭、連結串列頭包含的訊息原文時可以證明連結串列頭裡的訊息原文的存在性和正確性
【L】CST的全稱是Channel Send Table
【M】CST是按列儲存的,每一列的表項對應相同的接收鏈
【N】CST除了一張表外,還有第二部分對映(para_id => row_root),其中row_root會作為葉子繼續構出一棵Merkle樹,其根為XCMP_Root
【O】當新的平行鏈區塊頭到來時,中繼鏈會根據區塊頭裡的交易樹的樹根去更新CST
【A】中繼鏈狀態根(State Root)路徑到CST的表項分為2部分,先從State Root到row_root,再從row_root到表項,因此從State Root直接到表項裡的MR的整個merkle proof被稱為「nested proof」
【B】PoV區塊出塊是一步完成的,沒有比較明顯先構建、再驗證確認的分割階段
【C】基於State Root的那些proof需要在同一時刻構建,這樣這些proof才能基於中繼鏈的同一個State Root
【D】PoV區塊裡需要包含內容至少有:所有的訊息原文,及其對應的MQHC元素,及其整個的proof
5題全對的前五名小夥伴
會有好禮相送喲!
作者簡介
樓嵩
來自致力於「構建區塊鏈網際網路絡,打通價值孤島」的BitXHub團隊
研究方向:Web3+