[譯] 用 Java 程式碼實現區塊鏈

Starrier發表於2018-04-29

用 Java 程式碼實現區塊鏈

讓我們來看看用 Java 程式碼實現區塊鏈的可能性。我們從基本原理出發,開發一些程式碼來演示它們是如何融合在一起的。

比特幣(Bitcoin)炙手可熱 —— 多麼的輕描淡寫。雖然數字加密貨幣的前景尚不明確,但區塊鏈 —— 用於驅動比特幣的技術 —— 卻非常流行。

區塊鏈的應用領域尚未探索完畢。它也有可能會破壞企業自動化。關於區塊鏈的工作原理,有很多可用的資訊。我們有一個深度區塊鏈的免費白皮書(無需註冊)。

本文將重點介紹區塊鏈體系結構,特別是通過簡單的程式碼示例演示“不可變,僅附加”的分散式賬本是如何工作的。

作為開發者,閱讀程式碼會比閱讀技術文章更容易理解。至少對我來說是這樣。那麼我們開始吧!

簡述區塊鏈

首先我們簡要總結下區塊鏈。區塊包含一些頭資訊和任意一組資料型別或一組交易。該鏈從第一個(初始)區塊開始。隨著交易被新增/擴充套件,將基於區塊中可以儲存多少交易來建立新區塊。

當超過區塊閥值大小時,將建立一個新的交易區塊。新區塊與前一個區塊連線,因此稱為區塊鏈。

不可變性

因為交易時會計算 SHA-256 雜湊值,所以區塊鏈是不可變的。區塊鏈的內容也被雜湊則提供了唯一的識別符號。此外,相連的前一個區塊的雜湊也會被在區塊的頭資訊中雜湊並儲存。

這就是為什麼試圖篡改區塊基本上是不可能的,至少以目前的計算能力是這樣的。下面是一個展示區塊屬性的 Java 類的部分定義。

...
public class Block<T extends Tx>; {
public long timeStamp;
private int index;
private List<T> transactions = new ArrayList<T>();
private String hash;
private String previousHash;
private String merkleRoot;
private String nonce = "0000";

// 快取事務用 SHA256 雜湊
    public Map<String,T> map = new HashMap<String,T>();
...
複製程式碼

注意,注入的泛型型別為 Tx 型別。這允許交易資料發生變化。此外,previousHash 屬性將引用前一個區塊的雜湊值。稍後將描述 merkleRootnonce 屬性。

區塊雜湊值

每個區塊可以計算一個雜湊。這實際上是連結在一起的所有區塊屬性的雜湊,包括前一個區塊的雜湊和由此計算而得的 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 字串,因此可以在雜湊之前將其追加到塊屬性中。

區塊鏈通過接受交易來管理區塊。當到達預定閥值時,就建立一個區塊。下面是 SimpleBlockChain.java 的部分實現:

...
...
public class SimpleBlockchain<T extends Tx> {
public static final int BLOCK_SIZE = 10;
public List<Block<T>> chain = new ArrayList<Block<T>>();

public SimpleBlockchain() {
// 建立初始區塊
chain.add(newBlock());
}

...
複製程式碼

注意,chain 屬性維護了一個型別為 Tx 的區塊列表。此外,無參構造器 會在建立初始連結串列時初始化“初始”區塊。下面是 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;
}
...
複製程式碼

這個方法將會建立一個新的區塊例項,產生合適的值,並分配前一個塊的雜湊(這將是鏈頭的雜湊),然後返回這個例項。

在將區塊新增到鏈中之前,可以通過將新區塊的上一個雜湊與鏈的最後一個區塊(頭)進行比較來驗證區塊,以確保它們匹配。SimpleBlockchain.java 描述了這一過程。

....
public void addAndValidateBlock(Block<T> block) {

// 比較之前的區塊雜湊,如果有效則新增
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);
}
...
複製程式碼

整個區塊鏈通過迴圈整個鏈來驗證,確保區塊的雜湊仍然與前一個區塊的雜湊匹配。

以下是 SimpleBlockChain.java validate() 方法的實現。

...
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;

}
...
複製程式碼

你可以看到,試圖以任何方式偽造交易資料或任何其他屬性都是非常困難的。而且,隨著鏈的增長,它會繼續變得非常、非常、非常困難,基本上是不可能的 —— 除非量子計算機可用!

新增交易

區塊鏈技術的另一個重要技術點是它是分散式的。區塊鏈只增的特性很好地幫助了它在區塊鏈網路的節點之間的複製。節點通常以點對點的方式進行通訊,就像比特幣那樣,但不一定非得是這種方式。其他區塊鏈實現使用分散的方法,比如使用基於 HTTP 協議的 API。這都是題外話了。

交易可以代表任何東西。交易可以包含要執行的程式碼(例如,智慧合約)或儲存和追加有關某種業務交易的資訊。

智慧合約:旨在以數字形式來促進、驗證或強制執行合約談判及履行的計算機協議。

就比特幣而言,交易包含所有者賬戶中的金額和其他賬戶的金額(例如,在賬戶之間轉移比特幣金額)。交易中還包括公鑰和賬戶 ID,因此傳輸需要保證安全。但這是比特幣特有的。

交易被新增到網路中並被池化;它們不在區塊中或鏈本身中。

這是區塊鏈共識機制發揮作用的地方。現在有許多經過驗證的共識演算法和模式,不過那已經超出了本文的範圍。

挖礦是比特幣區塊鏈使用的共識機制。這就是下文討論的共識型別。共識機制收集交易,用它們構建一個區塊,然後將該區塊新增到鏈中。區塊鏈會在新的交易區塊被新增之前驗證它。

