代碼格式可能有錯誤,可以點擊閱讀原文查看代碼
讓我們來看看用 Java 代碼實現(xiàn)區(qū)塊鏈的可能性。我們從基本原理出發(fā),開發(fā)一些代碼來演示它們是如何融合在一起的。
Bitcoin炙手可熱 —— 多么的輕描淡寫。雖然數(shù)字加密貨幣的前景尚不明確,但區(qū)塊鏈 —— 用于驅(qū)動比特幣的技術 —— 卻非常流行。
區(qū)塊鏈的應用領域尚未探索完畢。它也有可能會破壞企業(yè)自動化。關于區(qū)塊鏈的工作原理,有很多可用的信息。我們有一個深度區(qū)塊鏈的免費白皮書(無需注冊)。
本文將重點介紹區(qū)塊鏈體系結(jié)構,特別是通過簡單的代碼示例演示“不可變,僅附加”的分布式賬本是如何工作的。
作為開發(fā)者,閱讀代碼會比閱讀技術文章更容易理解。至少對我來說是這樣。那么我們開始吧!
簡述區(qū)塊鏈
首先我們簡要總結(jié)下區(qū)塊鏈。區(qū)塊包含一些頭信息和任意一組數(shù)據(jù)類型或一組交易。該鏈從第一個(初始)區(qū)塊開始。隨著交易被添加/擴展,將基于區(qū)塊中可以存儲多少交易來創(chuàng)建新區(qū)塊。
當超過區(qū)塊閥值大小時,將創(chuàng)建一個新的交易區(qū)塊。新區(qū)塊與前一個區(qū)塊連接,因此稱為區(qū)塊鏈。
不可變性
因為交易時會計算 SHA-256 哈希值,所以區(qū)塊鏈是不可變的。區(qū)塊鏈的內(nèi)容也被哈希則提供了唯一的標識符。此外,相連的前一個區(qū)塊的哈希也會被在區(qū)塊的頭信息中散列并儲存。
這就是為什么試圖篡改區(qū)塊基本上是不可能的,至少以目前的計算能力是這樣的。下面是一個展示區(qū)塊屬性的 Java 類的部分定義。
。.. public class Block
注意,注入的泛型類型為 Tx 類型。這允許交易數(shù)據(jù)發(fā)生變化。此外,previousHash 屬性將引用前一個區(qū)塊的哈希值。稍后將描述 merkleRoot 和 nonce 屬性。
區(qū)塊哈希值
每個區(qū)塊可以計算一個哈希。這實際上是鏈接在一起的所有區(qū)塊屬性的哈希,包括前一個區(qū)塊的哈希和由此計算而得的 SHA-256 哈希。
下面是在 Block.java 類中定義的計算哈希值的方法。
。.. public void computeHash() { Gson parser = new Gson(); // 可能應該緩存這個實例 String serializedData = parser.toJson(transactions); setHash(SHA256.generateHash(timeStamp + index + merkleRoot + serializedData + nonce + previousHash)); } 。..
交易被序列化為 JSON 字符串,因此可以在哈希之前將其追加到塊屬性中。
鏈
區(qū)塊鏈通過接受交易來管理區(qū)塊。當?shù)竭_預定閥值時,就創(chuàng)建一個區(qū)塊。下面是 SimpleBlockChain.java 的部分實現(xiàn):
。.. public class SimpleBlockchain《T extends Tx》 { public static final int BLOCK_SIZE = 10; public List《Block《T》》 chain = new ArrayList《Block《T》》(); public SimpleBlockchain() { // 創(chuàng)建初始區(qū)塊 chain.add(newBlock()); } 。..
注意,chain 屬性維護了一個類型為 Tx 的區(qū)塊列表。此外,無參構造器 會在創(chuàng)建初始鏈表時初始化“初始”區(qū)塊。下面是 newBlock() 方法源碼。
。.. public Block《T》 newBlock() { int count = chain.size(); String previousHash = “root”; if (count 》 0) previousHash = blockChainHash(); Block《T》 block = new Block《T》(); block.setTimeStamp(System.currentTimeMillis()); block.setIndex(count); block.setPreviousHash(previousHash); return block; } 。..
這個方法將會創(chuàng)建一個新的區(qū)塊實例,產(chǎn)生合適的值,并分配前一個塊的哈希(這將是鏈頭的哈希),然后返回這個實例。
在將區(qū)塊添加到鏈中之前,可以通過將新區(qū)塊的上一個哈希與鏈的最后一個區(qū)塊(頭)進行比較來驗證區(qū)塊,以確保它們匹配。SimpleBlockchain.java 描述了這一過程。
。..。 public void addAndValidateBlock(Block《T》 block) { // 比較之前的區(qū)塊哈希,如果有效則添加 Block《T》 current = block; for (int i = chain.size() - 1; i 》= 0; i--) { Block《T》 b = chain.get(i); if (b.getHash().equals(current.getPreviousHash())) { current = b; } else { throw new RuntimeException(“Block Invalid”); } } this.chain.add(block); } 。..
整個區(qū)塊鏈通過循環(huán)整個鏈來驗證,確保區(qū)塊的哈希仍然與前一個區(qū)塊的哈希匹配。
以下是 SimpleBlockChain.java validate() 方法的實現(xiàn)。
。.. public boolean validate() { String previousHash = null; for (Block《T》 block : chain) { String currentHash = block.getHash(); if (!currentHash.equals(previousHash)) { return false; } previousHash = currentHash; } return true; } 。..
你可以看到,試圖以任何方式偽造交易數(shù)據(jù)或任何其他屬性都是非常困難的。而且,隨著鏈的增長,它會繼續(xù)變得非常、非常、非常困難,基本上是不可能的 —— 除非量子計算機可用!
添加交易
區(qū)塊鏈技術的另一個重要技術點是它是分布式的。區(qū)塊鏈只增的特性很好地幫助了它在區(qū)塊鏈網(wǎng)絡的節(jié)點之間的復制。節(jié)點通常以點對點的方式進行通信,就像比特幣那樣,但不一定非得是這種方式。其他區(qū)塊鏈實現(xiàn)使用分散的方法,比如使用基于 HTTP 協(xié)議的 API。這都是題外話了。
交易可以代表任何東西。交易可以包含要執(zhí)行的代碼(例如,智能合約)或存儲和追加有關某種業(yè)務交易的信息。
智能合約:旨在以數(shù)字形式來促進、驗證或強制執(zhí)行合約談判及履行的計算機協(xié)議。
就比特幣而言,交易包含所有者賬戶中的金額和其他賬戶的金額(例如,在賬戶之間轉(zhuǎn)移比特幣金額)。交易中還包括公鑰和賬戶 ID,因此傳輸需要保證安全。但這是比特幣特有的。
交易被添加到網(wǎng)絡中并被池化;它們不在區(qū)塊中或鏈本身中。
這是區(qū)塊鏈共識機制發(fā)揮作用的地方?,F(xiàn)在有許多經(jīng)過驗證的共識算法和模式,不過那已經(jīng)超出了本文的范圍。
挖礦是比特幣區(qū)塊鏈使用的共識機制。這就是下文討論的共識類型。共識機制收集交易,用它們構建一個區(qū)塊,然后將該區(qū)塊添加到鏈中。區(qū)塊鏈會在新的交易區(qū)塊被添加之前驗證它。
默克爾樹
交易被哈希并添加到區(qū)塊中。默克爾樹被用來計算默克爾根哈希。默克爾樹是一種內(nèi)部節(jié)點的值是兩個子節(jié)點值的哈希值的平衡二叉樹。而默克爾根,就是默克爾樹的根節(jié)點。
該樹用于區(qū)塊交易的驗證。如果在交易中更改了一些信息,默克爾根將失效。此外,在分布式中,它們還可以加速傳輸區(qū)塊,因為該結(jié)構只允許添加和驗證整個交易區(qū)塊所需的單個交易哈希分支。
以下是 Block.java 類中的方法,它從交易列表中創(chuàng)建了一個默克爾樹。
。.. public List《String》 merkleTree() { ArrayList《String》 tree = new ArrayList《》(); // 首先, // 將所有交易的哈希作為葉子節(jié)點添加到樹中。 for (T t : transactions) { tree.add(t.hash()); } int levelOffset = 0; // 當前處理的列表中的偏移量。 // 當前層級的第一個節(jié)點在整個列表中的偏移量。 // 每處理完一層遞增, // 當我們到達根節(jié)點時(levelSize == 1)停止。 for (int levelSize = transactions.size(); levelSize 》 1; levelSize = (levelSize + 1) / 2) { // 對于該層上的每一對節(jié)點: for (int left = 0; left 《 levelSize; left += 2) { // 在我們沒有足夠交易的情況下, // 右節(jié)點和左節(jié)點 // 可以一樣。 int right = Math.min(left + 1, levelSize - 1); String tleft = tree.get(levelOffset + left); String tright = tree.get(levelOffset + right); tree.add(SHA256.generateHash(tleft + tright)); } // 移動至下一層 levelOffset += levelSize; } return tree; } 。..
此方法用于計算區(qū)塊的默克爾樹根。伴隨項目有一個默克爾樹單元測試,它試圖將交易添加到一個區(qū)塊中,并驗證默克爾根是否已經(jīng)更改。下面是單元測試的源碼。
。.. @Test public void merkleTreeTest() { // 創(chuàng)建鏈,添加交易 SimpleBlockchain《Transaction》 chain1 = new SimpleBlockchain《Transaction》(); chain1.add(new Transaction(“A”)).add(new Transaction(“B”)).add(new Transaction(“C”)).add(new Transaction(“D”)); // 獲取鏈中的區(qū)塊 Block《Transaction》 block = chain1.getHead(); System.out.println(“Merkle Hash tree :” + block.merkleTree()); //從區(qū)塊中獲取交易 Transaction tx = block.getTransactions().get(0); // 查看區(qū)塊交易是否有效,它們應該是有效的 block.transasctionsValid(); assertTrue(block.transasctionsValid()); // 更改交易數(shù)據(jù) tx.setValue(“Z”); //當區(qū)塊的默克爾根與計算出來的默克爾樹不匹配時,區(qū)塊不應該是有效。 assertFalse(block.transasctionsValid()); } 。..
此單元測試模擬驗證交易,然后通過共識機制之外的方法改變區(qū)塊中的交易,例如,如果有人試圖更改交易數(shù)據(jù)。
記住,區(qū)塊鏈是只增的,當塊區(qū)鏈數(shù)據(jù)結(jié)構在節(jié)點之間共享時,區(qū)塊數(shù)據(jù)結(jié)構(包括默克爾根)被哈希并連接到其他區(qū)塊。所有節(jié)點都可以驗證新的區(qū)塊,并且現(xiàn)有的區(qū)塊可以很容易地被證明是有效的。因此,如果一個挖礦者想要添加一個偽造的區(qū)塊或者節(jié)點來調(diào)整原有的交易是不可能的。
挖礦和工作量證明
在比特幣世界中,將交易組合成區(qū)塊,然后提交給鏈中的成員進行驗證的過程叫做“挖礦”。
更寬泛地說,在區(qū)塊鏈中,這被稱為共識?,F(xiàn)在有好幾種經(jīng)過驗證的分布式共識算法,使用哪種機制取決于你有一個公共的還是私有的區(qū)塊鏈。我們的白皮書對此進行了更為深入的描述,但本文的重點是區(qū)塊鏈的原理,因此這個例子中我們將使用一個工作量證明(POW)的共識機制。
因此,挖掘節(jié)點將偵聽由區(qū)塊鏈執(zhí)行的交易,并執(zhí)行一個簡單的數(shù)學任務。這個任務是用一個不斷改變的一次性隨機數(shù)(nonce)來生成帶有一連串以 0 開頭的區(qū)塊哈希值,直到一個預設的哈希值被找到。
Java 示例項目有一個 Miner.java 類,其中的 proofOfWork(Block block) 方法實現(xiàn)如下所示。
private String proofOfWork(Block block) { String nonceKey = block.getNonce(); long nonce = 0; boolean nonceFound = false; String nonceHash = “”; Gson parser = new Gson(); String serializedData = parser.toJson(transactionPool); String message = block.getTimeStamp() + block.getIndex() + block.getMerkleRoot() + serializedData + block.getPreviousHash(); while (!nonceFound) { nonceHash = SHA256.generateHash(message + nonce); nonceFound = nonceHash.substring(0, nonceKey.length()).equals(nonceKey); nonce++; } return nonceHash; }
同樣,這是簡化的,但是一旦收到一定量的交易,這個挖礦算法會為區(qū)塊計算一個工作量證明的哈希。該算法簡單地循環(huán)并創(chuàng)建塊的SHA-256散列,直到產(chǎn)生前導數(shù)字哈希。
這可能需要很多時間,這就是為什么特定的GPU微處理器已經(jīng)被實現(xiàn)來盡可能快地執(zhí)行和解決這個問題的原因。
單元測試
你可以在 GitHub上看到結(jié)合了這些概念的 Java 示例的 JUnit 測試。
運行一下,看看這個簡單的區(qū)塊鏈是如何工作的。
另外,如果你是 C# 程序員的話,其實(我不會告訴任何人),我們也有用 C# 寫的示例。下面是 C# 區(qū)塊鏈實現(xiàn)的示例。
最后的思考
希望這篇文章能讓你對區(qū)塊鏈技術有一定的了解,并有充足的興趣繼續(xù)研究下去。
本文介紹的所有示例都用于我們的深度區(qū)塊鏈白皮書 (無需注冊即可閱讀)。 這些例子在白皮書中有更詳細的說明。
另外,如果你想在 Java 中看到完整的區(qū)塊鏈實現(xiàn),這里有一個開源項目 BitcoinJ 的鏈接。你可以看到上文的概念在實際生產(chǎn)中一一實現(xiàn)。
來源:區(qū)塊網(wǎng)
評論