使用Java语言从零开始创建区块链
使用Java語言從零開始創(chuàng)建區(qū)塊鏈
2018年04月01日 17:08:12?大俠區(qū)塊鏈?閱讀數(shù):1312?標(biāo)簽:?java區(qū)塊鏈java區(qū)塊鏈?更多
個(gè)人分類:?區(qū)塊鏈
?
Java區(qū)塊鏈開發(fā)與交流群:?613121183
有興趣的也可以加下哈,提供了不少區(qū)塊鏈資料,以后有資料可以相會(huì)共享
?
目前網(wǎng)絡(luò)上關(guān)于區(qū)塊鏈入門、科普的文章不少,本文就不再贅述區(qū)塊鏈的基本概念了,如果對(duì)區(qū)塊鏈不是很了解的話,可以看一下我之前收集的一些入門學(xué)習(xí)資源:
http://blog.51cto.com/zero01/2066321
對(duì)區(qū)塊鏈技術(shù)感到新奇的我們,都想知道區(qū)塊鏈在代碼上是怎么實(shí)現(xiàn)的,所以本文是實(shí)戰(zhàn)向的,畢竟理論我們都看了不少,但是對(duì)于區(qū)塊鏈具體的實(shí)現(xiàn)還不是很清楚,本文就使用Java語言來實(shí)現(xiàn)一個(gè)簡單的區(qū)塊鏈。
但是要完全搞懂區(qū)塊鏈并非易事,對(duì)于一門較為陌生的技術(shù),我們需要在理論+實(shí)踐中學(xué)習(xí),通過寫代碼來學(xué)習(xí)技術(shù)會(huì)掌握得更牢固,構(gòu)建一個(gè)區(qū)塊鏈可以加深對(duì)區(qū)塊鏈的理解。
準(zhǔn)備工作
掌握基本的JavaSE以及JavaWeb開發(fā),能夠使用Java開發(fā)簡單的項(xiàng)目,并且需要了解HTTP協(xié)議。
我們知道區(qū)塊鏈?zhǔn)怯蓞^(qū)塊的記錄構(gòu)成的不可變、有序的鏈結(jié)構(gòu),記錄可以是交易、文件或任何你想要的數(shù)據(jù),重要的是它們是通過哈希值(hashes)鏈接起來的。
如果你還不是很了解哈希是什么,可以查看這篇文章
環(huán)境描述
- JDK1.8
- Tomcat 9.0
- Maven 3.5
- JSON 20160810
- javaee-api 7.0
pom.xml文件配置內(nèi)容:
<dependencies><dependency><groupId>javax</groupId><artifactId>javaee-api</artifactId><version>7.0</version><scope>provided</scope></dependency><dependency><groupId>org.json</groupId><artifactId>json</artifactId><version>20160810</version></dependency></dependencies>然后還需要一個(gè)HTTP客戶端,比如Postman,Linux命令行下的curl或其它客戶端,我這里使用的是Postman。
Blockchain類
首先創(chuàng)建一個(gè)Blockchain類,在構(gòu)造器中創(chuàng)建了兩個(gè)主要的集合,一個(gè)用于儲(chǔ)存區(qū)塊鏈,一個(gè)用于儲(chǔ)存交易列表,本文中所有核心的主要代碼都寫在這個(gè)類里,方便隨時(shí)查看,在實(shí)際開發(fā)則不宜這么做,應(yīng)該把代碼拆分仔細(xì)降低耦合度。
以下是Blockchain類的框架代碼:
package org.zero01.dao;import java.util.ArrayList; import java.util.HashMap; import java.util.List;public class BlockChain {// 存儲(chǔ)區(qū)塊鏈private List<Object> chain;// 該實(shí)例變量用于當(dāng)前的交易信息列表private List<Object> currentTransactions;public BlockChain() {// 初始化區(qū)塊鏈以及當(dāng)前的交易信息列表this.chain = new ArrayList<Object>();this.currentTransactions= new ArrayList<Object>();}public List<Object> getChain() {return chain;}public void setChain(List<Object> chain) {this.chain = chain;}public List<Object> getCurrentTransactions() {return currentTransactions;}public void setCurrentTransactions(List<Object> currentTransactions) {this.currentTransactions = currentTransactions;}public Object lastBlock() {return null;}public HashMap<String, Object> newBlock() {return null;}public int newTransactions() {return 0;}public static Object hash(HashMap<String, Object> block) {return null;} }Blockchain類用來管理區(qū)塊鏈,它能存儲(chǔ)交易,加入新塊等,下面我們來進(jìn)一步完善這些方法。
區(qū)塊的結(jié)構(gòu)
首先需要說明一下區(qū)塊的結(jié)構(gòu),每個(gè)區(qū)塊包含屬性:索引(index),時(shí)間戳(timestamp),交易列表(transactions),工作量證明(稍后解釋)以及前一個(gè)區(qū)塊的Hash值。
以下是一個(gè)區(qū)塊的結(jié)構(gòu):
block = {'index': 1,'timestamp': 1506057125.900785,'transactions': [{'sender': "8527147fe1f5426f9dd545de4b27ee00",'recipient': "a77f5cdfa2934df3954a5c7c7da5df1f",'amount': 5,}],'proof': 324984774000,'previous_hash': "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824" }到這里,區(qū)塊鏈的概念就清楚了,每個(gè)新的區(qū)塊都包含上一個(gè)區(qū)塊的Hash,這是關(guān)鍵的一點(diǎn),它保障了區(qū)塊鏈不可變性。如果攻擊者破壞了前面的某個(gè)區(qū)塊,那么后面所有區(qū)塊的Hash都會(huì)變得不正確。不理解的話,慢慢消化,可以參考區(qū)塊鏈記賬原理。
由于需要計(jì)算區(qū)塊的hash,所以我們得先編寫一個(gè)用于計(jì)算hash值的工具類:
package org.zero01.util;import java.security.MessageDigest; import java.security.NoSuchAlgorithmException;public class Encrypt {/*** 傳入字符串,返回 SHA-256 加密字符串* * @param strText* @return*/public String getSHA256(final String strText) {return SHA(strText, "SHA-256");}/*** 傳入字符串,返回 SHA-512 加密字符串* * @param strText* @return*/public String getSHA512(final String strText) {return SHA(strText, "SHA-512");}/*** 傳入字符串,返回 MD5 加密字符串* * @param strText* @return*/public String getMD5(final String strText) {return SHA(strText, "SHA-512");}/*** 字符串 SHA 加密* * @param strSourceText* @return*/private String SHA(final String strText, final String strType) {// 返回值String strResult = null;// 是否是有效字符串if (strText != null && strText.length() > 0) {try {// SHA 加密開始// 創(chuàng)建加密對(duì)象,傳入加密類型MessageDigest messageDigest = MessageDigest.getInstance(strType);// 傳入要加密的字符串messageDigest.update(strText.getBytes());// 得到 byte 數(shù)組byte byteBuffer[] = messageDigest.digest();// 將 byte 數(shù)組轉(zhuǎn)換 string 類型StringBuffer strHexString = new StringBuffer();// 遍歷 byte 數(shù)組for (int i = 0; i < byteBuffer.length; i++) {// 轉(zhuǎn)換成16進(jìn)制并存儲(chǔ)在字符串中String hex = Integer.toHexString(0xff & byteBuffer[i]);if (hex.length() == 1) {strHexString.append('0');}strHexString.append(hex);}// 得到返回結(jié)果strResult = strHexString.toString();} catch (NoSuchAlgorithmException e) {e.printStackTrace();}}return strResult;} }加入交易功能
接下來我們需要實(shí)現(xiàn)一個(gè)交易\記賬功能,所以來完善newTransactions以及l(fā)astBlock方法:
/*** @return 得到區(qū)塊鏈中的最后一個(gè)區(qū)塊*/public HashMap<String, Object> lastBlock() {return getChain().get(getChain().size() - 1);}/*** 生成新交易信息,信息將加入到下一個(gè)待挖的區(qū)塊中* * @param sender* 發(fā)送方的地址* @param recipient* 接收方的地址* @param amount* 交易數(shù)量* @return 返回存儲(chǔ)該交易事務(wù)的塊的索引*/public int newTransactions(String sender, String recipient, long amount) {Map<String, Object> transaction = new HashMap<String, Object>();transaction.put("sender", sender);transaction.put("recipient", recipient);transaction.put("amount", amount);getCurrentTransactions().add(transaction);return (Integer) lastBlock().get("index") + 1;}newTransactions方法向列表中添加一個(gè)交易記錄,并返回該記錄將被添加到的區(qū)塊 (下一個(gè)待挖掘的區(qū)塊)的索引,等下在用戶提交交易時(shí)會(huì)有用。
創(chuàng)建新塊
當(dāng)Blockchain實(shí)例化后,我們需要構(gòu)造一個(gè)創(chuàng)世區(qū)塊(沒有前區(qū)塊的第一個(gè)區(qū)塊),并且給它加上一個(gè)工作量證明。
每個(gè)區(qū)塊都需要經(jīng)過工作量證明,俗稱挖礦,稍后會(huì)繼續(xù)講解。
為了構(gòu)造創(chuàng)世塊,我們還需要完善剩下的幾個(gè)方法,并且把該類設(shè)計(jì)為單例:
package org.zero01.dao;import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map;import org.json.JSONObject; import org.zero01.util.Encrypt;public class BlockChain {// 存儲(chǔ)區(qū)塊鏈private List<Map<String, Object>> chain;// 該實(shí)例變量用于當(dāng)前的交易信息列表private List<Map<String, Object>> currentTransactions;private static BlockChain blockChain = null;private BlockChain() {// 初始化區(qū)塊鏈以及當(dāng)前的交易信息列表chain = new ArrayList<Map<String, Object>>();currentTransactions = new ArrayList<Map<String, Object>>();// 創(chuàng)建創(chuàng)世區(qū)塊newBlock(100, "0");}// 創(chuàng)建單例對(duì)象public static BlockChain getInstance() {if (blockChain == null) {synchronized (BlockChain.class) {if (blockChain == null) {blockChain = new BlockChain();}}}return blockChain;}public List<Map<String, Object>> getChain() {return chain;}public void setChain(List<Map<String, Object>> chain) {this.chain = chain;}public List<Map<String, Object>> getCurrentTransactions() {return currentTransactions;}public void setCurrentTransactions(List<Map<String, Object>> currentTransactions) {this.currentTransactions = currentTransactions;}/*** @return 得到區(qū)塊鏈中的最后一個(gè)區(qū)塊*/public Map<String, Object> lastBlock() {return getChain().get(getChain().size() - 1);}/*** 在區(qū)塊鏈上新建一個(gè)區(qū)塊* * @param proof* 新區(qū)塊的工作量證明* @param previous_hash* 上一個(gè)區(qū)塊的hash值* @return 返回新建的區(qū)塊*/public Map<String, Object> newBlock(long proof, String previous_hash) {Map<String, Object> block = new HashMap<String, Object>();block.put("index", getChain().size() + 1);block.put("timestamp", System.currentTimeMillis());block.put("transactions", getCurrentTransactions());block.put("proof", proof);// 如果沒有傳遞上一個(gè)區(qū)塊的hash就計(jì)算出區(qū)塊鏈中最后一個(gè)區(qū)塊的hashblock.put("previous_hash", previous_hash != null ? previous_hash : hash(getChain().get(getChain().size() - 1)));// 重置當(dāng)前的交易信息列表setCurrentTransactions(new ArrayList<Map<String, Object>>());getChain().add(block);return block;}/*** 生成新交易信息,信息將加入到下一個(gè)待挖的區(qū)塊中* * @param sender* 發(fā)送方的地址* @param recipient* 接收方的地址* @param amount* 交易數(shù)量* @return 返回該交易事務(wù)的塊的索引*/public int newTransactions(String sender, String recipient, long amount) {Map<String, Object> transaction = new HashMap<String, Object>();transaction.put("sender", sender);transaction.put("recipient", recipient);transaction.put("amount", amount);getCurrentTransactions().add(transaction);return (Integer) lastBlock().get("index") + 1;}/*** 生成區(qū)塊的 SHA-256格式的 hash值* * @param block* 區(qū)塊* @return 返回該區(qū)塊的hash*/public static Object hash(Map<String, Object> block) {return new Encrypt().getSHA256(new JSONObject(block).toString());} }通過上面的代碼和注釋可以對(duì)區(qū)塊鏈有直觀的了解,接下來我們來編寫一些簡單的測試代碼來測試一下這些代碼能否正常工作:
package org.zero01.test;import java.util.HashMap; import java.util.Map;import org.json.JSONObject; import org.zero01.dao.BlockChain;public class Test {public static void main(String[] args) throws Exception {BlockChain blockChain = BlockChain.getInstance();// 一個(gè)區(qū)塊中可以不包含任何交易記錄Map<String, Object> block = blockChain.newBlock(300, null);System.out.println(new JSONObject(block));// 一個(gè)區(qū)塊中可以包含一筆交易記錄blockChain.newTransactions("123", "222", 33);Map<String, Object> block1 = blockChain.newBlock(500, null);System.out.println(new JSONObject(block1));// 一個(gè)區(qū)塊中可以包含多筆交易記錄blockChain.newTransactions("321", "555", 133);blockChain.newTransactions("000", "111", 10);blockChain.newTransactions("789", "369", 65);Map<String, Object> block2 = blockChain.newBlock(600, null);System.out.println(new JSONObject(block2));// 查看整個(gè)區(qū)塊鏈Map<String, Object> chain = new HashMap<String, Object>();chain.put("chain", blockChain.getChain());chain.put("length", blockChain.getChain().size());System.out.println(new JSONObject(chain));} }運(yùn)行結(jié)果:
// 挖出來的新區(qū)塊 {"index": 2,"transactions": [],"proof": 300,"timestamp": 1519478559703,"previous_hash": "185b62ca1fc31285bce8878acfc970983cb561f19c63b65120d2c95148cf151f" }// 包含一筆交易的區(qū)塊 {"index": 3,"transactions": [{"amount": 33,"sender": "123","recipient": "222"}],"proof": 500,"timestamp": 1519478559728,"previous_hash": "bce15693c0a028b1fc6d7d1c1d30494f97ef37b8b3384865559ceed9b5ff798b" }// 包含多筆交易的區(qū)塊 {"index": 4,"transactions": [{"amount": 133,"sender": "321","recipient": "555"},{"amount": 10,"sender": "000","recipient": "111"},{"amount": 65,"sender": "789","recipient": "369"}],"proof": 600,"timestamp": 1519478656178,"previous_hash": "b0edde645f76fc3a6cb45b7c91b07b686e8e214cfc1dea4823bf38bda37c909c" }// 整個(gè)區(qū)塊鏈,第一個(gè)是創(chuàng)始區(qū)塊 {"chain": [{"index": 1,"transactions": [],"proof": 100,"timestamp": 1519478656153,"previous_hash": "0"},{"index": 2,"transactions": [],"proof": 300,"timestamp": 1519478656154,"previous_hash": "7925a01fa8cb67b51ea89b9cfcfa16c5febee008bb559f94c5758418e7acc670"},{"index": 3,"transactions": [{"amount": 33,"sender": "123","recipient": "222"}],"proof": 500,"timestamp": 1519478656178,"previous_hash": "40ccc2f4ad97f75cb611ed69a4ecc7438eefd31afca17ca00c2ed7b5163d0831"},{"index": 4,"transactions": [{"amount": 133,"sender": "321","recipient": "555"},{"amount": 10,"sender": "000","recipient": "111"},{"amount": 65,"sender": "789","recipient": "369"}],"proof": 600,"timestamp": 1519478656178,"previous_hash": "b0edde645f76fc3a6cb45b7c91b07b686e8e214cfc1dea4823bf38bda37c909c"}],"length": 4 }通過以上的測試,可以很直觀的看到區(qū)塊鏈的數(shù)據(jù),但是現(xiàn)在只是完成了初步的代碼編寫,還有幾件事情還沒做,接下來我們看看區(qū)塊是怎么挖出來的。
理解工作量證明
新的區(qū)塊依賴工作量證明算法(PoW)來構(gòu)造。PoW的目標(biāo)是找出一個(gè)符合特定條件的數(shù)字,這個(gè)數(shù)字很難計(jì)算出來,但容易驗(yàn)證。這就是工作量證明的核心思想。
為了方便理解,舉個(gè)例子:
假設(shè)一個(gè)整數(shù) x 乘以另一個(gè)整數(shù) y 的積的 Hash 值必須以 0 結(jié)尾,即 hash(x * y) = ac23dc…0。設(shè)變量 x = 5,求 y 的值?
用Java實(shí)現(xiàn)如下:
package org.zero01.test;import org.zero01.util.Encrypt;public class TestProof {public static void main(String[] args) {int x = 5;int y = 0;while (!new Encrypt().getSHA256((x * y) + "").endsWith("0")) {y++;}System.out.println("y=" + y);} }結(jié)果是 y=21 ,因?yàn)?#xff1a;
hash(5 * 21) = 1253e9373e...5e3600155e860在比特幣中,使用稱為Hashcash的工作量證明算法,它和上面的問題很類似。礦工們?yōu)榱藸帄Z創(chuàng)建區(qū)塊的權(quán)利而爭相計(jì)算結(jié)果。通常,計(jì)算難度與目標(biāo)字符串需要滿足的特定字符的數(shù)量成正比,礦工算出結(jié)果后,會(huì)獲得比特幣獎(jiǎng)勵(lì)。
當(dāng)然,在網(wǎng)絡(luò)上非常容易驗(yàn)證這個(gè)結(jié)果。
實(shí)現(xiàn)工作量證明
讓我們來實(shí)現(xiàn)一個(gè)相似PoW算法,規(guī)則是:尋找一個(gè)數(shù) p,使得它與前一個(gè)區(qū)塊的 proof 拼接成的字符串的 Hash 值以 4 個(gè)零開頭:
衡量算法復(fù)雜度的辦法是修改零開頭的個(gè)數(shù)。使用4個(gè)來用于演示,你會(huì)發(fā)現(xiàn)多一個(gè)零都會(huì)大大增加計(jì)算出結(jié)果所需的時(shí)間。
現(xiàn)在Blockchain類基本已經(jīng)完成了,接下來使用Servlet接收HTTP請(qǐng)求來進(jìn)行交互。
Blockchain作為API接口
我們將使用Java Web中的Servlet來接收用戶的HTTP請(qǐng)求,通過Servlet我們可以方便的將網(wǎng)絡(luò)請(qǐng)求的數(shù)據(jù)映射到相應(yīng)的方法上進(jìn)行處理,現(xiàn)在我們來讓Blockchain運(yùn)行在基于Java Web上。
我們將創(chuàng)建三個(gè)接口:
- /transactions/new 創(chuàng)建一個(gè)交易并添加到區(qū)塊
- /mine 告訴服務(wù)器去挖掘新的區(qū)塊
- /chain 返回整個(gè)區(qū)塊鏈
注冊(cè)節(jié)點(diǎn)ID
我們的“Tomcat服務(wù)器”將扮演區(qū)塊鏈網(wǎng)絡(luò)中的一個(gè)節(jié)點(diǎn),而每個(gè)節(jié)點(diǎn)都需要有一個(gè)唯一的標(biāo)識(shí)符,也就是id。在這里我們使用UUID來作為節(jié)點(diǎn)ID,我們需要在服務(wù)器啟動(dòng)時(shí),將UUID設(shè)置到ServletContext屬性中,這樣我們的服務(wù)器就擁有了唯一標(biāo)識(shí),這一步我們可以配置監(jiān)聽類來完成,首先配置web.xml文件內(nèi)容如下:
然后編寫一個(gè)類實(shí)現(xiàn)ServletContextListener接口,在初始化方法中把uuid設(shè)置到ServletContext的屬性中:
package org.zero01.servlet;import java.util.UUID;import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener;public class InitialID implements ServletContextListener {public void contextInitialized(ServletContextEvent sce) {ServletContext servletContext = sce.getServletContext();String uuid = UUID.randomUUID().toString().replace("-", "");servletContext.setAttribute("uuid", uuid);}public void contextDestroyed(ServletContextEvent sce) {} }創(chuàng)建Servlet類
我們這里沒有使用任何框架,所以我們需要通過最基本的Servlet來接收并處理用戶的HTTP請(qǐng)求:
我們先來完成最簡單的FullChain的代碼,這個(gè)Servlet用于向客戶端輸出整個(gè)區(qū)塊鏈的數(shù)據(jù)(JSON格式):
package org.zero01.servlet;import java.io.IOException; import java.io.PrintWriter; import java.util.HashMap; import java.util.Map;import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;import org.json.JSONObject; import org.zero01.core.BlockChain;// 該Servlet用于輸出整個(gè)區(qū)塊鏈的數(shù)據(jù) @WebServlet("/chain") public class FullChain extends HttpServlet {protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {BlockChain blockChain = BlockChain.getInstance();Map<String, Object> response = new HashMap<String, Object>();response.put("chain", blockChain.getChain());response.put("length", blockChain.getChain().size());JSONObject jsonResponse = new JSONObject(response);resp.setContentType("application/json");PrintWriter printWriter = resp.getWriter();printWriter.println(jsonResponse);printWriter.close();} }發(fā)送交易
然后是記錄交易數(shù)據(jù)的功能,每一個(gè)區(qū)塊都可以記錄交易數(shù)據(jù),發(fā)送到節(jié)點(diǎn)的交易數(shù)據(jù)結(jié)構(gòu)如下:
實(shí)現(xiàn)代碼如下:
package org.zero01.servlet;import java.io.BufferedReader; import java.io.IOException; import java.io.PrintWriter;import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;import org.json.JSONObject; import org.zero01.core.BlockChain;// 該Servlet用于接收并處理新的交易信息 @WebServlet("/transactions/new") public class NewTransaction extends HttpServlet {protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {req.setCharacterEncoding("utf-8");// 讀取客戶端傳遞過來的數(shù)據(jù)并轉(zhuǎn)換成JSON格式BufferedReader reader = req.getReader();String input = null;StringBuffer requestBody = new StringBuffer();while ((input = reader.readLine()) != null) {requestBody.append(input);}JSONObject jsonValues = new JSONObject(requestBody.toString());// 檢查所需要的字段是否位于POST的data中String[] required = { "sender", "recipient", "amount" };for (String string : required) {if (!jsonValues.has(string)) {// 如果沒有需要的字段就返回錯(cuò)誤信息resp.sendError(400, "Missing values");}}// 新建交易信息BlockChain blockChain = BlockChain.getInstance();int index = blockChain.newTransactions(jsonValues.getString("sender"), jsonValues.getString("recipient"),jsonValues.getLong("amount"));// 返回json格式的數(shù)據(jù)給客戶端resp.setContentType("application/json");PrintWriter printWriter = resp.getWriter();printWriter.println(new JSONObject().append("message", "Transaction will be added to Block " + index));printWriter.close();} }挖礦
挖礦正是神奇所在,它很簡單,只做了以下三件事:
- 計(jì)算工作量證明PoW
- 通過新增一個(gè)交易授予礦工(自己)一個(gè)幣
- 構(gòu)造新區(qū)塊并將其添加到鏈中
代碼實(shí)現(xiàn)如下:
package org.zero01.servlet;import java.io.IOException; import java.io.PrintWriter; import java.util.HashMap; import java.util.Map;import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;import org.json.JSONObject; import org.zero01.core.BlockChain;//該Servlet用于運(yùn)行工作算法的證明來獲得下一個(gè)證明,也就是所謂的挖礦 @WebServlet("/mine") public class Mine extends HttpServlet {protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {BlockChain blockChain = BlockChain.getInstance();Map<String, Object> lastBlock = blockChain.lastBlock();long lastProof = Long.parseLong(lastBlock.get("proof") + "");long proof = blockChain.proofOfWork(lastProof);// 給工作量證明的節(jié)點(diǎn)提供獎(jiǎng)勵(lì),發(fā)送者為 "0" 表明是新挖出的幣String uuid = (String) this.getServletContext().getAttribute("uuid");blockChain.newTransactions("0", uuid, 1);// 構(gòu)建新的區(qū)塊Map<String, Object> newBlock = blockChain.newBlock(proof, null);Map<String, Object> response = new HashMap<String, Object>();response.put("message", "New Block Forged");response.put("index", newBlock.get("index"));response.put("transactions", newBlock.get("transactions"));response.put("proof", newBlock.get("proof"));response.put("previous_hash", newBlock.get("previous_hash"));// 返回新區(qū)塊的數(shù)據(jù)給客戶端resp.setContentType("application/json");PrintWriter printWriter = resp.getWriter();printWriter.println(new JSONObject(response));printWriter.close();} }注意交易的接收者是我們自己的服務(wù)器節(jié)點(diǎn),我們做的大部分工作都只是圍繞Blockchain類的方法進(jìn)行交互。到此,我們的區(qū)塊鏈就算完成了,我們來實(shí)際運(yùn)行下。
運(yùn)行區(qū)塊鏈
由于我們這里也沒有寫前端的web頁面,只寫了后端的API,所以只能使用 Postman 之類的軟件去和API進(jìn)行交互。首先啟動(dòng)Tomcat服務(wù)器,然后通過post請(qǐng)求?http://localhost:8089/BlockChain_Java/transactions/new?來添加新的交易信息(注意我這里沒有使用默認(rèn)的8080端口,默認(rèn)的情況下是8080端口):
但是這時(shí)候還沒有新的區(qū)塊可以寫入這個(gè)交易信息,所以我們還需要請(qǐng)求?http://localhost:8089/BlockChain_Java/mine?來進(jìn)行挖礦,挖出一個(gè)新的區(qū)塊來存儲(chǔ)這筆交易:
在挖了兩次礦之后,就有3個(gè)塊了,通過請(qǐng)求?http://localhost:8089/BlockChain_Java/chain?可以得到所有的區(qū)塊塊的信息:
{"chain": [{"index": 1,"proof": 100,"transactions": [],"timestamp": 1520928588165,"previous_hash": "0"},{"index": 2,"proof": 35293,"transactions": [{"amount": 6,"sender": "d4ee26eee15148ee92c6cd394edd974e","recipient": "someone-other-address"},{"amount": 1,"sender": "0","recipient": "050bbfe4ad644d008545ff490387a889"}],"timestamp": 1520928734580,"previous_hash": "e5cf7ba38f7f0c3a93fcca5d57b624c8fd255093af4abe3c6999be61bdb81040"},{"index": 3,"proof": 35089,"transactions": [{"amount": 1,"sender": "0","recipient": "050bbfe4ad644d008545ff490387a889"}],"timestamp": 1520928870963,"previous_hash": "aa64ab003d15d50a43bd59deb88c939ea43349d00d0b653abd83b42e8fa4417c"}],"length": 3 }一致性(共識(shí))
我們已經(jīng)有了一個(gè)基本的區(qū)塊鏈可以接受交易和挖礦。但是區(qū)塊鏈系統(tǒng)應(yīng)該是分布式的。既然是分布式的,那么我們究竟拿什么保證所有節(jié)點(diǎn)有同樣的鏈呢?這就是一致性問題,我們要想在網(wǎng)絡(luò)上有多個(gè)節(jié)點(diǎn),就必須實(shí)現(xiàn)一個(gè)一致性的算法。
注冊(cè)節(jié)點(diǎn)
在實(shí)現(xiàn)一致性算法之前,我們需要找到一種方式讓一個(gè)節(jié)點(diǎn)知道它相鄰的節(jié)點(diǎn)。每個(gè)節(jié)點(diǎn)都需要保存一份包含網(wǎng)絡(luò)中其它節(jié)點(diǎn)的記錄。因此讓我們新增幾個(gè)接口:
- /nodes/register 接收URL形式的新節(jié)點(diǎn)列表
- /nodes/resolve執(zhí)行一致性算法,解決任何沖突,確保節(jié)點(diǎn)擁有正確的鏈
我們需要修改下BlockChain的構(gòu)造函數(shù)并提供一個(gè)注冊(cè)節(jié)點(diǎn)方法:
package org.zero01.core; ... import java.net.URL; ...private Set<String> nodes;private BlockChain() {...// 用于存儲(chǔ)網(wǎng)絡(luò)中其他節(jié)點(diǎn)的集合nodes = new HashSet<String>();...}public Set<String> getNodes() {return nodes;}/*** 注冊(cè)節(jié)點(diǎn)* * @param address* 節(jié)點(diǎn)地址* @throws MalformedURLException*/public void registerNode(String address) throws MalformedURLException {URL url = new URL(address);String node = url.getHost() + ":" + (url.getPort() == -1 ? url.getDefaultPort() : url.getPort());nodes.add(node);}...我們用 HashSet 集合來儲(chǔ)存節(jié)點(diǎn),這是一種避免出現(xiàn)重復(fù)添加節(jié)點(diǎn)的簡單方法。
實(shí)現(xiàn)共識(shí)算法
前面提到,沖突是指不同的節(jié)點(diǎn)擁有不同的鏈,為了解決這個(gè)問題,規(guī)定最長的、有效的鏈才是最終的鏈,換句話說,網(wǎng)絡(luò)中有效最長鏈才是實(shí)際的鏈。
我們使用以下算法,來達(dá)到網(wǎng)絡(luò)中的共識(shí):
... import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; ...public class BlockChain {.../*** 檢查是否是有效鏈,遍歷每個(gè)區(qū)塊驗(yàn)證hash和proof,來確定一個(gè)給定的區(qū)塊鏈?zhǔn)欠裼行? * @param chain* @return*/public boolean validChain(List<Map<String, Object>> chain) {Map<String, Object> lastBlock = chain.get(0);int currentIndex = 1;while (currentIndex < chain.size()) {Map<String, Object> block = chain.get(currentIndex);System.out.println(lastBlock.toString());System.out.println(block.toString());System.out.println("\n-------------------------\n");// 檢查block的hash是否正確if (!block.get("previous_hash").equals(hash(lastBlock))) {return false;}lastBlock = block;currentIndex++;}return true;}/*** 共識(shí)算法解決沖突,使用網(wǎng)絡(luò)中最長的鏈. 遍歷所有的鄰居節(jié)點(diǎn),并用上一個(gè)方法檢查鏈的有效性, 如果發(fā)現(xiàn)有效更長鏈,就替換掉自己的鏈* * @return 如果鏈被取代返回true, 否則返回false* @throws IOException*/public boolean resolveConflicts() throws IOException {Set<String> neighbours = this.nodes;List<Map<String, Object>> newChain = null;// 尋找最長的區(qū)塊鏈long maxLength = this.chain.size();// 獲取并驗(yàn)證網(wǎng)絡(luò)中的所有節(jié)點(diǎn)的區(qū)塊鏈for (String node : neighbours) {URL url = new URL("http://" + node + "/BlockChain_Java/chain");HttpURLConnection connection = (HttpURLConnection) url.openConnection();connection.connect();if (connection.getResponseCode() == 200) {BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream(), "utf-8"));StringBuffer responseData = new StringBuffer();String response = null;while ((response = bufferedReader.readLine()) != null) {responseData.append(response);}bufferedReader.close();JSONObject jsonData = new JSONObject(bufferedReader.toString());long length = jsonData.getLong("length");List<Map<String, Object>> chain = (List) jsonData.getJSONArray("chain").toList();// 檢查長度是否長,鏈?zhǔn)欠裼行f (length > maxLength && validChain(chain)) {maxLength = length;newChain = chain;}}}// 如果發(fā)現(xiàn)一個(gè)新的有效鏈比我們的長,就替換當(dāng)前的鏈if (newChain != null) {this.chain = newChain;return true;}return false;}...第一個(gè)方法 validChain() 用來檢查是否是有效鏈,遍歷每個(gè)塊驗(yàn)證hash和proof.
第2個(gè)方法 resolveConflicts() 用來解決沖突,遍歷所有的鄰居節(jié)點(diǎn),并用上一個(gè)方法檢查鏈的有效性, 如果發(fā)現(xiàn)有效更長鏈,就替換掉自己的鏈
讓我們添加兩個(gè)Servlet,一個(gè)用來注冊(cè)節(jié)點(diǎn),一個(gè)用來解決沖突:
我們可以在不同的機(jī)器運(yùn)行節(jié)點(diǎn),或在一臺(tái)機(jī)機(jī)開啟不同的網(wǎng)絡(luò)端口來模擬多節(jié)點(diǎn)的網(wǎng)絡(luò),這里在同一臺(tái)機(jī)器開啟不同的端口演示,配置兩個(gè)不同端口的服務(wù)器即可,我這里啟動(dòng)了兩個(gè)節(jié)點(diǎn):http://localhost:8089?和?http://localhost:8066。
兩個(gè)節(jié)點(diǎn)互相進(jìn)行注冊(cè):
然后在8066節(jié)點(diǎn)上挖兩個(gè)塊,確保是更長的鏈:
接著在8089節(jié)點(diǎn)上訪問接口/nodes/resolve ,這時(shí)8089節(jié)點(diǎn)的鏈會(huì)通過共識(shí)算法被8066節(jié)點(diǎn)的鏈取代:
通過共識(shí)算法保持一致性后,兩個(gè)節(jié)點(diǎn)的區(qū)塊鏈數(shù)據(jù)就都是一致的了:
到此為止我們就完成了一個(gè)區(qū)塊鏈的開發(fā),雖然這只是一個(gè)最基本的區(qū)塊鏈,而且在開發(fā)的過程中也沒有考慮太多的程序設(shè)計(jì)方面的問題,而是以最基本、原始的方式進(jìn)行開發(fā)的。但是我們不妨以這個(gè)簡單的區(qū)塊鏈為基礎(chǔ),發(fā)揮自己的能力動(dòng)手去重構(gòu)、擴(kuò)展、完善這個(gè)區(qū)塊鏈程序,直至成為自己的一個(gè)小項(xiàng)目。
總結(jié)
以上是生活随笔為你收集整理的使用Java语言从零开始创建区块链的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 基于Java语言构建区块链(四)—— 交
- 下一篇: 张首晟:用科学思维指导投资