閱讀對象
本文閱讀對象,主要是希望和即將從事區塊鏈開發的項目架構師,開發工程師,項目設計或產品設計師。要求閱讀者具備一定的“區塊鏈”基礎知識、概念和以及相關的技術知識。
如果你只需要對區塊鏈應用做一個更深更直觀的瞭解,通過本文的例子更清晰瞭解區塊鏈是怎麼回事,大概是怎麼開發出來的,怎麼使用,那麼知道一些區塊鏈相關知識即可。
如果你是一個想從事這方面工作的人,尤其是從事開發和架構的技術人員,那麼需要的技術知識就相當的多了,無法一一列舉,大概一個高級網絡開發工程師和中級以上的架構師水平,是需要的。
前言
很多新的概念,人們在剛接觸的時候,會讓人感到非常困惑,學了好多次都稀裏糊塗,並不僅僅是技術,很多地方都是如此。對於比較難理解的概念,用什麼辦法去學習最高效呢?我一直認爲,就是找一個例子,或者寫一個Demo,世上無難事,for一個example即可。。。
最近一段時間,AI人工智能方興未艾,區塊鏈技術又迎面走來,對數字貨幣的崛起感到新奇的我們,估計很想知道其背後的技術--區塊鏈是怎樣的一個東西。但是完全搞懂區塊鏈並非易事,在實踐中學習方爲上策。
我喜歡把程序當成詩歌來寫,理所當然要通過寫代碼來實現並運行一個例子,來理解並學習這門技術。這裏,就通過用JAVA語言實現和構建一個區塊鏈來探討對區塊鏈的理解。
本文,我們要用區塊鏈來實現這麼一個例子,名字叫“區塊鏈成語接龍”。用戶通過這個例子,可以不斷的接龍前面一個用戶的成語,規則和普通遊戲一樣,前面一個用戶的成語的最後一個字,作爲後面一個成語的第一個字。
單單作爲一個應用,這個很簡單吶,問題是,我們要用區塊鏈的原理和相關技術實現它。
OK,let’s go… …
準備工作
這個時候其實我很想大喊一聲:“區塊鏈”其實不是某一種單純的技術,而是基於某一種思想的多種技術的結合。
需要的技術包括分佈式存儲,分佈式計算,P2P數據同步,加密解密,安全傳輸,一些語言,一些開發工具。。。還有一些新型的概念 – 分佈式節點,工作量證明,共識算法。。。等等
基於哪一種思想呢,大家都說是“去中心化”,我覺得就是“反壟斷”“反。。。”,敏感話題,不展開了。但是我事實上是一個喜歡民主的人,所以區塊鏈的“去中心化”對我吸引力頗大。。。all men are created equal…J
本文裏面涉及到的一些例子,來源是網上,有些是Python版本,有些是Java版本,C版本,考慮到JAVA更爲通用和易讀寫(其實我是認爲JAVA語言更像詩歌了),就把他們改成了JAVA版,所以要求讀者對詩歌..哦,不是,是對JAVA非常瞭解,能讀寫基本的語法框架和邏輯,並且因爲這個DEMO的特殊性,需要對網絡框架和HTTP請求有基本的瞭解。
我們知道區塊鏈是數據塊和鏈的存儲方式的組合,是由N多個數據區塊按照鏈的組織和記錄構成的不可變、有序的鏈結構,記錄可以是交易、文件或任何你想要的數據,同時它們是通過哈希值(hashes)鏈接起來的。所以,在閱讀本文之前,一定要閱讀幾篇關於區塊鏈的文章,瞭解裏面塊,鏈,數據,工作量證明等等概念,最好也瞭解一些相關技術比如Hash,分佈式存儲等等的概念。如果你還不是很瞭解這些,請找度娘。
如果在瞭解這些概念的過程中,一不小心接觸到了比特幣,請一定要明白,比特幣只是區塊鏈技術的一個產品實現。如果還接觸到其它一些諸如以太網等等的名詞,那麼需要知道,這些都是區塊鏈技術下實現的一些框架和產品。
環境準備
理論上來說,可以用任何一種語言來創建任何一種技術的例子,包括區塊鏈的例子,我們這裏選用了JAVA,所以要在自己的電腦上,準備JAVA的相關開發環境。
確保你的電腦上已經安裝了較新版本的JDK,Tomcat和某一種你熟悉的最好能整合Tomcat的JAVA IDE,本例用的是Eclipse。Tomcat在我們這個例子中間,是一個WEB工具,因爲我們的項目,需要基於WEB HTTP 發佈和運行。
如何安裝Java和各種工具,包括如何用Eclipse創建Dynamic web項目併發布到Tomcat等等步驟,既然你已經如此熟悉JAVA了,這裏就不再多講。
<小提示:如需源碼,請點擊這裏下載>
開始創建區塊鏈
區塊或稱數據塊Block類
區塊鏈中每個區塊包含以下基本內容:索引(index),Unix時間戳(timestamp),數據塊(data)(包括交易,文字,申明,獎勵等任何和合適的內容),證明或工作量證明(proof稍後解釋)以及前一個區塊的Hash值,Hash 用來鏈接數據塊,同時確保數據塊不被非法修改。
以下是一個區塊的結構:
public class Block {
int iIndex; //索引
String sProof; //工作量證明,在這個例子裏面,其實就是一個經過驗證的正確的成語
String sPreviousHash; //前一個區塊的Hash值
Timestamp tsCreateTime; //區塊創建時間戳
/*數據塊
*
* 用戶每接上一個成語,會得到系統10元錢的獎勵,同時會贏得前面一個用戶的2元錢
* 數據區同時需要記錄自己的用戶名和回答出上一個成語的用戶名
*
* */
String sSender; //回答出上一個成語的用戶名
String sRecipient; //回答出當前這個成語的用戶名
final int iMoneyAward=10; //系統獎勵,數額固定
final int iMoneyWin=2; //贏取獎勵,數額固定
public Block(){
}
}
區塊鏈實現Blockchain類
import java.util.*;
import blockchain.Block;
public class BlockChain {
//用來存儲區塊
private List<Block> lBlockchain=new ArrayList<>[];
public BlockChain(){
}
//創建新塊
public Block NewBlock(){
Block bRet=null;
//在這裏創建一個新塊
return bRet;
}
//Hash 一個塊
public String Hash(Block block){
String sHash=null;
//在這裏Hash 一個塊
return sHash;
}
//其他方法
//....
}
Blockchain類用來管理鏈條,它能存儲和更新鏈數據,加入新塊等等,下面我們來進一步增加和完善裏面的一些方法
<小提示:如需源碼,請點擊這裏下載>
創建新塊和創世塊
當一個用戶按照成語接龍的規則,對上上一個成語,並且系統驗證這成語正確(工作量被證明)。這個時候我們就可以創建一個新塊,並且加到鏈裏面。
一旦工作量證明確認,並且上一個塊hash 生成後,就可以簡單調用函數創建一個新塊了。
創建新塊方法如下:
//創建新塊
public Block NewBlock(int i,String proof,String hash,Timestamp c,String sender,String recipient){
Block bRet=null;
//在這裏創建一個新塊
bRet = new Block(i,proof,hash,c,sender,recipient);
return bRet;
}
這裏,我們需要提一下創世塊的概念,創世塊是區塊鏈的第一個區塊,它是沒有前區塊的。邏輯上只在第一個用戶第一次啓動系統的時候,才需要創建創世塊,後面的都是通過同步獲得。創世塊索引是0,同樣需要給它加上一個工作量證明,我們這裏就是初始成語“海闊天空”,因爲沒有前面的塊,所以hash=””,同時給一個固定的創建時間。方法如下:
//創始塊的創建,創世塊是一個塊,必須是固定的信息
//邏輯上來說,只有在區塊鏈產品的第一個用戶第一次啓動的時候,纔會需要創建創世塊
public Block CreateFirstBlock(){
try{
Timestamp t=new Timestamp((new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss")).parse("2018-01-01 01:01:01").getTime());
return NewBlock(0,"海闊天空","",t,"","");
}catch(Exception e){
return null;
}
}
這裏順便貼上生成塊的Hash字符串的方法:
//Hash 一個塊
public static String Hash(Block block){
String sHash=null;
//在這裏Hash 一個塊
String s=block.sPreviousHash+block.sProof+block.sRecipient+block.sSender+block.tsCreateTime.toString();
sHash = MD5(s);
return sHash;
}
public static String MD5(String key) {
char hexDigits[] = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
};
try {
byte[] btInput = key.getBytes();
// 獲得MD5摘要算法的 MessageDigest 對象
java.security.MessageDigest mdInst = java.security.MessageDigest.getInstance("MD5");
// 使用指定的字節更新摘要
mdInst.update(btInput);
// 獲得密文
byte[] md = mdInst.digest();
// 把密文轉換成十六進制的字符串形式
int j = md.length;
char str[] = new char[j * 2];
int k = 0;
for (int i = 0; i < j; i++) {
byte byte0 = md[i];
str[k++] = hexDigits[byte0 >>> 4 & 0xf];
str[k++] = hexDigits[byte0 & 0xf];
}
return new String(str);
} catch (Exception e) {
return null;
}
}
<小提示:如需源碼,請點擊這裏下載>
理解工作量證明
新的區塊生成,必須依賴工作量證明算法(PoW)來構造。
PoW的目標是找出一個能被公認的方法,來證明你所從事工作的正確性。
在當前區塊鏈應用中,PoW經常是被設計成通過尋找某一個符合特定條件的數字來證明,這個數字可能很難計算出來,但很容易驗證。
這就是工作量證明的核心思想。
爲了方便理解,舉個例子:
假設一個整數 x 乘以另一個整數 y 的積的 Hash 值必須以兩個零 ‘00’ 結尾,即 hash(x * y) = xxxxxx...00。設變量 x = 5,求 y 的值?
用Java實現如下:
int x=5;
int y=0;
while(true){
String md5=BlockChain.MD5(""+(x*y));
if(md5.charAt(md5.length()-1)=='0'){
break;
}
y+=1;
}
最後得出 y=69
因爲MD5(5*69)=D81F9C1BE2E08964BF9F24B15F0E4900
隨着區塊鏈應用的擴展,應該有更多合理的工作量證明算法。
在比特幣中,使用稱爲Hashcash的工作量證明算法,它和上面的問題很類似。礦工們爲了爭奪創建區塊的權利而爭相計算結果,甚至動用N臺電腦,這就是稱之爲挖礦。通常,計算難度與目標字符串需要滿足的特定字符的數量成正比,礦工算出結果後,會獲得比特幣獎勵。當然,一旦計算出來,會非常容易驗證這個結果。
實現我們的工作量證明
我們的工作量證明,那就比較簡單,當用戶輸入下一個成語的時候,我們打開成語詞典或者百度,一查。。。。就可以知道它接的對還是錯了。
從系統上來說,我們可以通過調用外部開發查詢接口,或者自帶成語詞庫,來實現這個工作量證明,代碼如下(此處引用了一個外部成語真實性查詢接口):
//驗證當前的成語是否符合規則
//pre 前一個成語
//cur 這一個成語
public static boolean ValidProof(String pre,String cur){
//驗證這個成語的頭一個字是不是上一個成語的最後一個字
if(cur.charAt(0)!=pre.charAt(pre.length()-1)){
return false;
}
//驗證是否是成語
//http://chengyu.t086.com/chaxun.php?q=%B9%E2%C3%F7%D5%FD%B4%F3&t=ChengYu
String content=httpRequest("http://chengyu.t086.com/chaxun.php?q="+cur+"&t=ChengYu");
if(content.indexOf("沒有找到與您搜索相關的成語")!=-1 || content.indexOf("搜索詞太長")!=-1){
return false;
}
return true;
}
小結:通過上面的講解,描述了區塊鏈涉及到的一些新概念,區塊,創世塊,鏈式存儲,工作量證明,主數據形式和內容,Hash加密等等。同時我們通過“成語接龍”的例子,在例子中用Java構造了相關的類和方法。
具備了區塊鏈項目的基礎組件後,下一步就是將這些組件運行起來,構造一個可以真正運行起來的區塊鏈項目。
<小提示:如需源碼,請點擊這裏下載>
啓動一個Blockchain項目
我們都知道,區塊鏈是一個分佈式存儲和分佈式計算互相結合的技術和產品框架。其核心是去中心化存儲。這個意味着,所有區塊鏈的數據都應該存儲在用戶本地而不是某個中心服務器。所以,一個真正的區塊鏈產品,需要在沒有中心服務器支持情況下,解決如下幾個基本的問題:
1、 每個用戶端啓動區塊鏈產品時,如何將區塊數據的初始下載到本地;
2、 新增加區塊時,區塊鏈內容的更新發布以及不同用戶新增區塊時間衝突的處理;
3、 日常運行過程中如何保證每個用戶端(節點)數據的最新和及時同步;
創建節點
區塊鏈項目中,一個正在運行着區塊鏈項目的用戶終端,稱爲一個節點。
每個用戶第一次啓動某個區塊鏈項目,就相當於創建了一個節點。而以後每次啓動項目,都意味着啓動了這個節點。
爲什麼說是創建一個節點?原因很簡單,區塊鏈的基本思想是去中心化,去中心化的換一種說法,中間任何一個終端,都可以是中心,各自是對等的組網模型,也就是P2P組網模型。因爲任何啓動的終端,除了需要完成數據下載,使用,運算之外,還需要完成對外提供數據,同步,驗證等等功能。
區塊鏈網絡系統之所以選擇P2P作爲其組網模型,這是由於二者思想的契合度確定的;區塊鏈的根本出發點之一是去中心化,中本聰在他的白皮書裏,提到電子現金系統中,第三方系統是多餘的,沒有價值,意思就是整個系統不要依賴任何特殊的第三方來完成自身系統的運轉;而P2P網絡的天然屬性,就是全網節點平等,無特殊節點;由於區塊鏈和對等網絡的建設思想,高度契合,再加上P2P網絡已經是一個發展成熟的網絡技術;二者走到一起,幾乎是一種必然。
所以我們在這個例子裏面引入Tomcat,一方面是爲了將一些用戶操作轉換到瀏覽器,演示方便,另外一方面,是爲了方便發佈一些對外接口,用來模擬在P2P對等網絡之間數據同步。
首先我們使用Eclipse建立一個動態WEB 項目,將上面相關類導入,同時新建一個類似下面的 index.jsp 歡迎頁面
<%@ page language="java" contentType="text/html; charset=gb2312"
pageEncoding="gb2312"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>區塊鏈測試</title>
</head>
<body>
<% java.util.Date d = new java.util.Date(); %>
<h1>
歡迎使用區塊鏈成語接龍。 <br>
</h1>
區塊數據同步中,請稍等。。。<br>
<%= d.toString() %> !
</body>
</html>
發佈項目到你安裝的Tomcat,啓動Tomcat,輸入如下地址,當看到如下圖所示頁面,即創建區塊鏈項目“成語接龍”的節點準備工作已經完成。
啓動區塊鏈應用,創建節點,包括以後每次啓動應用,都需要完成的第一個工作,是同步區塊鏈數據到本地或者更新本地區塊數據。碰到的第一個問題是,我怎麼知道到哪裏去同步數據呢,或者說我怎麼知道其它可以同步數據的節點的地址呢?
找遍百度,很多都說的很模糊,大致是需要 node-seed 也就是種子節點支持,最後找到了區塊鏈師祖比特幣的開發者的一段說明如下:
翻譯成中文,簡而言之就是在代碼裏面寫死了一個域名數組和IP地址數組,程序啓動的時候就去連這些域名和IP。
當然,覺得這種方法可以解決問題,但是,這或多或少使得“去中心化”蒙上了一層淡淡的陰影。
那麼根據我這個例子的模擬環境,我就把公司局域網可能的IP都 hard-code 在程序裏,逐個掃描查找最長鏈,作爲下載區塊鏈數據的掃描地址吧。下面是Java代碼:
static final String sIPPre="10.70.5.";
//對局域網內的電腦進行掃描,找到最長的鏈,下載到本地
static final String sDataFileDir="c://blockchain"; //本地存儲路徑
//從網絡讀取區塊鏈數據到本地文件
public static void DowloadData(){
//檢查數據文件目錄,不存在就創建
File dirFile = new File(sDataFileDir);
boolean bFile = dirFile.exists();
if(!bFile ){
bFile = dirFile.mkdir();
//往新創建的本地文件裏面寫一個創世塊
try{
FileOutputStream out = new FileOutputStream(new File(dirFile+"//data.txt"));
out.write((BlockChain.CreateFirstBlock().toInfoString()+"\r\n").getBytes());
out.close();
}catch(Exception e){
}
}
//掃描周邊的節點,找到最長的鏈,下載到本地
int iLastLen=0;
String sLastChain="";
for(int i=0;i<255;i+=1){
String sThisURL="http://"+sIPPre+i+":8080/blockchain/chain.jsp";
System.out.println(sThisURL);
String sChain=httpRequest(sThisURL);
if(sChain!=""){
System.out.println(sChain);
String sTemp[]=sChain.split("##");
if(sTemp.length>iLastLen){
iLastLen=sTemp.length;
sLastChain=sChain;
}
}
}
try{
if(sLastChain!=""){
FileOutputStream out = new FileOutputStream(new File(dirFile+"//data.txt"));
//System.out.println("before:"+sLastChain);
sLastChain=sLastChain.replace("##", "\r\n");
//System.out.println("after:"+sLastChain);
out.write((sLastChain+"\r\n").getBytes());
out.close();
}
}catch(Exception e){
}
}
DowloadData()這個方法,應該在應用啓動的時候被調用。也可能在應用執行過程中,後臺間隔性被調用。
你可能馬上會發現,多了一個外部接口:
http://"+IP+":8080/blockchain/chain.jsp
這個接口是每個節點都需要實現的,用來輸出自己節點最新的區塊鏈數據,在平時各個節點彼此互相調用,初始化和同步區塊鏈數據。在本例中,簡單實現方法如下:
建立一個 chain.jsp 頁面,放到Tomcat 路徑下,貼上裏面主要代碼
<%@ page language="java" contentType="text/html; charset=gb2312"
pageEncoding="gb2312"%><%
//java.util.Date d = new java.util.Date();
blockchain.BlockChain.LoadData();
%>
<%= blockchain.BlockChain.StringBlockchain() %>
用戶怎麼挖礦
到了這一步,可以把我們例子程序稍微完善一下,爲後面的“挖礦”概念做準備。
完善後的產品界面如下
啓動應用,首先是一個數據同步的歡迎頁面。此頁面用ajax方式實現後臺數據同步。
同步完成後,輸入手機號碼,進入下一個頁面。
注意頁面下方,增加了一個自動更新區塊鏈功能,也通過ajax實現。
所謂“挖礦”和“礦工”,只不過是借用了比特幣裏面的相關說法。“礦工”就是用戶了,“挖礦”就是用戶在這個區塊鏈應用上面做的事情,一般帶有一定的價值和目的,目前很多區塊鏈應用,“挖礦”就是完成某個工作,獲得系統一些獎勵。
在我們這個演示應用裏面,挖礦就是用戶根據上一個成語,遵守成語接龍的規則,想出並輸入下一個成語,系統驗證無誤,就會爲你在區塊鏈增加一條記錄,同時把獎勵也給你記錄。
其實把名字取得那麼神奇,事實卻很簡單,系統就是做了以下三件事:
1、計算工作量證明PoW,驗證用戶遞交的工作;
2、對於“挖礦”成功的用戶,通過新增一個區塊授予礦工即用戶一些分數或者虛擬幣獎勵;
3、構造新區塊並將其添加到鏈中,同步到每個節點。
代碼如下:
if(request.getParameter("answer")!=null){ //讀取到用戶的輸入
//加載區塊鏈數據
blockchain.BlockChain.LoadData();
//獲取區塊鏈中最後一個成語
String sPre=blockchain.BlockChain.lBlockchain.get(blockchain.BlockChain.lBlockchain.size()-1).sProof;
//用戶輸入的成語
String sCur=new String(request.getParameter("answer").getBytes("ISO8859-1"),"gb2312");
//判斷是不是正確答案,如果是,就要創建新塊並添加到區塊鏈中
if(blockchain.BlockChain.ValidProof(sPre,sCur)){
blockchain.Block bPre=blockchain.BlockChain.lBlockchain.get(blockchain.BlockChain.lBlockchain.size()-1);
//獲取前一個塊的Hash
String sHash=blockchain.BlockChain.Hash(bPre);
//創建新塊
blockchain.Block bCur=blockchain.BlockChain.NewBlock(bPre.iIndex+1, sCur, sHash, new java.sql.Timestamp(System.currentTimeMillis()), bPre.sRecipient, (String)session.getAttribute("mobile"));
//加入區塊鏈並保存到本地文件
blockchain.BlockChain.lBlockchain.add(bCur);
blockchain.BlockChain.WriteData();
。。。
}else{
。。。。
}
}
下面是經過幾個接龍後的頁面:
這是可以打開本地區塊鏈數據文件 “c:/blockchain/data.txt”可以看到如下內容,爲了查看簡單,數據文件用明文txt存儲。
其中第一條,是創世塊的數據,在區塊鏈中,它是根區塊。
在實際項目中,數據文件一般都會以某種較複雜的方式存儲,比如採用某種加密方式。
<小提示:如需源碼,請點擊這裏下載>
節點數據一致性(共識算法/一致性算法)
到目前爲止我們已經有了一個基本的區塊鏈產品和界面,並且可以做簡單的“挖礦”,在應用啓動的時候,我們也看到了第一次數據同步和後面每隔一段時間數據同步的實現方法,在一個節點上運行沒有問題。
但是,區塊鏈系統應該是分佈式的。既然是分佈式的,那麼我們就需要考慮究竟拿什麼保證所有節點有同樣的鏈呢?這就是一致性問題,我們要想在網絡上有很多個節點並且他們的數據保持一致,那就必須實現一個一致性的算法。
實現共識算法(一致性算法)
單個節點分佈在不同的計算機或手機等等終端,各自工作,必然有一些衝突產生。衝突的產生,主要來自兩方面,一是鏈長的不一致,二是鏈分叉的產生。
用本例來說明。第一種情況是,每個節點同步後,各自做各自的題目,各自的鏈數據都在擴展,導致每個人的鏈長度不一致。還有一種情況是,在某個區塊下面,存在不同的都正確的答案,那麼,即使長度一致,但鏈的內容不一致。比如在本例中“海闊天空”後面可以接的成語有很多,比如“空中樓閣”、“空前絕後”、“空口無憑”,他們都是正確的。這種情況下,系統必須定義一個唯一性的算法。
解決上面問題的算法,稱之爲“共識算法”或“一致性算法”。
通過上面的描述,我們知道“共識算法”其實是解決不同用戶(或稱不同節點)在完成被“工作量證明”算法承認的工作之後,如何公平合理的獎勵問題。他必須合理,體現公平、公正。在不同的區塊鏈應用中,在不同的“工作量證明”基礎下,其實現方法是不同的,在基於某些複雜的“工作量證明”的區塊鏈應用中,可以想象,這些複雜的“工作量證明”,會導致需要非常複雜的“共識算法”去解決衝突。
如果每個節點在增加一個新區塊的時候,都可以實時同步到每個節點,並且在同步過程中,能鎖定其它節點,當然這是最理想的。但事實上,在現實場景中,這很難做到。網上有很多共識性算法討論,大家可以查詢參考,不過很多都存在一個問題,需要中心節點支持,我覺得這個是“去中心化”不可接受的,所以非常不以爲然。
就本例子而言,可以設計一個相對簡單的“共識算法”,我們稱之爲最長鏈法,爲了解決這個問題,規定最長的、有效的鏈纔是最終的鏈,換句話說,網絡中有效最長鏈纔是實際的鏈。具體實現如下:
當一個用戶完成一個成語接龍並被系統判斷答案正確的時候,他將計劃新增一個區塊,並且希望添加到區塊鏈中,但這之前,系統會先去掃描當前所有節點目前的鏈,如果發現存在長度比當前節點加一還要長的鏈,那麼系統認爲,當前節點雖然完成了題目,但已經不是最快的那個人了,因此不再承認這次工作的有效性。並且同時同步最長鏈到本地,提示用戶重新做成語接龍。
這個算法在本例中,理論上是合理的,因爲不能說一個用戶花了一分鐘就回答出來的答案,另外一個花了一天才回答出來,當然後面的就不能說有效了。沒有更仔細的想過這個“共識算法”的合理性,作爲演示,暫時就那麼用一下了。下面說一下在本例中實現需要的兩個材料:
1、 每個節點需要一個能被掃描後提供它自己的區塊鏈數據的接口,這個其實接口已經有了,就是http:/…/blockchain/chain.jsp,回顧前面,在啓動節點的時候用到過;
2、 實現一個共識算法方法,在每次“挖礦”完成後,判斷工作的有效性。
共識算法如下:
//共識算法
//返回 true 表示當前節點工作可以被認可
public static boolean Unanimous(){
boolean bRet=true;
//掃描周邊的節點,找到最長的鏈,下載到本地
int iLastLen=0;
String sLastChain="";
for(int i=0;i<255;i+=1){
String sThisURL="http://"+sIPPre+i+":8080/blockchain/chain.jsp";
System.out.println(sThisURL);
String sChain=httpRequest(sThisURL);
if(sChain!=""){
System.out.println(sChain);
String sTemp[]=sChain.split("##");
if(sTemp.length>iLastLen){
iLastLen=sTemp.length;
sLastChain=sChain;
}
}
}
BlockChain.LoadData();
try{
//如果其它節點存在長度大於本地節點+1的鏈,則本次“挖礦”無效
if(sLastChain!="" && iLastLen >= BlockChain.lBlockchain.size()+1){
bRet=false;
FileOutputStream out = new FileOutputStream(new File(sDataFileDir+"//data.txt"));
sLastChain=sLastChain.replace("##", "\r\n");
out.write((sLastChain+"\r\n").getBytes());
out.close();
}
}catch(Exception e){
}
return bRet;
}
加入“共識算法”後的界面變成如下:
當用戶遞交一個成語後,系統會通過Ajax首先檢測區塊鏈的一致性,一致性通過了,用戶遞交的這個新的成語,纔會加入到區塊鏈。如果發現有更長的鏈條存在,則系統會提示你慢了一步,已經被搶答了,必須重新開始。
頁面大致如下:
發佈和運行區塊鏈成語接龍
到這裏,這個“區塊鏈成語接龍”遊戲就基本搭建和測試完成。
接下來的事情,你可以將此項目打包war,然後發佈到你所在局域網不同的節點的機器上,因爲環境的支持問題,所以節點必須有Windows+JDK+Tomcat運行環境,將項目打包成 blockchain.war,放到你的Tomcat目錄 webapps下面,然後啓動Tomcat,打開你的瀏覽器,輸入:http://localhost:8080/blockchain/
過程不再累述。如果局域網的環境和本地目錄結構有所改變,則需要對項目的部分地方稍作修改,比如:
//對局域網內的電腦進行掃描,找到最長的鏈,下載到本地
static final String sIPPre="10.70.5.";
//本地存儲路徑
static final String sDataFileDir="c://blockchain";
好啦,你可以邀請你的同事和朋友們一起來測試你的區塊鏈成語接龍!
<小提示:如需源碼,請點擊這裏下載>
總結:
通過搭建一個區塊鏈項目,我們可以瞭解一個區塊鏈項目啓動所需要的所有知識,環境和其基本算法所代表的概念,在例子中,我們一一講解並且實現,我們可以非常具體地體會到“去中心化”的真實含義。事實上,可以在測試過程中,加入很多終端,然後,關閉中間任何一臺終端,看這個產品是否還是可以正常運行。
雖然如此,在這個過程中,也可以看到有些問題並沒有很好和徹底的解決方案,區塊鏈作爲一個新起的概念和技術,這個當然也可以理解,同樣,這個可以作爲我們研究人員進一步仔細考慮的點。
1、 關於種子節點的問題
在任何一個節點啓動的時候,如何獲得其它節點的位置信息,從而下載和初始化區塊鏈呢?種子節點的存在,的確可以解決這個問題,但種子節點本身,就帶有“中心化”這個概念,所以,如果沒有種子節點會更加好,但如何實現呢?
2、 關於“工作量證明”
一個技術是否能普及和商業化,或者說對用戶對社會是否有真正的價值,就在於它能不能解決我們具體生活場景的問題。區塊鏈裏面具體所做的事情,都在“工作量證明”裏面,當前很多區塊鏈項目,比如“比特幣”,都是用一個計算機化的方式,來描述和做某一種工作,但這類工作的意義究竟有多大,或者其適用性有多大,真的需要思考。這種方式用來產生“虛擬幣”是不錯,但“虛擬幣”有真的價值麼?還有就是,真的所有工作,都可以不需要“中心節點”支持麼?所以,設計更多的符合人們生活場景的“工作量證明”算法,將大大拓展這個技術的前景。
3、 關於“共識算法”
“共識算法”和“工作量證明”慼慼相關,但所有“共識算法”都需要考慮解決起碼兩個問題,當各個節點獨自運作的時候,不可避免會產生每個節點區塊鏈不同步的問題,不同步可能在時間上,也可能在“工作量證明”的多重結果上,這兩種,需要最終達成共識,使得系統中始終只有一條正確的鏈,應該說是相當困難。網絡有延遲,終端有差異,不同的結果都合理,怎麼公平取捨。。。等等。實在不是那麼簡單的。
4、 關於巨量區塊鏈數據同步速度
即使解決了上面1,2,3 提出的問題,也存在數據同步速度問題。
理論上來說,區塊鏈每個節點,應該是數據的全備份。在我們這個例子中,同步數據量比較少,同步節點也不多,所以每次同步時間很短。但是在商用系統中,如果出現大量的節點,大量的數據,這個同步肯定會有時間。一個是節點存儲容量問題,一個是同步帶寬問題,兩者都是不可預料的。目前基於區塊鏈的智能合約開發框架以太訪(Ethereum)需要同步的數據已經接近了驚人的100G,還在不斷增長,我下載安裝一個同步了一個星期。在如此大的數據兩下,簡單的同步和拷貝,肯定是不合理的,既會帶來巨大的資源浪費,也會帶來應用的可使用性及差。所以這裏面需要解決的問題還很多。
以上這些,任何一點,仔細研究都是一個巨大的課題,做一個簡單那的區塊鏈項目不難,要做好並且做成社會真正有用而非炒作的產品,估計還有很遠的路,具體想來都是需要好好解決的問題。讓我們一起努力吧。!
<小提示:如需源碼,請點擊這裏下載>