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