默克爾樹

交易被雜湊並新增到區塊中。默克爾樹被用來計算默克爾根雜湊。默克爾樹是一種內部節點的值是兩個子節點值的雜湊值的平衡二叉樹。而默克爾根,就是默克爾樹的根節點。

[譯] 用 Java 程式碼實現區塊鏈

該樹用於區塊交易的驗證。如果在交易中更改了一些資訊,默克爾根將失效。此外,在分散式中,它們還可以加速傳輸區塊,因為該結構只允許新增和驗證整個交易區塊所需的單個交易雜湊分支。

以下是 Block.java 類中的方法,它從交易列表中建立了一個默克爾樹。

...
public List<String> merkleTree() {
ArrayList<String> tree = new ArrayList<>();
// 首先,
// 將所有交易的雜湊作為葉子節點新增到樹中。
for (T t : transactions) {
tree.add(t.hash());
}
int levelOffset = 0; // 當前處理的列表中的偏移量。
//  當前層級的第一個節點在整個列表中的偏移量。
// 每處理完一層遞增,
// 當我們到達根節點時(levelSize == 1)停止。
for (int levelSize = transactions.size(); levelSize > 1; levelSize = (levelSize + 1) / 2) {
// 對於該層上的每一對節點:
for (int left = 0; left < levelSize; left += 2) {
// 在我們沒有足夠交易的情況下,
// 右節點和左節點
// 可以一樣。
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;
}

...
複製程式碼

此方法用於計算區塊的默克爾樹根。伴隨專案有一個默克爾樹單元測試,它試圖將交易新增到一個區塊中,並驗證默克爾根是否已經更改。下面是單元測試的原始碼。

...
@Test
public void merkleTreeTest() {

// 建立鏈,新增交易

SimpleBlockchain<Transaction> chain1 = new SimpleBlockchain<Transaction>();

chain1.add(new Transaction("A")).add(new Transaction("B")).add(new Transaction("C")).add(new Transaction("D"));

// 獲取鏈中的區塊
Block<Transaction> block = chain1.getHead();

System.out.println("Merkle Hash tree :" + block.merkleTree());

//從區塊中獲取交易
Transaction tx = block.getTransactions().get(0);

// 檢視區塊交易是否有效,它們應該是有效的
block.transasctionsValid();
assertTrue(block.transasctionsValid());

// 更改交易資料
tx.setValue("Z");

//當區塊的默克爾根與計算出來的默克爾樹不匹配時,區塊不應該是有效。
assertFalse(block.transasctionsValid());

}

...
複製程式碼

此單元測試模擬驗證交易,然後通過共識機制之外的方法改變區塊中的交易,例如,如果有人試圖更改交易資料。

記住,區塊鏈是隻增的,當塊區鏈資料結構在節點之間共享時,區塊資料結構(包括默克爾根)被雜湊並連線到其他區塊。所有節點都可以驗證新的區塊,並且現有的區塊可以很容易地被證明是有效的。因此,如果一個挖礦者想要新增一個偽造的區塊或者節點來調整原有的交易是不可能的。

挖礦和工作量證明

在比特幣世界中,將交易組合成區塊,然後提交給鏈中的成員進行驗證的過程叫做“挖礦”。

更寬泛地說,在區塊鏈中,這被稱為共識。現在有好幾種經過驗證的分散式共識演算法,使用哪種機制取決於你有一個公共的還是私有的區塊鏈。我們的白皮書對此進行了更為深入的描述,但本文的重點是區塊鏈的原理,因此這個例子中我們將使用一個工作量證明(POW)的共識機制。

因此,挖掘節點將偵聽由區塊鏈執行的交易,並執行一個簡單的數學任務。這個任務是用一個不斷改變的一次性隨機數(nonce)來生成帶有一連串以 0 開頭的區塊雜湊值,直到一個預設的雜湊值被找到。

Java 示例專案有一個 Miner.java 類,其中的 proofOfWork(Block block) 方法實現如下所示。

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;

}
複製程式碼

同樣,這是簡化的,但是一旦收到一定量的交易,這個挖礦演算法會為區塊計算一個工作量證明的雜湊。該演算法簡單地迴圈並建立塊的SHA-256雜湊,直到產生前導數字雜湊。

這可能需要很多時間,這就是為什麼特定的GPU微處理器已經被實現來儘可能快地執行和解決這個問題的原因。

單元測試

你可以在 GitHub上看到結合了這些概念的 Java 示例的 JUnit 測試。

[譯] 用 Java 程式碼實現區塊鏈

執行一下,看看這個簡單的區塊鏈是如何工作的。

另外,如果你是 C# 程式設計師的話,其實(我不會告訴任何人),我們也有用 C# 寫的示例。下面是 C# 區塊鏈實現的示例

最後的思考

希望這篇文章能讓你對區塊鏈技術有一定的瞭解,並有充足的興趣繼續研究下去。

本文介紹的所有示例都用於我們的深度區塊鏈白皮書 (無需註冊即可閱讀). 這些例子在白皮書中有更詳細的說明。

另外,如果你想在 Java 中看到完整的區塊鏈實現,這裡有一個開源專案 BitcoinJ 的連結。你可以看到上文的概念在實際生產中一一實現。

如果是這樣的話,推薦你看看更貼近生產的開源區塊鏈框架。一個很好的示例是 HyperLedger Fabric,這將是我下一篇文章的主題 —— 請持續關注!


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章