前面兩章說(shuō)了許多關(guān)于區(qū)塊鏈的理論知識(shí),這一章我們將會(huì)將前面的所有的理論知識(shí)結(jié)合起來(lái)妄荔,用java簡(jiǎn)答實(shí)現(xiàn)一套區(qū)塊鏈泼菌。
以下內(nèi)容轉(zhuǎn)自:https://github.com/longfeizheng/blockchain-java
基于面向?qū)ο蟮乃枷耄紫?/p>
1啦租、定義區(qū)塊鏈的類(lèi)塊
import java.util.Date;
public class Block {
public String hash;//區(qū)塊Hash
public String previousHash;//前導(dǎo)Hash
private String data; //可以理解為前面講到的區(qū)塊結(jié)構(gòu)里的區(qū)塊體里打包的交易數(shù)據(jù)
private long timeStamp; //時(shí)間戳
//Block Constructor.
public Block(String data,String previousHash ) {
this.data = data;
this.previousHash = previousHash;
this.timeStamp = new Date().getTime();
}
}
2哗伯、創(chuàng)建數(shù)字簽名
創(chuàng)建一個(gè)StringUtil,封裝了sha256方法篷角,方便后面調(diào)用焊刹,因?yàn)檫@是一個(gè)工具類(lèi),所以我們可以不關(guān)心它的具體實(shí)現(xiàn)方式恳蹲,只要知道它是sha256的java實(shí)現(xiàn)即可虐块。
import java.security.MessageDigest;
public class StringUtil {
//Applies Sha256 to a string and returns the result.
public static String applySha256(String input){
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256"); ? ? ? ?
//Applies sha256 to our input,
byte[] hash = digest.digest(input.getBytes("UTF-8")); ? ? ? ?
StringBuffer hexString = new StringBuffer(); // This will contain hash as hexidecimal
for (int i = 0; i < hash.length; i++) {
String hex = Integer.toHexString(0xff & hash[i]);
if(hex.length() == 1) hexString.append('0');
hexString.append(hex);
}
return hexString.toString();
}
catch(Exception e) {
throw new RuntimeException(e);
}
}
}
接下來(lái)讓我們?cè)贐lock類(lèi)中應(yīng)用
方法 applySha256
方法,其主要的目的就是計(jì)算hash值阱缓,我們計(jì)算的hash值應(yīng)該包括區(qū)塊中所有我們不希望被惡意篡改的數(shù)據(jù)非凌,再加上preHash,data和timeStamp荆针,這在上面已經(jīng)演示過(guò)了敞嗡,是一套固定公式颁糟,所以這里沒(méi)有什么爭(zhēng)議。
//簡(jiǎn)化了計(jì)算方式
public String calculateHash() {
String calculatedhash = StringUtil.applySha256(
previousHash +
Long.toString(timeStamp) +
data
);
return calculatedhash;
}
然后把這個(gè)方法加入到Block的構(gòu)造函數(shù)中去
public Block(String data,String previousHash ) {
this.data = data;
this.previousHash = previousHash;
this.timeStamp = new Date().getTime();
this.hash = calculateHash();
}
3喉悴、測(cè)試
在主方法中讓我們創(chuàng)建一些區(qū)塊棱貌,并把其hash值打印出來(lái),來(lái)看看是否一切都在我們的掌控中箕肃。
第一個(gè)塊稱為創(chuàng)世紀(jì)區(qū)塊婚脱,因?yàn)樗穷^區(qū)塊,所以我們只需輸入“0”作為前一個(gè)塊的previous hash勺像。
public class NoobChain {
public static void main(String[] args) {
Block genesisBlock = new Block("Hi im the first block", "0");
System.out.println("Hash for block 1 : " + genesisBlock.hash);
Block secondBlock = new Block("Yo im the second block",genesisBlock.hash);
System.out.println("Hash for block 2 : " + secondBlock.hash);
Block thirdBlock = new Block("Hey im the third block",secondBlock.hash);
System.out.println("Hash for block 3 : " + thirdBlock.hash);
}
}
打诱厦场:
Hash for block 1: f6d1bc5f7b0016eab53ec022db9a5d9e1873ee78513b1c666696e66777fe55fb
Hash for block 2: 6936612b3380660840f22ee6cb8b72ffc01dbca5369f305b92018321d883f4a3
Hash for block 3: f3e58f74b5adbd59a7a1fc68c97055d42e94d33f6c322d87b29ab20d3c959b8f
每一個(gè)區(qū)塊都必須要有自己的數(shù)據(jù)簽名即hash值,這個(gè)hash值依賴于自身的信息(data)和上一個(gè)區(qū)塊的數(shù)字簽名(previousHash)吟宦,但這個(gè)還不是區(qū)塊鏈篮洁,下面讓我們存儲(chǔ)區(qū)塊到數(shù)組中,這里我會(huì)引入gson包殃姓,目的是可以用json方式查看整個(gè)一條區(qū)塊鏈結(jié)構(gòu)袁波。
import java.util.ArrayList;
import com.google.gson.GsonBuilder;
public class NoobChain {
public static ArrayList<Block> blockchain = new ArrayList<Block>();
public static void main(String[] args) {
//add our blocks to the blockchain ArrayList:
blockchain.add(new Block("Hi im the first block", "0"));
blockchain.add(new Block("Yo im the second block",blockchain.get(blockchain.size()-1).hash));
blockchain.add(new Block("Hey im the third block",blockchain.get(blockchain.size()-1).hash));
String blockchainJson = new GsonBuilder().setPrettyPrinting().create().toJson(blockchain);
System.out.println(blockchainJson);
}
}這樣的輸出結(jié)構(gòu)就更類(lèi)似于我們所期待的區(qū)塊鏈的樣子。
4蜗侈、檢查區(qū)塊鏈的完整性
在主方法中增加一個(gè)isChainValid()方法篷牌,目的是循環(huán)區(qū)塊鏈中的所有區(qū)塊并且比較hash值,這個(gè)方法用來(lái)檢查hash值是否是于計(jì)算出來(lái)的hash值相等踏幻,同時(shí)previousHash值是否和前一個(gè)區(qū)塊的hash值相等枷颊。(第二章講到過(guò),驗(yàn)證區(qū)塊的有效性即重新計(jì)算區(qū)塊的Hash值并與鏈上記錄的區(qū)塊Hash值是否一致该面,而上一個(gè)區(qū)塊的Hash值—preHash偷卧,又影響了當(dāng)前Hash的計(jì)算)
所以我會(huì)的驗(yàn)證方法就會(huì)這么寫(xiě):
public static Boolean isChainValid() {
Block currentBlock;
Block previousBlock;
//遍歷整個(gè)區(qū)塊鏈
for(int i=1; i < blockchain.size(); i++) {
currentBlock = blockchain.get(i);
previousBlock = blockchain.get(i-1);
//當(dāng)重新計(jì)算區(qū)塊Hash發(fā)現(xiàn)和鏈上記錄的不相等時(shí)
if(!currentBlock.hash.equals(currentBlock.calculateHash()) ){
System.out.println("Current Hashes not equal");
return false;
}
//當(dāng)上一個(gè)區(qū)塊上的Hash和當(dāng)前區(qū)塊記錄的preHash不相等時(shí)
if(!previousBlock.hash.equals(currentBlock.previousHash) ) {
System.out.println("Previous Hashes not equal");
return false;
}
}
return true;
}
任何區(qū)塊鏈中區(qū)塊的一絲一毫改變都會(huì)導(dǎo)致這個(gè)函數(shù)返回false,也就證明了區(qū)塊鏈無(wú)效了吆倦。
5、挖礦
這里我們要求挖礦者做工作量證明坐求,具體的方式是在區(qū)塊中嘗試不同的參數(shù)值直到它的hash值是從一系列的0開(kāi)始的蚕泽。讓我們添加一個(gè)名為nonce的int類(lèi)型以包含在我們的calculatehash()方法中,以及需要的mineblock()方法
public class Block {
public String hash;
public String previousHash;
private String data; //our data will be a simple message.
private long timeStamp; //as number of milliseconds since 1/1/1970.
private int nonce;
//區(qū)塊的構(gòu)造?
public Block(String data,String previousHash ) {
this.data = data;
this.previousHash = previousHash;
this.timeStamp = new Date().getTime();
this.hash = calculateHash();
}
//基于交易數(shù)據(jù)信息和其他數(shù)據(jù)生成當(dāng)前區(qū)塊的hash
public String calculateHash() {
String calculatedhash = StringUtil.applySha256(
previousHash +
Long.toString(timeStamp) +
Integer.toString(nonce) +
data
);
return calculatedhash;
}
public void mineBlock(int difficulty) {
String target = new String(new char[difficulty]).replace('\0', '0');? ? ? ? //計(jì)算出的Hash要求前面多少個(gè)0(理論上前面的0要求越多代表挖礦難度越大)
while(!hash.substring( 0, difficulty).equals(target)) {
nonce ++;
hash = calculateHash();
}
System.out.println("Block Mined!!! : " + hash);
}
}
mineBlock()方法中引入了一個(gè)int值稱為difficulty難度桥嗤,低的難度比如1和2须妻,普通的電腦基本都可以馬上計(jì)算出來(lái),我的建議是在4-6之間進(jìn)行測(cè)試泛领,普通電腦大概會(huì)花費(fèi)3秒時(shí)間荒吏,在萊特幣中難度大概圍繞在442592左右,而在比特幣中每一次挖礦都要求大概在10分鐘左右渊鞋,當(dāng)然根據(jù)所有網(wǎng)絡(luò)中的計(jì)算能力绰更,難度也會(huì)不斷的進(jìn)行修改瞧挤。
我們?cè)贜oobChain類(lèi) 中增加difficulty這個(gè)靜態(tài)變量。
public static int difficulty = 5;
這樣我們必須修改主方法中讓創(chuàng)建每個(gè)新區(qū)塊時(shí)必須觸發(fā)mineBlock()方法儡湾,而isChainValid()方法用來(lái)檢查每個(gè)區(qū)塊的hash值是否正確特恬,整個(gè)區(qū)塊鏈?zhǔn)欠袷怯行У摹?/p>
public class NoobChain {
public static ArrayList<Block> blockchain = new ArrayList<Block>();
public static int difficulty = 5;
public static void main(String[] args) {
//手動(dòng)創(chuàng)建幾個(gè)區(qū)塊
blockchain.add(new Block("Hi im the first block", "0"));
System.out.println("Trying to Mine block 1... ");
blockchain.get(0).mineBlock(difficulty);
blockchain.add(new Block("Yo im the second block",blockchain.get(blockchain.size()-1).hash));
System.out.println("Trying to Mine block 2... ");
blockchain.get(1).mineBlock(difficulty);
blockchain.add(new Block("Hey im the third block",blockchain.get(blockchain.size()-1).hash));
System.out.println("Trying to Mine block 3... ");
blockchain.get(2).mineBlock(difficulty);
System.out.println("\nBlockchain is Valid: " + isChainValid());
String blockchainJson = new GsonBuilder().setPrettyPrinting().create().toJson(blockchain);
System.out.println("\nThe block chain: ");
System.out.println(blockchainJson);
}
public static Boolean isChainValid() {
Block currentBlock;
Block previousBlock;
String hashTarget = new String(new char[difficulty]).replace('\0', '0');
//遍歷區(qū)塊,驗(yàn)證有效性
for(int i=1; i < blockchain.size(); i++) {
currentBlock = blockchain.get(i);
previousBlock = blockchain.get(i-1);
//當(dāng)重新計(jì)算區(qū)塊Hash發(fā)現(xiàn)和鏈上記錄的不相等時(shí)
if(!currentBlock.hash.equals(currentBlock.calculateHash()) ){
System.out.println("Current Hashes not equal");
return false;
}
//當(dāng)上一個(gè)區(qū)塊上的Hash和當(dāng)前區(qū)塊記錄的preHash不相等時(shí)
if(!previousBlock.hash.equals(currentBlock.previousHash) ) {
System.out.println("Previous Hashes not equal");
return false;
}
//這里涉及到的其實(shí)就挖礦算法徐钠,通過(guò)不斷變化的nonce值來(lái)生成Hash值癌刽,如果生成的Hash值的前幾位的都是0且和target要求的位數(shù)一致,代表這個(gè)隨機(jī)數(shù)生成的區(qū)塊是有效的尝丐。
if(!currentBlock.hash.substring( 0, difficulty).equals(hashTarget)) {
System.out.println("This block hasn't been mined");
return false;
}
}
return true;
}
}
打酉园荨:
Trying to Mine block 1...
Block Mined!!! : 0000016667d4240e9c30f53015310b0ec6ce99032d7e1d66d670afc509cab082
Trying to Mine block 2...
Block Mined!!! : 000002ea55735bea4cac7e358c7b0d8d81e8ca24021f5f85211bf54fd4ac795a
Trying to Mine block 3...
Block Mined!!! : 000000576987e5e9afbdf19b512b2b7d0c56db0e6ca49b3a7e638177f617994b
Blockchain is Valid: true
[
? {
? ? "hash": "0000016667d4240e9c30f53015310b0ec6ce99032d7e1d66d670afc509cab082",
? ? "previousHash": "0",
? ? "data": "first",
? ? "timeStamp": 1520659506042,
? ? "nonce": 618139
? },
? {
? ? "hash": "000002ea55735bea4cac7e358c7b0d8d81e8ca24021f5f85211bf54fd4ac795a",
? ? "previousHash": "0000016667d4240e9c30f53015310b0ec6ce99032d7e1d66d670afc509cab082",
? ? "data": "second",
? ? "timeStamp": 1520659508825,
? ? "nonce": 1819877
? },
? {
? ? "hash": "000000576987e5e9afbdf19b512b2b7d0c56db0e6ca49b3a7e638177f617994b",
? ? "previousHash": "000002ea55735bea4cac7e358c7b0d8d81e8ca24021f5f85211bf54fd4ac795a",
? ? "data": "third",
? ? "timeStamp": 1520659515910,
? ? "nonce": 1404341
? }
]
經(jīng)過(guò)測(cè)試增加一個(gè)新的區(qū)塊即挖礦必須花費(fèi)一定時(shí)間,大概是3秒左右爹袁,你可以提高difficulty難度來(lái)看远荠,它是如何影響數(shù)據(jù)難題所花費(fèi)的時(shí)間的
如果有人在你的區(qū)塊鏈系統(tǒng)中惡意篡改數(shù)據(jù):
他們的區(qū)塊鏈?zhǔn)菬o(wú)效的。
他們無(wú)法創(chuàng)建更長(zhǎng)的區(qū)塊鏈
網(wǎng)絡(luò)中誠(chéng)實(shí)的區(qū)塊鏈會(huì)在長(zhǎng)鏈中更有時(shí)間的優(yōu)勢(shì)
因?yàn)榇鄹牡膮^(qū)塊鏈將無(wú)法趕上長(zhǎng)鏈和有效鏈呢簸,除非他們比你網(wǎng)絡(luò)中所有的節(jié)點(diǎn)擁有更大的計(jì)算速度矮台,可能是未來(lái)的量子計(jì)算機(jī)或者是其他什么。
如果你耐心的看到這里了根时,恭喜瘦赫,我們已經(jīng)基本創(chuàng)建了屬于自己的區(qū)塊鏈
它有:
有很多區(qū)塊組成用來(lái)存儲(chǔ)數(shù)據(jù)
有數(shù)字簽名讓你的區(qū)塊鏈鏈接在一起
需要挖礦的工作量證明新的區(qū)塊
可以用來(lái)檢查數(shù)據(jù)是否是有效的同時(shí)是未經(jīng)篡改的
結(jié)語(yǔ):
坦白說(shuō),這篇文章更多的是從代碼講解的角度來(lái)剖析區(qū)塊鏈的工作原理蛤迎,為的是讓大家更好的吸收前面所講解的知識(shí)點(diǎn)确虱,雖然有一些偽代碼的成分,但是我們確實(shí)完成了區(qū)塊鏈所有應(yīng)該有的功能替裆。當(dāng)然校辩,真正的區(qū)塊鏈實(shí)現(xiàn)遠(yuǎn)不止這么一點(diǎn),大量的實(shí)現(xiàn)細(xì)節(jié)值得我們?nèi)パ芯苛就⑻接懞蛯?shí)現(xiàn)宜咒,更多的我們后續(xù)會(huì)再講到。