以太坊賬戶狀態的4個組成部分:Nonce、Balance、StorageRoot、CodeHash。

其中,對於外部賬戶真正有效的只有Nonce和Balance兩個欄位,這兩個欄位也好理解。而對於合約賬戶,4個欄位都有效,且後面兩個欄位,StorageRoot和CodeHash就比較令人費解。StorageRoot跟合約擁有的資料有關,CodeHash跟合約擁有的程式碼有關。

我們先來看看StorageRoot。

這其實是一棵梅克爾樹(Merkle Tree)的樹根,但這裡的梅克爾樹和比特幣裡的梅克爾樹不一樣,它是一棵經過改進的梅克爾樹,稱之為MPT(Merkle Patricia Tree)。

在比特幣裡,只有一處用到梅克爾樹,就是在區塊裡,把該區塊的所有交易用一棵梅克爾樹組織起來,然後將樹根儲存到區塊頭裡。

而在以太坊,我們會發現,將會有很多地方用到梅克爾樹或MTP。而StorageRoot就儲存了一棵MPT樹的樹根,它是這麼來的:

1、將該合約賬戶包含的所有資料,用一定方式劃分成一個個的32位元組長的資料塊,做為“value”,並且將這些資料塊按順序從0開始編號,編號也是32位元組長,做為“key”,這樣,key和value一一對應;

2、但這時的key和value還是原始key和原始value,需將原始key雜湊一下,得到最終的key,將原始value用RLP編碼處理一下,得到最終的value;

3、最後,把最終的[key, value]對用一棵MPT樹儲存起來。其樹根就是StorageRoot。

雜湊我們學過了,不再講。MTP樹和RLP編碼是以太坊獨創的,且在多處用到。MTP樹我們明天再講。所以,今天主要就是弄清楚RLP編碼。

RLP(Recursive Length Prefix),全名叫遞迴長度字首編碼,很拗口,不管它,我們叫它RLP就行。RLP編碼其實就是一些變換規則。

它變換的物件有兩種,一種就是字串,比如“abcde”,另一種是由多個字串組成的列表,比如[“abc”,”bde”,”a23f”]。

RLP編碼基於標準的ASCII編碼,標準的ASCII編碼是常用的計算機編碼方式,但它只編碼了字母、數字、標點等128個常用字元,一個字元佔用一個位元組(所以,想要在以太坊上儲存些奇怪字元是不可能的?如果理解錯了,請大神指出,拜謝!)。

接下來,我們來看看它的具體轉換規則:

規則一: 對於值在[0, 127]之間(因為基於ASCII標準表,所以超過128就處理不了啦)的單個字元,其編碼是其ASCII編碼。 比如,單個字元‘0’在ASCII標準表裡是48(十六進位制是0x30),所以在RLP這裡也是48。

規則二: 如果字串長度是0-55個位元組,那麼在前面加上(128+字串長度)作為字首。 比如,空字串編碼就是128,即128 = 128 + 0; “0”這個字串只有1個字元,編碼就是12948; “0123”在ASCII標準表是48495051,長度是4個位元組,字首為132,那麼RLP 轉換後就是13248495051。

規則三: 如果字串長度大於55, 那麼在前面加上(183+字串長度的二進位制編碼的長度)和(字串長度)做字首。(為什麼是183?因為在規則二里,最大就是183(128+55)。)

比如,一個字串長度為1024個位元組,1024換成二進位制是00000100 00000000,佔2個位元組,所以,字首為18540,其中185得自183+2,4得自長度的第一個位元組00000100轉換成十進位制,0得自第二個位元組00000000轉換成十進位制。 接下來還有2條規則,是針對字串列表的。

規則四: 如果列表中所有字串的總長度按規則1到3編碼後,小於等於55,那麼在前面加上(192+該長度)做字首。列表裡的字串編碼參照規則1到3(183和192之間差了9個位元組,9個位元組用來表示長度,那是個非常非常大的數字了,所以,RLP幾乎可以編碼非常非常長的字串)。

比如,一個列表有3個字串,這3個字串分別按規則1到3編碼後,總長度是9個位元組,那麼字首是192+9=201;

規則五: 如果列表中所有字串的總長度按規則1到3編碼後,超過55,那麼在前面加上(247+該長度的二進位制編碼的長度)和(該長度)做字首。列表裡的字串編碼參照規則1到3(為什麼是247?因為192+55=247)。

比如,一個列表有10個字串,這10個字串分別按規則1到3編碼後,總長度是100個位元組,因為100進行二進位制編碼,只佔用1個位元組,所以字首是248(247+1)和100。

其實,像RLP這種序列化的編碼已有很多,但冗餘太大,佔用空間太大,而區塊鏈上的儲存和計算資源都很金貴,所以太坊重新發明了一個更緊湊的。