這個系列主要結合neo的源碼,和你們分享一下區塊鏈的那些事.
這篇文章和你們家聊聊梅克爾樹.node
梅克爾樹是一種二叉樹,因爲它能快速檢查和概括大量數據,被用在區塊中記錄交易記錄的完整性.下面是neo中Block的屬性,是區塊的頭信息組成:程序員
public uint Version; //區塊版本
public UInt256 PrevHash; //前一個區塊的散列值
public UInt256 MerkleRoot;// 該區塊中全部交易的Merkle樹的根
public uint Timestamp; // 時間戳
public uint Index; // 區塊高度
public ulong ConsensusData; //一致性數據 不知道是什麼,這個字段之後再說
public UInt160 NextConsensus; // 下一個區塊的記帳合約的散列值
public Witness Script;// 用於驗證該區塊的腳本
複製代碼
可見區塊由區塊版本、前一個哈希值、梅克爾樹根節點、時間戳、區塊高度、下一個區塊的記帳合約散列、驗證腳本組成,今天主要聊一下Block中的Merkle Tree Root.
區塊頭瞭解了,那麼區塊的數據區包含什麼了,來了:public Transaction[] Transactions
,這就是neo中區塊的數據區,一個Transaction的數組.正如你們所知道的:區塊鏈中流通的信息就是交易數據了,在neo中就是Transaction.
將這個的緣由是想讓你們意識到:梅克爾樹在neo中存在於區塊頭部的根節點,數據區是交易的數組,並非交易數據的梅克爾樹.neo中梅克爾樹的價值經過根節點體現.數組
neo區塊中,全部的交易的哈希值構成梅克爾樹的葉子節點,葉子節點兩兩組合作雙SHA-256運算獲得父節點,以此類推等到最後的根節點,同時根節點被記入區塊頭部.bash
//雙SHA-256運算
public byte[] Hash256(byte[] message)
{
return message.Sha256().Sha256();
}
//左右孩子組合求父節點的哈希值
//concat:組合 a.concat(b) = ab
parents[i].Hash = new UInt256(Crypto.Default.Hash256(parents[i].LeftChild.Hash.ToArray()
.Concat(parents[i].RightChild.Hash.ToArray()).ToArray()));
複製代碼
代碼畢竟是一個程序員友好型的東西,那麼下面來一波圖解,相信聰明的你就get了.
區塊鏈
梅克爾樹
ui
葉子節點是一個交易的哈希值,父節點的哈希值是左右孩子的哈希值聯合進行雙哈希運算的來的.你們會看到最後面那個葉子節點和倒數第二個相同.這是由於當缺乏一個葉子節點時,梅克爾樹會將最後的那個節點複製,而後兩兩組成他們的父節點,造成了一個完整的樹形結構.spa
neo建樹的過程就是經過兩兩子節點計算父節點的過程.話很少說,源碼貼上先.net
//梅克爾樹節點
internal class MerkleTreeNode
{
public UInt256 Hash; //節點哈希值
public MerkleTreeNode Parent; //父節點
public MerkleTreeNode LeftChild; //左孩子節點
public MerkleTreeNode RightChild;//右孩子節點
public bool IsLeaf => LeftChild == null && RightChild == null;
public bool IsRoot => Parent == null;
}
//經過創建梅克爾樹獲取梅克爾樹根節點
private static MerkleTreeNode Build(MerkleTreeNode[] leaves)
{
//沒有數據,沒法建樹
if (leaves.Length == 0) throw new ArgumentException();
//只有一個數據,那麼根節點就是它了
if (leaves.Length == 1) return leaves[0];
//父節點數組,new出來的不會保存前面的值
MerkleTreeNode[] parents = new MerkleTreeNode[(leaves.Length + 1) / 2];
for (int i = 0; i < parents.Length; i++)
{
parents[i] = new MerkleTreeNode();
//父節點有左右孩子節點
parents[i].LeftChild = leaves[i * 2];
leaves[i * 2].Parent = parents[i];
if (i * 2 + 1 == leaves.Length)
{
//若是葉子節點是奇數,那麼最後的葉子節點作複製,兩兩組成父節點
parents[i].RightChild = parents[i].LeftChild;
}
else
{
parents[i].RightChild = leaves[i * 2 + 1];
leaves[i * 2 + 1].Parent = parents[i];
}
//父節點的哈希值爲孩子節點哈希值的聯合求雙SHA256.
parents[i].Hash = new UInt256(Crypto.Default.Hash256(parents[i].LeftChild.Hash.ToArray().Concat(parents[i].RightChild.Hash.ToArray()).ToArray()));
}
//遞歸計算
return Build(parents); //TailCall
}
複製代碼
上面的遞歸能夠經過下面的圖示來加深理解
code
Neo Markle Tree Build
相鄰兩個子節點組合,生成父節點.缺失右孩子節點的,用左孩子節點補齊.不存儲中間變量,只保存最後計算出來的根節點.
經過梅克爾樹,大量交易記錄就縮小在256bits的數據指紋裏面啦.
若是你是一個節點,那麼檢查數據是否被修改就只須要計算一下交易記錄的梅克爾樹的根節點,而後和區塊頭的梅克爾跟比較就能夠得出結果了。cdn
這裏屬於拓展篇了,小編想在這裏和你們聊聊梅克爾路徑認證.歸爲拓展的緣由是neo中沒有應用spv.
spv是比特幣的輕量級客戶端,它的中文爲簡單支付驗證,主要解決的痛點是比特幣共識鏈太大,使非礦工節點須要拉取過多數據。
spv只會下載共識鏈的去塊頭的鏈條,能節省大約1000倍的存儲空間。因爲沒有獲取完整的數據,因此spv客戶端不能做爲礦工節點。
spv沒有交易數據,因此若是須要進行交易驗證的時候,spv就會發送一條廣播,讓其餘非spv節點給它發送驗證數據,bitcoin中,這類數據類型爲MerkleBlock。
class CMerkleBlock
{
public:
/** Public only for unit testing */
CBlockHeader header;
CPartialMerkleTree txn;
.......
}
複製代碼
這是比特幣區塊的一段源碼,咱們暫且稱呼它爲梅克爾塊.
梅克爾塊中包含兩個重要的數據,一個是區塊頭,另一個是梅克爾樹。這裏的區塊頭就是比特幣區塊的區塊頭,梅克爾樹能夠根據它的命名看出是一串交易,它其實是一條梅克爾認證路徑。
class CPartialMerkleTree
{
protected:
/** the total number of transactions in the block */
unsigned int nTransactions;
/** node-is-parent-of-matched-txid bits */
std::vector<bool> vBits;
/** txids and internal hashes */
std::vector<uint256> vHash;
/** flag set when encountering invalid data */
bool fBad;
......
複製代碼
收到spv節點請求的完整節點會把目標區塊的區塊頭和一條驗證路徑的交易串(交易編號和哈希)發送過來。這樣節點就能夠經過下圖所示的方法進行驗證了。(黑色區塊就是發送過來的交易)
梅克爾路徑證實
spv驗證交易e時,只須要經過和交易f、H(gg)、H(H(ab)+H(cd))的計算來計算ROOT的值,而後和區塊頭中的梅克爾根進行比對就能夠進行簡單的驗證了。這裏的整個過程就是梅克爾交易驗證,黑色路徑爲梅克爾認證路徑。
spv能夠有效減小非礦工節點對共有鏈的數據存儲,目前neo中並不支持。同時比特幣區塊數據區存儲的是交易的梅克爾樹,而neo中則是存放的交易數組。
梅克爾樹就講到這裏來,後面還會有不少關於區塊鏈的分享,期待與您共享雙贏~
csdn地址:blog.csdn.net/little_prog…
做者:謝益文_2f26
連接:www.jianshu.com/p/2b082157b…