區塊鏈源碼完整可運行(基于Java開發一套完整的區塊鏈系統(附完整源碼))
前言
近幾年區塊鏈概念越來越火,特別是區塊鏈技術被納入國家基礎設施建設名單后,各大企業也開始招兵買馬,對區塊鏈技術進行研究,從各大招聘網站的區塊鏈職位來看,薪資待遇都很不錯,月薪30K到80K的都有,這對于我們程序員來說也是一種機遇,說明學習區塊鏈技術刻不容緩。
我個人從2016年就開始在做區塊鏈系統開發的相關工作,最近身邊很多朋友都在找我,想讓我給他們講講區塊鏈技術開發的相關知識,介于此,索性我就手擼了一套簡單的java區塊鏈系統,并寫了一份詳細的開發教程,發布到博客,希望能夠對熱愛區塊鏈技術的小伙伴學習入門有所幫助。
這套區塊鏈系統代碼非常簡潔清晰,對于新手來說非常好理解,旨在告訴大家其實區塊鏈技術并沒有那么高深復雜。系統中除了springboot框架外,其他基本都是純原生開發,就連P2P網絡也是用的java socket來實現的。
文末有本文完整源碼鏈接。
一、區塊鏈技術理論基礎
1、基本概念
(1)區塊鏈
從技術層面來看,區塊鏈是由包含交易信息的區塊按照時間順序從后向前有序鏈接起來的數據結構。
從應用層面來說,區塊鏈是一個分布式的共享賬本和數據庫,具有去中心化、不可篡改、全程留痕、集體維護、公開透明等特點。基于這些特點,區塊鏈技術可以開發出自帶信任體系特征的系統,實現多個主體之間的協作信任與一致行動。
區塊是區塊鏈中的最小組成單位,主要有包含元數據的區塊頭和存放一條或者多條交易信息的區塊體兩部分組成,每個區塊都記錄著當前區塊的哈希和上一個區塊的哈希,通過兩個哈希值的關聯,讓所有的區塊以鏈式結構串起來,就形成了一個完整的區塊鏈。
區塊鏈中的第一個區塊被稱作為創世區塊,無需關聯上一個區塊。以BTC網絡為例,每個區塊主要包含如下信息字段:
區塊大小:用字節表示的區塊數據大小
區塊頭:組成區塊頭的包括以下幾個字段:
1. 區塊頭hash值2. 父區塊頭hash值3. 時間戳:區塊產生的近似時間4. Merkle根:該區塊中交易的merkle樹根的哈希值5. 難度目標:該區塊工作量證明算法的難度目標6. Nonce:用于工作量證明算法的計數器
交易計數器:交易的數量
交易:記錄在區塊里的交易信息
區塊鏈結構的簡易模型,如下圖所示:
區塊中的交易集合記錄的是一些特定的信息,在BTC網絡中主要記錄的是交易信息,在其他區塊鏈網絡中可以按照業務邏輯來保存相應的業務數據,如審計信息、版權信息、票據信息等,這也是區塊鏈經常用來當做共享賬本的原因。
打個比方,可以把區塊鏈當做一個用來記賬的筆記本,一個區塊就相當于一頁紙,上面記錄了某一時間段內所有的賬務信息,從第一頁到最后一頁,按照頁碼順序排列起來就是一個完整的賬本。
(2)區塊鏈網絡
實際的區塊鏈系統由多個區塊鏈節點組成,每個節點都運行著相同一套區塊鏈主干網絡的副本,且各個節點間通過P2P網絡進行交互,并最終形成一個完整的區塊鏈網絡系統。
P2P網絡具有可靠性、去中心化,以及開放性,各個節點之間交互運作、協同處理,每個節點在對外提供服務的同時也使用網絡中其他節點所提供的服務。當某一個區塊鏈節點產生新的區塊時,會通過廣播的方式告訴其他節點,其他節點通過網絡接收到該區塊信息時,會對這個區塊信息進行驗證,當有一定數量的節點都驗證通過后,各個節點會把該區塊更新到各自現有的區塊鏈上,最終使得整個區塊鏈網絡中的各個節點信息保持一致,這也是區塊鏈去中心化、可信任特性的體現。
區塊鏈網絡簡易模型,如下圖所示:
2、區塊鏈分類
(1)公有鏈
公有區塊鏈(Public Block Chains)是指:世界上任何個體或者團體都可以發送交易,且交易能夠獲得該區塊鏈的有效確認,任何人都可以參與使用和維護該區塊鏈,信息公開透明。公有區塊鏈是最早的區塊鏈,例如BTC、以太坊等虛擬數字貨幣均基于公有區塊鏈。不過目前公有鏈實際應用價值不大,并沒有產生特別合適的應用場景。
(2)聯盟鏈
行業區塊鏈(Consortium Block Chains):由某個群體內部指定多個預選的節點為記賬人,每個塊的生成由所有的預選節點共同決定(預選節點參與共識過程),其他接入節點可以參與交易,但有權限限制,信息受保護,如銀聯組織。目前聯盟鏈是各個區塊鏈技術團隊主要研究的對象,由于聯盟鏈擁有區塊鏈技術的大部分特征,并且在權限管理、數據安全、監管方面更有優勢,是企業優先考慮的區塊鏈技術方案。
市面上也有一些比較主流的聯盟鏈技術框架,讓開發維護聯盟鏈更加便捷。國內一些大的軟件廠商也都有自己的企業區塊鏈技術解決方案,例如螞蟻金服區塊鏈平臺,騰訊的TrustSQL平臺,東軟的SaCa EchoTrust區塊鏈應用平臺以及京東區塊鏈防偽追溯平臺等等。
(3)私有鏈
私有區塊鏈(Private Block Chains):僅僅使用區塊鏈的總賬技術進行記賬,可以是一個公司,也可以是個人,獨享該區塊鏈的寫入權限,利用區塊鏈的不易篡改特性,把區塊鏈作為賬本數據庫來使用。
3、關鍵技術與特性
(1)共識機制
共識機制被稱作為區塊鏈系統的靈魂,是區塊鏈系統信任體系的基礎。區塊鏈系統作為一個多節點的分布式賬本系統,當有新的信息需要記錄時,哪個節點來負責記賬,記賬獎勵發放給哪個節點,哪些節點負責驗證記賬結果,如何讓各個節點達成最終一致,將記賬結果被網絡中所有節點以同樣的順序復制并記錄下來,就是共識機制要做的事情。
而按照百度百科上的說法:
所謂“共識機制”是通過特殊節點的投票,在很短的時間內完成對交易的驗證和確認,對一筆交易,如果利益不相干的若干個節點能夠達成共識,我們就可以認為全網對此也能夠達成共識。再通俗一點來講,如果中國一名微博大V、美國一名虛擬幣玩家、一名非洲留學生和一名歐洲旅行者互不相識,但他們都一致認為你是個好人,那么基本上就可以斷定你這人還不壞。
目前,較為主流的共識算法有PoW、PoS、DPoS、PBFT等,在實際使用時,每種算法都有各自的優點和缺點。在應用于不同場景時,區塊鏈項目將會采用不同的共識機制和算法。
(2)去中心化
去中心化,是互聯網發展過程中形成的社會關系形態和內容產生形態,是相對于“中心化”而言的新型網絡內容生產過程。在一個分布有眾多節點的區塊鏈系統中,每個節點都具有高度自治的特征。任何一個節點都可能成為階段性的中心,但不具備強制性的中心控制功能。節點與節點之間的影響,會通過網絡而形成關聯關系。這種開放式、扁平化、平等性的系統現象或結構,我們稱之為去中心化。
去中心化的系統具有容錯力高、抗攻擊力強的特征。中心化的系統一旦中心出現問題,整個系統都會崩潰,但是區塊鏈系統中的任何一個節點出現問題,并不會對整個區塊鏈網絡產生太大的影響。
另外,去中介化并不代表著不接受監管,“去中心化”去的是中央控制方和中介方,而不是監管方。監管節點可以方便地接入任何一個區塊鏈網絡。并且由于區塊鏈的公開透明特性,監管機構反而可以更加方便地監控整個系統的交易數據。
(3)智能合約
從技術層面講,智能合約是一段部署在在區塊鏈上的程序代碼,當滿足程序設定的條件時,它便會在區塊鏈上運行,并得到相應的結果。這種情況有點類似于微信的小程序,區塊鏈提供虛擬機和腳本語言,用戶根據腳本語言的語法開發帶有一定業務邏輯的程序,部署在區塊鏈上,當滿足執行的條件時,智能合約便會被區塊鏈虛擬機解釋并運行。
典型的應用便是以太坊平臺的智能合約,在這個平臺里可以支持用戶通過簡單的幾行代碼就能實現他們想要的合約,實現無需人為監督的、不可篡改、自動化運行的合約,買賣房子不需要再找中介、借錢不需要再找公證人……人們可以隨時隨地根據自身需求發起合約,它的執行不依賴某個人和組織,所有的信任完全基于以太坊區塊鏈平臺本身。
(4)不可篡改性
大部分人習慣稱它為不可篡改性,但是從技術層面來說,我個人覺得叫做不可逆轉性更貼切,既然是一個計算機系統,增刪改查是基本的功能屬性,只不過區塊鏈系統刪除和修改操作比較特殊一點。
區塊鏈是由每個區塊的哈希值串連起來的鏈式結構,而區塊的哈希值=SHA256(“當前區塊內容+上一個區塊的哈希值”),任何一個區塊的內容發生修改,都會引起哈希值的變化,而哈希值的變化也會引起子區塊哈希值發生變化,進而引起整個區塊鏈的改變。
因此任何人想要修改區塊的數據幾乎是不可能的,除非他把整個區塊鏈中從創世區塊到最新的區塊的所有哈希值全部重新修改一遍,并且修改完之后,還得廣播告訴網絡中的其他所有節點,讓其他所有節點接受修改。
不過按照目前計算機的算力,想要在短時間內從區塊鏈頭部到尾部全部修改一遍,是一件非常困難的事,并且即使修改完了,其他節點也不會接受修改,因為憑一己之力,沒有能夠讓所有節點達成共識的條件。
4、流行的區塊鏈框架與應用
(1)公有鏈應用:BTC網絡
區塊鏈1.0產品,對于比特幣,中本聰是這樣定義的:是一種完全通過點對點技術實現的電子現金系統,它使得在線支付能夠直接由一方發起并支付給另外一方,中間不需要通過任何的金融機構。
與所有的貨幣不同,比特幣不依靠特定貨幣機構發行,它依據特定算法,通過大量的計算產生,比特幣經濟使用整個P2P網絡中眾多節點構成的分布式數據庫來確認并記錄所有的交易行為,并使用密碼學的設計來確保貨幣流通各個環節安全性。之后人們根據比特幣網絡技術整理出了區塊鏈技術體系,去解決信任的問題,而比特幣網絡原理也成為了區塊鏈技術初學者的經典教材。
(2)公有鏈應用:以太坊網絡
區塊鏈2.0產品的代表,以太坊是一個為去中心化應用(Dapp)而生的開源區塊鏈平臺,擁有著大部分區塊鏈技術的特征,但與其它區塊鏈不同的是,以太坊是可編程的,開發者可以用它來構建不同的應用程序,通過其專用加密貨幣以太幣(簡稱“ETH”)提供去中心化的以太虛擬機(Ethereum Virtual Machine)來處理點對點合約(就是一些腳本程序代碼)。如果把比特幣網絡看作是一套分布式的數據庫,而以太坊則更進一步,它可以看作是一臺分布式的計算機:區塊鏈是計算機的ROM,合約是程序,而以太坊的礦工們則負責計算,擔任CPU的角色。
以太坊的概念首次在2013至2014年間由程序員Vitalik Buterin受比特幣啟發后提出,大意為“下一代加密貨幣與去中心化應用平臺”。雖然以太坊作為平臺可以在其上開發新的應用,但是由于以太坊的運行和BTC網絡一樣,采用的是Token機制,且平臺性能不足,經常出現網絡擁堵的情況,平臺用來學習開發與測試區塊鏈技術還可以,用于實際生產的話不太現實。
(3)聯盟鏈開發框架:Hyperledger Fabric
Hyperledger Fabric 也叫超級賬本,它是 IBM 貢獻給 Linux 基金會的商用分布式賬本,是面向企業應用的全球最大的分布式開源項目。像其他區塊鏈技術一樣,它也有一個賬本,可以使用智能合約。Fabric的智能合約可以有多種架構,它可以用主流語言編程,例如Go、Java和Javascript,此外也可以使用Solidity。
至今,Fabric已獲得了阿里巴巴、AWS、Azure、百度、谷歌、華為、IBM、甲骨文、騰訊等互聯網巨頭的支持。許多企業的區塊鏈平臺都把Fabric作為底層框架來使用,例如甲骨文。不過由于IBM對區塊鏈的定義強調了區塊鏈的分布式和不可變兩個元素,對共識機制進行了削弱,采用了Kafka和zookeeper的“排序服務”實現共識,因此部分業內人士也稱超級賬本是“偽區塊鏈”,但是即便如此,也抵擋不了企業對超級賬本的喜愛,目前Fabric 2.0版本已經正式發布。
(4)小結
目前公有鏈在實際應用中并沒有太多的業務場景落地,大部分都是以挖礦為主題或者線上寵物飼養的游戲為主,并且由于數字貨幣的匿名性,有些不法分子利用這一特點,將數字貨幣用于洗錢、暗網買賣等違法行為,是各個國家的打擊對象,我國政策法規也嚴厲禁止,因此對于技術人員來說,公有鏈可以作為研究學習的對象,其他方面暫時沒有太多實際意義。
目前大部分區塊鏈企業的研究方向主要是針對企業的聯盟鏈和私有鏈,并且國家層面也在大力支持區塊鏈技術的發展,特別是區塊鏈底層核心技術的研發,倡導把區塊鏈作為核心技術自主創新的重要突破口,明確主攻方向,加大投入力度,著力攻克一批關鍵核心技術,加快推動區塊鏈技術和產業創新發展。不過現在市面上主流的區塊鏈平臺大部分還是以國外公司主導的為主,國內區塊鏈底層核心技術的發展,還需要技術人員的加倍努力。
二、區塊鏈技術Java實現
1、區塊鏈技術架構
目前主流的區塊鏈技術架構主要分為五層,數據層是最底層的技術,主要實現了數據存儲、賬戶信息、交易信息等模塊,數據存儲主要基于Merkle樹,通過區塊的方式和鏈式結構實現,而賬戶和交易基于數字簽名、哈希函數和非對稱加密技術等多種密碼學算法和技術,來保證區塊鏈中數據的安全性。
網絡層主要實現網絡節點的連接和通訊,又稱點對點技術,各個區塊鏈節點通過網絡進行通信。共識層是通過共識算法,讓網絡中的各個節點對全網所有的區塊數據真實性正確性達成一致,防止出現拜占庭攻擊、51攻擊等區塊鏈共識算法攻擊。
激勵層主要是實現區塊鏈代幣的發行和分配機制,是公有鏈的范疇,我們不做分析。應用層一般把區塊鏈系統作為一個平臺,在平臺之上實現一些去中心化的應用程序或者智能合約,平臺提供運行這些應用的虛擬機。
接下來我們基于Java語言來開發一套小型的區塊鏈系統,來實現數據層、網絡層、共識層的一些功能,用簡單的代碼來直觀抽象的概念,以便加深對以上區塊鏈技術基礎理論的理解。
2、基于java的區塊鏈開發實戰
(1)開發環境
開發工具 | VSCode |
開發語言 | Java |
JDK版本 | JDK1.8或者OpenJDK11 |
開發框架 | SpringBoot2.2.1 |
工程管理 | Maven3.6 |
測試工具 | Postman |
(2)區塊鏈基本模型構建
區塊是區塊鏈系統的最小單元,第一步我們先實現最簡單的區塊結構,新建Block.java類,主要包含以下幾個字段:
Block.java
/** * 區塊結構 * * @author Jared Jia * */public class Block implements Serializable { private static final long serialVersionUID = 1L; /** * 區塊索引號(區塊高度) */ private int index; /** * 當前區塊的hash值,區塊唯一標識 */ private String hash; /** * 前一個區塊的hash值 */ private String previousHash; /** * 生成區塊的時間戳 */ private long timestamp; /** * 工作量證明,計算正確hash值的次數 */ private int nonce; /** * 當前區塊存儲的業務數據集合(例如轉賬交易信息、票據信息、合同信息等) */ private List<Transaction> transactions; /*** 省略get set方法****/ }
區塊鏈是由區塊按照區塊哈希前后順序串聯起來的數據結構,哈希值通過散列算法對區塊進行二次哈希計算而得到的數字摘要信息(不了解散列函數的,可以先百度了解一下SHA算法),用于保證區塊的信息安全以及整條區塊鏈的有效性。因此第二步我們新增計算區塊Hash值的方法,采用SHA256算法,通過java實現:
CryptoUtil.java
/** * 密碼學工具類 * * @author Jared Jia * */public class CryptoUtil { /** * SHA256散列函數 * @param str * @return */ public static String SHA256(String str) { MessageDigest messageDigest; String encodeStr = ""; try { messageDigest = MessageDigest.getInstance("SHA-256"); messageDigest.update(str.getBytes("UTF-8")); encodeStr = byte2Hex(messageDigest.digest()); } catch (Exception e) { System.out.println("getSHA256 is error" + e.getMessage()); } return encodeStr; } private static String byte2Hex(byte[] bytes) { StringBuilder builder = new StringBuilder(); String temp; for (int i = 0; i < bytes.length; i++) { temp = Integer.toHexString(bytes[i] & 0xFF); if (temp.length() == 1) { builder.append("0"); } builder.append(temp); } return builder.toString(); }}
第三步,創建一個鏈式結構對象,按照先后順序來保存區塊對象,從來形成一個有序的區塊鏈表,考慮到線程安全問題,采用CopyOnWriteArrayList來實現,為了方便測試,暫且把區塊鏈結構保存在本地緩存中,實際的區塊鏈網絡最終會實現持久層的功能,把區塊鏈數據保存至數據庫中,例如BTC核心網絡采用的是K-V數據庫LevelDB:
BlockCache.java
public class BlockCache { /** * 當前節點的區塊鏈結構 */ private List<Block> blockChain = new CopyOnWriteArrayList<Block>(); public List<Block> getBlockChain() { return blockChain; } public void setBlockChain(List<Block> blockChain) { this.blockChain = blockChain; } }
第四步,有了區塊鏈結構后,需要新增向區塊鏈中添加區塊的方法,同時每次添加區塊的時候,我們需要驗證新區塊的有效性,例如Hash值是否正確,新區塊中上一區塊的Hash屬性的值,與上一區塊的Hash值是否相等。
另外,區塊鏈中必須有個創世區塊,我們直接通過硬編碼實現:
BlockService.java
/** * 區塊鏈核心服務 * * @author Jared Jia * */@Servicepublic class BlockService { @Autowired BlockCache blockCache; /** * 創建創世區塊 * @return */ public String createGenesisBlock() { Block genesisBlock = new Block(); //設置創世區塊高度為1 genesisBlock.setIndex(1); genesisBlock.setTimestamp(System.currentTimeMillis()); genesisBlock.setNonce(1); //封裝業務數據 List<Transaction> tsaList = new ArrayList<Transaction>(); Transaction tsa = new Transaction(); tsa.setId("1"); tsa.setBusinessInfo("這是創世區塊"); tsaList.add(tsa); Transaction tsa2 = new Transaction(); tsa2.setId("2"); tsa2.setBusinessInfo("區塊鏈高度為:1"); tsaList.add(tsa2); genesisBlock.setTransactions(tsaList); //設置創世區塊的hash值 genesisBlock.setHash(calculateHash("",tsaList,1)); //添加到已打包保存的業務數據集合中 blockCache.getPackedTransactions().addAll(tsaList); //添加到區塊鏈中 blockCache.getBlockChain().add(genesisBlock); return JSON.toJSONString(genesisBlock); } /** * 創建新區塊 * @param nonce * @param previousHash * @param hash * @param blockTxs * @return */ public Block createNewBlock(int nonce, String previousHash, String hash, List<Transaction> blockTxs) { Block block = new Block(); block.setIndex(blockCache.getBlockChain().size() + 1); //時間戳 block.setTimestamp(System.currentTimeMillis()); block.setTransactions(blockTxs); //工作量證明,計算正確hash值的次數 block.setNonce(nonce); //上一區塊的哈希 block.setPreviousHash(previousHash); //當前區塊的哈希 block.setHash(hash); if (addBlock(block)) { return block; } return null; } /** * 添加新區塊到當前節點的區塊鏈中 * * @param newBlock */ public boolean addBlock(Block newBlock) { //先對新區塊的合法性進行校驗 if (isValidNewBlock(newBlock, blockCache.getLatestBlock())) { blockCache.getBlockChain().add(newBlock); // 新區塊的業務數據需要加入到已打包的業務數據集合里去 blockCache.getPackedTransactions().addAll(newBlock.getTransactions()); return true; } return false; } /** * 驗證新區塊是否有效 * * @param newBlock * @param previousBlock * @return */ public boolean isValidNewBlock(Block newBlock, Block previousBlock) { if (!previousBlock.getHash().equals(newBlock.getPreviousHash())) { System.out.println("新區塊的前一個區塊hash驗證不通過"); return false; } else { // 驗證新區塊hash值的正確性 String hash = calculateHash(newBlock.getPreviousHash(), newBlock.getTransactions(), newBlock.getNonce()); if (!hash.equals(newBlock.getHash())) { System.out.println("新區塊的hash無效: " + hash + " " + newBlock.getHash()); return false; } if (!isValidHash(newBlock.getHash())) { return false; } } return true; } }
以上關鍵代碼實現之后,我們就構建了一個非常簡單的區塊鏈模型,包含一個基本的區塊模型和一個區塊鏈模型,并且能夠生成新的區塊并添加到區塊鏈中,接下來我們進行測試。
第五步,我們編寫一個Controller類進行調用:
BlockController.java
@Controllerpublic class BlockController { @Resource BlockService blockService; @Autowired BlockCache blockCache; /** * 查看當前節點區塊鏈數據 * @return */ @GetMapping("/scan") @ResponseBody public String scanBlock() { return JSON.toJSONString(blockCache.getBlockChain()); } /** * 創建創世區塊 * @return */ @GetMapping("/create") @ResponseBody public String createFirstBlock() { blockService.createGenesisBlock(); return JSON.toJSONString(blockCache.getBlockChain()); } }
第六步,系統測試
首先系統啟動后,先查看區塊鏈中的數據,可以看到當前系統中的區塊鏈為空:
然后我們調用創建創世區塊的方法,查看返回結果:
我們把生成的創世區塊添加到本地區塊鏈中后,轉換成JSON字符串返回,可以看到當前區塊鏈中存儲的有一個區塊對象,至此我們已經實現了一個簡單的區塊鏈。實際的區塊鏈系統模型要復雜的多,需要根據不同的業務場景擴展相應的字段,但是基本特征都是一樣的。
(3)共識機制實現
在上章節中,我們實現了一個簡單的區塊鏈結構,并且能夠生成并添加新的區塊,但是問題來了,實際的區塊鏈系統是一個多節點、分布式、去中心化的網絡,每個節點通過網絡交互,實時同步保存著同樣的整條區塊鏈數據,那么我們生成的區塊,如何才能被其他節點認可,并同步添加到其他所有節點上呢,這個時候我們就需要一套規則,讓所有網絡節點的參與者達成能夠達成共識,接納并保存新的區塊,也就是所謂的“共識機制”。
理論基礎部分已經提到了,共識機制有很多種,各有各的優勢與缺點,接下來我們就用java代碼來模擬實現我們最為熟知的一種機制:工作量證明(Proof of Work),顧名思義就是對工作量的證明,在基于POW機制構建的區塊鏈網絡中,節點通過計算隨機哈希散列的數值爭奪記賬權,求得正確的數值并生成區塊的能力是節點算力的具體表現,計算的過程一般被形象地稱為“挖礦”。
簡單來說就是,區塊鏈系統設定一套計算規則或者說是一套計算題,在新區塊生成前,各個節點都投入到這道題的求解計算中,哪個節點先計算出結果,并得到其它節點的驗證和認可,這個節點就會獲得新區塊的記賬權,并獲得系統相應的獎勵,共識結束。
典型的PoW共識機制應用就是BTC網絡,在BTC網絡中,共識計算的目標是找到滿足某個特定要求的區塊Hash(哈希值)。這個區塊哈希值就是工作結果的一個證明,計算工作的目的就是為了尋找到這個證明值,上一章節中,測試時我們已經見過這個Hash值:
[ { "hash": "25931395e736653212f0258824df4222ae739ec2d5897310258b0857d4d3870c", "index": 1, "nonce": 1, "timestamp": 1580970554734, "transactions": [ { "businessInfo": "這是創世區塊", "id": "1" } ] }]
BTC網絡PoW使用的Hashcash算法,大致邏輯如下:
獲取某種公開可知的數據data(BTC網絡中,指的是區塊頭);
添加一個計數器nonce,初始值設置為0;
計算data與nonce拼接字符串的哈希值;
檢查上一步的哈希值是否滿足某個條件,滿足則停止計算,不滿足則nonce加1,然后重復第3步和第4步,直到滿足這個特定的條件為止。
接下來我們用Java代碼實現這個算法,設定滿足的特定條件為,Hash值前4位都是0,則計算成功(實際區塊鏈網絡中的特定條件要求更高,對計算的運算能力要求也高,并且系統隨著計算難度動態調整滿足的特定條件,來保證區塊生成的速度)。
第一步,我們新建一個共識機制服務類,添加一個“挖礦”方法,計算成功后,獲取記賬權,調用添加區塊的方法,把區塊添加到區塊鏈中:
PowService.java
/** * 共識機制 * 采用POW即工作量證明實現共識 * @author Administrator * */@Servicepublic class PowService { @Autowired BlockCache blockCache; @Autowired BlockService blockService; /** * 通過“挖礦”進行工作量證明,實現節點間的共識 * * @return */ public Block mine(){ // 封裝業務數據集合,記錄區塊產生的節點信息,臨時硬編碼實現 List<Transaction> tsaList = new ArrayList<Transaction>(); Transaction tsa1 = new Transaction(); tsa1.setId("1"); tsa1.setBusinessInfo("這是IP為:"+CommonUtil.getLocalIp()+",端口號為:"+blockCache.getP2pport()+"的節點挖礦生成的區塊"); tsaList.add(tsa1); Transaction tsa2 = new Transaction(); tsa2.setId("2"); tsa2.setBusinessInfo("區塊鏈高度為:"+(blockCache.getLatestBlock().getIndex()+1)); tsaList.add(tsa2); // 定義每次哈希函數的結果 String newBlockHash = ""; int nonce = 0; long start = System.currentTimeMillis(); System.out.println("開始挖礦"); while (true) { // 計算新區塊hash值 newBlockHash = blockService.calculateHash(blockCache.getLatestBlock().getHash(), tsaList, nonce); // 校驗hash值 if (blockService.isValidHash(newBlockHash)) { System.out.println("挖礦完成,正確的hash值:" + newBlockHash); System.out.println("挖礦耗費時間:" + (System.currentTimeMillis() - start) + "ms"); break; } System.out.println("第"+(nonce+1)+"次嘗試計算的hash值:" + newBlockHash); nonce++; } // 創建新的區塊 Block block = blockService.createNewBlock(nonce, blockCache.getLatestBlock().getHash(), newBlockHash, tsaList); return block; } /** * 驗證hash值是否滿足系統條件 * 暫定前4位是0則滿足條件 * @param hash * @return */ public boolean isValidHash(String hash) { //System.out.println("難度系數:"+blockCache.getDifficulty()); return hash.startsWith("0000"); }}
第二步,編寫測試共識機制服務的Controller類方法:
BlockController.java
/** * 工作量證明PoW * 挖礦生成新的區塊 */ @GetMapping("/mine") @ResponseBody public String createNewBlock() { powService.mine(); return JSON.toJSONString(blockCache.getBlockChain()); }
第三步,啟動系統,進行測試。
首先執行
http://localhost:8080/create方法,生成創世區塊。
其次調用
http://localhost:8080/mine方法進行工作量計算證明,生成新的區塊,并添加到本地區塊鏈中:
我們來看一下,系統后臺計算的過程,此次計算共花費1048ms計算出滿足條件的Hash值,共計算4850次:
至此,我們實現了一個簡單的工作量證明機制,并在當前區塊鏈系統節點上運行,完成了正確結果的計算,生成了一個新的區塊。
接下來我們將會開發一個P2P網絡,實現多個節點的同時運行,當一個節點挖礦完成后,通過P2P網絡廣播給其他節點,其他節點驗證通過后,會把新產生的區塊添加到自己的區塊鏈上,進而保證整個區塊鏈網絡所有節點的數據一致性。
(4)P2P網絡開發
前面我們已經實現了一個基本的區塊鏈系統,并且實現了PoW工作量證明共識機制,通過挖礦計算出正確的結果同時生成一個新的區塊添加到區塊鏈中,但是這些都是基于單節點的運行,實際的區塊鏈是有多個節點同時運行的分布式網絡系統,所有節點同時計算搶奪記賬權,共同維護一條完整的區塊鏈。
接下來我們基于Java的WebSocket實現一個Peer-to-Peer網絡,實現多個節點間的相互通信,通過本章節,我們將要實現以下功能:
- 創建一個基于java的p2p網絡 - 運行多個節點,且多個節點通過p2p網絡自動同步區塊信息 - 一個節點挖礦生成新的區塊后,自動廣播給其他所有節點 - 每個節點在接收到其他節點發送的區塊內容后,進行驗證,驗證通過添加到本地區塊鏈上 - 在自我節點查看整個區塊鏈內容
開發測試本章節的功能,我們最好準備兩臺電腦或者虛擬機,再或者Docker集群環境也可以,便于多節點的運行測試。如果只有一臺電腦也可以,各個節點運行的端口號設置為不相同即可。
第一步,開發思路整理
目前我們已經實現了單節點的區塊生成,那么我們接下來只需要實現各個節點的消息同步即可。
首先,通過java代碼實現p2p網絡的server端和client端,每個節點既是服務端也是客戶端。
然后,一個節點啟動時,會尋找區塊鏈網絡上的有效節點,并建立socket連接(BTC網絡可以通過使用“DNS”種子方式獲取BTC有效節點,DNS種子提供比特幣節點的IP地址列表),我們直接把節點列表配置到application.yml文件中。
接著,從連接上的節點獲取最新的區塊信息,如果當前節點首次運行,則獲取整個區塊鏈信息,并替換到本地。
之后,各個節點同時挖礦計算,哪個節點先計算完成,就把生成的新區塊全網廣播給其他所有節點(實際的區塊鏈網絡一直在計算,我們為了便于測試,手動觸發一個節點挖礦產生區塊,然后全網廣播給其他所有節點)。
最后,當一個節點收到廣播內容后,對接收到的新區塊進行驗證,驗證通過后添加到本地區塊鏈上,驗證的首要條件是新區塊的高度必須比本地的區塊鏈高度要高。
第二步,先實現P2P網絡server端
新建一個P2PServer類,并添加一個初始化server端的方法:
P2PServer.java
/** * p2p服務端 * * @author Jared Jia * */@Componentpublic class P2PServer { @Autowired P2PService p2pService; public void initP2PServer(int port) { WebSocketServer socketServer = new WebSocketServer(new InetSocketAddress(port)) { /** * 連接建立后觸發 */ @Override public void onOpen(WebSocket webSocket, ClientHandshake clientHandshake) { p2pService.getSockets().add(webSocket); } /** * 連接關閉后觸發 */ @Override public void onClose(WebSocket webSocket, int i, String s, boolean b) { p2pService.getSockets().remove(webSocket); System.out.println("connection closed to address:" + webSocket.getRemoteSocketAddress()); } /** * 接收到客戶端消息時觸發 */ @Override public void onMessage(WebSocket webSocket, String msg) { //作為服務端,業務邏輯處理 p2pService.handleMessage(webSocket, msg, p2pService.getSockets()); } /** * 發生錯誤時觸發 */ @Override public void onError(WebSocket webSocket, Exception e) { p2pService.getSockets().remove(webSocket); System.out.println("connection failed to address:" + webSocket.getRemoteSocketAddress()); } @Override public void onStart() { } }; socketServer.start(); System.out.println("listening websocket p2p port on: " + port); }}
第三步,實現P2P網絡client端
P2PClient.java
/** * p2p客戶端 * * @author Jared Jia * */@Componentpublic class P2PClient { @Autowired P2PService p2pService; public void connectToPeer(String addr) { try { final WebSocketClient socketClient = new WebSocketClient(new URI(addr)) { @Override public void onOpen(ServerHandshake serverHandshake) { //客戶端發送請求,查詢最新區塊 p2pService.write(this, p2pService.queryLatestBlockMsg()); p2pService.getSockets().add(this); } /** * 接收到消息時觸發 * @param msg */ @Override public void onMessage(String msg) { p2pService.handleMessage(this, msg, p2pService.getSockets()); } @Override public void onClose(int i, String msg, boolean b) { p2pService.getSockets().remove(this); System.out.println("connection closed"); } @Override public void onError(Exception e) { p2pService.getSockets().remove(this); System.out.println("connection failed"); } }; socketClient.connect(); } catch (URISyntaxException e) { System.out.println("p2p connect is error:" + e.getMessage()); } }}
第四步,定義P2P網絡同步的消息模型
同步的消息模型,我們定義為四類,分別是:
BlockConstant.java
// 查詢最新的區塊 public final static int QUERY_LATEST_BLOCK = 1; // 返回最新的區塊 public final static int RESPONSE_LATEST_BLOCK = 2; // 查詢整個區塊鏈 public final static int QUERY_BLOCKCHAIN = 3; // 返回整個區塊鏈 public final static int RESPONSE_BLOCKCHAIN = 4;
定義一個各個節點間傳遞的消息模型:
Message.java
/** * p2p通訊消息 * * @author Jared Jia * */public class Message implements Serializable { private static final long serialVersionUID = 1L; /** * 消息類型 */ private int type; /** * 消息內容 */ private String data; /****set get方法省略****/ }
第五步,實現向其他節點廣播的方法
新建一個p2p網絡服務類,向外發送消息,或者處理當前節點收到其他節點發送的請求。
P2PService.java
/** * 全網廣播消息 * @param message */ public void broatcast(String message) { List<WebSocket> socketsList = this.getSockets(); if (CollectionUtils.isEmpty(socketsList)) { return; } System.out.println("======全網廣播消息開始:"); for (WebSocket socket : socketsList) { this.write(socket, message); } System.out.println("======全網廣播消息結束"); }
第六步,開發消息處理路由
第五步中已經實現了向外發送消息,本步驟實現接收消息。
首先設計一個服務端和客戶端共用的消息路由,來分發消息給對應的處理單元。
P2PService.java
/** * 客戶端和服務端共用的消息處理方法 * @param webSocket * @param msg * @param sockets */ public void handleMessage(WebSocket webSocket, String msg, List<WebSocket> sockets) { try { Message message = JSON.parseObject(msg, Message.class); System.out.println("接收到IP地址為:" +webSocket.getRemoteSocketAddress().getAddress().toString() +",端口號為:"+ webSocket.getRemoteSocketAddress().getPort() + "的p2p消息:" + JSON.toJSONString(message)); switch (message.getType()) { //客戶端請求查詢最新的區塊:1 case BlockConstant.QUERY_LATEST_BLOCK: write(webSocket, responseLatestBlockMsg());//服務端調用方法返回最新區塊:2 break; //接收到服務端返回的最新區塊:2 case BlockConstant.RESPONSE_LATEST_BLOCK: handleBlockResponse(message.getData(), sockets); break; //客戶端請求查詢整個區塊鏈:3 case BlockConstant.QUERY_BLOCKCHAIN: write(webSocket, responseBlockChainMsg());//服務端調用方法返回最新區塊:4 break; //直接接收到其他節點發送的整條區塊鏈信息:4 case BlockConstant.RESPONSE_BLOCKCHAIN: handleBlockChainResponse(message.getData(), sockets); break; } } catch (Exception e) { System.out.println("處理IP地址為:" +webSocket.getRemoteSocketAddress().getAddress().toString() +",端口號為:"+ webSocket.getRemoteSocketAddress().getPort() + "的p2p消息錯誤:" + e.getMessage()); } }
第七步,開發消息處理單元
有了消息路由之后,接著編寫不同的處理單元,處理其他節點發送來的區塊或者區塊鏈信息,總體原則是:先校驗其他節點發送來的區塊或者區塊鏈的有效性,然后判斷它們的高度比當前節點的區塊鏈高度要高,如果高則替換本地的區塊鏈,或者把新區塊添加到本地區塊鏈上。
P2PService.java
/** * 處理其它節點發送過來的區塊信息 * @param blockData * @param sockets */ public synchronized void handleBlockResponse(String blockData, List<WebSocket> sockets) { //反序列化得到其它節點的最新區塊信息 Block latestBlockReceived = JSON.parseObject(blockData, Block.class); //當前節點的最新區塊 Block latestBlock = blockCache.getLatestBlock(); if (latestBlockReceived != null) { if(latestBlock != null) { //如果接收到的區塊高度比本地區塊高度大的多 if(latestBlockReceived.getIndex() > latestBlock.getIndex() + 1) { broatcast(queryBlockChainMsg()); System.out.println("重新查詢所有節點上的整條區塊鏈"); }else if (latestBlockReceived.getIndex() > latestBlock.getIndex() && latestBlock.getHash().equals(latestBlockReceived.getPreviousHash())) { if (blockService.addBlock(latestBlockReceived)) { broatcast(responseLatestBlockMsg()); } System.out.println("將新接收到的區塊加入到本地的區塊鏈"); } }else if(latestBlock == null) { broatcast(queryBlockChainMsg()); System.out.println("重新查詢所有節點上的整條區塊鏈"); } } } /** * 處理其它節點發送過來的區塊鏈信息 * @param blockData * @param sockets */ public synchronized void handleBlockChainResponse(String blockData, List<WebSocket> sockets) { //反序列化得到其它節點的整條區塊鏈信息 List<Block> receiveBlockchain = JSON.parseArray(blockData, Block.class); if(!CollectionUtils.isEmpty(receiveBlockchain) && blockService.isValidChain(receiveBlockchain)) { //根據區塊索引先對區塊進行排序 Collections.sort(receiveBlockchain, new Comparator<Block>() { public int compare(Block block1, Block block2) { return block1.getIndex() - block2.getIndex(); } }); //其它節點的最新區塊 Block latestBlockReceived = receiveBlockchain.get(receiveBlockchain.size() - 1); //當前節點的最新區塊 Block latestBlock = blockCache.getLatestBlock(); if(latestBlock == null) { //替換本地的區塊鏈 blockService.replaceChain(receiveBlockchain); }else { //其它節點區塊鏈如果比當前節點的長,則處理當前節點的區塊鏈 if (latestBlockReceived.getIndex() > latestBlock.getIndex()) { if (latestBlock.getHash().equals(latestBlockReceived.getPreviousHash())) { if (blockService.addBlock(latestBlockReceived)) { broatcast(responseLatestBlockMsg()); } System.out.println("將新接收到的區塊加入到本地的區塊鏈"); } else { // 用長鏈替換本地的短鏈 blockService.replaceChain(receiveBlockchain); } } } } }
3、完整系統運行與測試
第一步,打包生成測試用的可執行jar包
準備兩臺機器(虛擬機或者Docker集群都行),同時運行兩個節點,節點信息如下:
通過mvn package -Dmaven.test.skip=true命令對工程進行打包,生成可直接運行的jar包,打包前對工程進行配置,配置信息如下圖:
節點Node1打包:
節點Node2打包:
分別打包之后,生成兩個節點的可執行jar包,如下:
把兩個jar包分別放在對應IP的windows機器上,打開命令行模式,進入jar所在文件夾,分別執行以下命令運行兩個節點:
java -jar dce-blockchain-node1.jar
java -jar dce-blockchain-node2.jar
啟動節點2的時候,可以看到后臺日志,已經連接上節點1,如下圖所示:
第二步,對兩個節點進行測試
首先,兩個節點啟動后,用postman分別執行
http://192.168.0.104:8080/scan和http://192.168.0.112:8090/scan請求,可以看到兩個節點的區塊鏈內容都為空。
然后,在節點1上分別執行
http://192.168.0.104:8080/create和http://192.168.0.104:8080/mine請求,來生成創世區塊,以及通過挖礦產生第二個區塊,執行后查看節點1的區塊鏈信息如下:
執行
http://192.168.0.104:8080/scan結果:
[ { "hash": "5303d2990c139992bdb5a22aa1dac4f2719755304e45bac03ca4a1f1688c909e", "index": 1, "nonce": 1, "timestamp": 1581064647736, "transactions": [ { "businessInfo": "這是創世區塊", "id": "1" }, { "businessInfo": "區塊鏈高度為:1", "id": "2" } ] }, { "hash": "0000de5eea0c20c2e7d06220bc023886e88dd8784eaa2fd2d1d6c5e581061d85", "index": 2, "nonce": 4850, "previousHash": "5303d2990c139992bdb5a22aa1dac4f2719755304e45bac03ca4a1f1688c909e", "timestamp": 1581064655139, "transactions": [ { "businessInfo": "這是IP為:192.168.0.104,端口號為:7001的節點挖礦生成的區塊", "id": "1" }, { "businessInfo": "區塊鏈高度為:2", "id": "2" } ] }]
最后,我們來驗證節點2是否已經完成了節點1生成的區塊鏈信息的網絡同步,Postman執行
http://192.168.0.112:8090/scan請求,查看返回結果:
從結果可以看到,區塊鏈網絡節點2已經接收到節點1發送的區塊鏈信息,系統日志如下:
反過來,我們在節點2上再執行一次挖礦操作,可以看到節點1上,已經接收到節點2挖礦新產生的區塊信息,并添加到節點1的區塊鏈上:
至此,我們已經實現了一個完整的小型區塊鏈網絡,并實現各個節點間的通信,多個節點共同維護同一個區塊鏈信息。
結語:
區塊鏈系統非常龐大,涉及方方面面的技術,本人所演示的代碼主要對區塊鏈基礎的一些概念進行了詮釋,感興趣的同學,還可以在此基礎上繼續開發,來實現例如持久層、消息的加密解密、系統賬戶模型、預言機、側鏈技術以及智能合約等區塊鏈系統功能。
寫給每個區塊鏈技術人員:
目前市面上流行的企業級區塊鏈框架,例如超級賬本Fabric都是國外人員在主導,而我們國內除了幾家大廠外,其他很多區塊鏈公司基本都是把人家的東西拿過來進行二次封裝,然后對外聲稱自己公司已經掌握了區塊鏈核心技術,并對企業提供服務,這是一種不好的現象。大家可以想想我們現在用的開發語言、框架有幾個真正是國產的,我們再聯想一下前段時間中興、華為被人家核心技術卡脖子事件,就知道我們要做的事情有很多,我們需要去除浮躁,靜下心來好好研究底層核心技術,這樣才能實現真正的“彎道超車”!
三、源代碼
最后源碼、項目地址、獲取方式:關注小編+轉發文章+私信【555】免費獲取
附贈福利:
另外整理成了40多套PDF文檔:全套的Java面試寶典手冊1000+pdf
1.編程+開源框架+分布式”等七大面試專欄
2.Java核心知識點1000+Java面試題合集pdf
3.阿里、京東、螞蟻等大廠面試真題解析
4.Spring全家桶面試題
5.算法筆記文檔+刷題手冊
6.思維導圖(jvm、mysql、并發編程、數據庫、kafka等等)。
如果你對這個感興趣,小編可以免費分享。
重要的事情說三遍,轉發+轉發+轉發,一定要記得點贊轉發哦!!!