首页 / 区块专栏 / 基于Java开发完整的区块链系统(附完整源码)

基于Java开发完整的区块链系统(附完整源码)

标题:基于Java开发一套完整的区块链系统(附完整源码)

前言

近年来,区块链的概念越来越流行。尤其是区块链技术被纳入国家基础设施建设清单后,各大公司也开始招人,进行区块链技术的研究。从各大招聘网站的区块链技术来看,区块链职位的薪资都非常不错,月薪从3万到8万不等。这对于我们程序员来说也是一个机会,说明学习区块链技术刻不容缓。

我从2016年开始就致力于区块链系统的开发,最近身边很多朋友都找我,想让我给他们讲讲区块链技术的发展。因此,我就简单的搭建了一个简单的Java区块链系统,写了详细的开发教程,发布在我的博客上。希望对热爱区块链技术的朋友学习和入门有所帮助。

这个区块链系统代码非常简洁明了,对于新手来说非常容易理解。它旨在告诉大家,区块链技术并没有那么先进和复杂。系统中除了springboot框架之外,其他的基本都是纯原生开发。甚至P2P网络也是使用java socket实现的。

文末有本文完整源代码的链接。

一、区块链技术的理论基础1、基本概念(一)区块链从技术角度看,区块链是一种由包含交易信息的区块按时间顺序从后到前链接而成的数据结构。

从应用层面来看,区块链是一种分布式共享账本和数据库,具有去中心化、不可篡改、全痕迹、集体维护、公开透明等特点。基于这些特点,区块链技术可以开发出具有自身信任体系特点的系统,实现多个主体之间的协同信任和一致行动。

区块是区块链中的最小单位。它主要由包含元数据的块头和存储一条或多条交易信息的块体组成。每个区块记录了当前区块的哈希值和。前一个区块的哈希值,通过两个哈希值的关联,让所有区块以链式结构串在一起,形成完整的区块链。

区块链中的第一个块称为创世块,不需要与前一个块相关。以BTC网络为例,每个区块主要包含以下信息字段:

区块大小:以字节为单位表示的区块数据大小区块头:区块头包含以下字段: 1. 区块头哈希值2. 父区块头哈希值3. 时间戳:区块生成的大致时间4. Merkle root:该区块中交易的默克尔树根的哈希值5. 难度目标:该区块工作量证明算法的难度目标6. Nonce:用于工作量证明算法的计数器交易计数器:该交易的交易数量:区块链中记录交易信息结构的简单模型,如下图所示:

区块中的交易集合记录了一些特定的信息。在BTC网络中,主要记录交易信息。在其他区块链网络中,可以根据业务逻辑保存相应的业务数据,例如审计信息、版权信息、账单信息等。这就是区块链经常被用作共享账本的原因。

例如,区块链可以被视为用于记账的笔记本。一个区块相当于一页,记录了一定时间内从第一页到最后一页的所有记账信息。按页码顺序排列,就是一本完整的账本。

(2) 区块链网络实际的区块链系统由多个区块链节点组成。每个节点运行同一套区块链主干网络的副本,每个节点之间通过P2P网络进行交互,最终将形成一个完整的区块链网络系统。

P2P网络是可靠的、去中心化的、开放的。每个节点交互协作地运行。每个节点不仅向外界提供服务,还使用网络中其他节点提供的服务。当某个区块链节点产生新的区块时,它会通过广播的方式告知其他节点。当其他节点通过网络接收到区块信息时,就会验证该区块信息。当一定数量的节点全部验证通过后,每个节点都会将区块更新到其现有的区块链上,最终使得整个区块链网络中每个节点的信息一致。这也是区块链去中心化、可信的特点。表现。

区块链网络的简单模型,如下图所示:

2. 区块链分类(1) 公有链公共区块链(Public Block Chains)是指:世界上任何个人或团体都可以发送交易,并且交易可以从区块链中获得有效的确认。任何人都可以参与区块链的使用和维护,信息公开透明。公链是最早的区块链。 BTC、以太坊等虚拟数字货币都是基于公链的。但目前公链的实际应用价值并不大,也没有特别适合的应用场景。

(2)联盟区块链:在一定群体内指定多个预选节点作为记账人,每个区块的生成由所有预选节点共同决定(预选节点参与共识过程),其他访问节点可以参与交易,但有权限限制,信息受到保护,比如银联组织。目前,联盟链是各个区块链技术团队的主要研究对象。由于联盟链具备区块链技术的大部分特性,在权限管理、数据安全、监管等方面更具优势,是企业优先考虑的区块链。技术解决方案。

市场上也有一些比较主流的联盟链技术框架,使得联盟链的开发和维护变得更加容易。国内一些大型软件厂商也有自己的企业区块链技术解决方案,如蚂蚁金服的区块链平台、腾讯的TrustSQL平台、东软的SaCa EchoTrust区块链应用平台、京东的区块链防伪溯源平台等。 ETC。

(3)私有区块链(Private Block Chains):仅利用区块链的总账技术进行记账。它可以是公司或个人。它拥有对区块链的独占写入权限,并且使用区块链不易被篡改,可以作为账本数据库。

3、关键技术及特点(1)共识机制共识机制被称为区块链系统的灵魂,是区块链系统信任体系的基础。区块链系统是一个多节点的分布式账本系统。当有新的信息需要记录时,哪个节点负责记账,哪个节点负责记账奖励,哪个节点负责验证记账结果,如何让各个节点达成最终共识,并进行复制和复制网络中所有节点以相同的顺序记录记账结果,这就是共识机制的作用。

据百度百科介绍:

所谓“共识机制”,就是通过特殊节点的投票,在极短的时间内完成交易的验证和确认。对于一笔交易,如果几个利益无关的节点能够达成共识,我们就可以认为整个网络是正确的。这也能达成共识。更简单地说,如果中国的微博网红、美国的虚拟货币玩家、非洲的学生和欧洲的旅行者彼此不认识,但他们都一致认为你是一个好人,那么基本上可以是结论是你不是一个坏人。

目前比较主流的共识算法有PoW、PoS、DPoS、PBFT等,在实际使用中,每种算法都有自己的优缺点。区块链项目应用于不同的场景时,会采用不同的共识机制和算法。

(2)去中心化去中心化是互联网发展过程中形成的社会关系形式和内容生产形式。它是相对于“中心化”而言的一种新的在线内容生产流程。在分布有众多节点的区块链系统中,每个节点都具有高度的自治权。任何节点都可能成为舞台中心,但不具备强制的中心控制功能。节点之间的影响力会通过网络形成关联。这种开放、扁平、平等的制度现象或结构就是我们所说的去中心化。

去中心化系统具有容错性高、抗攻击能力强的特点。在中心化系统中,一旦中心出现问题,整个系统就会崩溃。但是,如果区块链系统中的任何一个节点出现问题,都不会对整个区块链网络产生大的影响。

此外,去中介并不意味着不接受监管。 “去中心化”是指中央控制者和中介者,而不是监管者。监管节点可以轻松连接到任何区块链网络。并且由于区块链公开透明的特性,监管机构可以更轻松地监控整个系统的交易数据。

(3)智能合约从技术角度来看,智能合约是部署在区块链上的一段程序代码。当程序设定的条件满足时,就会在区块链上运行并获得相应的结果。这种情况有点类似于微信的小程序。区块链提供了虚拟机和脚本语言。用户根据脚本语言的语法开发具有一定业务逻辑的程序并将其部署在区块链上。当满足执行条件时,智能合约将由区块链虚拟机解释并运行。

一个典型的应用就是以太坊平台的智能合约。这个平台可以支持用户通过简单的几行代码来实现自己想要的合约,实现不需要人工监督、不可篡改、自动运行的合约,买卖房子。不需要找中介,不需要找公证人借钱……人们可以随时随地根据自己的需求发起合同。它的执行不依赖于任何个人或组织。所有信任完全基于以太坊区块链平台本身。

(4)不可篡改大多数人习惯称其为不可篡改,但从技术角度来看,我个人认为称其为不可逆性更为合适。既然是计算机系统,增、删、改、查是基本的功能属性。然而,区块链系统中的删除和修改操作有点特殊。

区块链是由各个区块的哈希值连接起来的链式结构,区块的哈希值=SHA256(“当前区块内容+前一个区块的哈希值”),任何对区块内容的修改都会引起变化哈希值的变化,哈希值的变化也会引起子区块哈希值的变化,从而引起整个区块链的变化。

因此,任何人想要修改该区块的数据几乎是不可能的,除非他重新修改整个区块链中从创世区块到最新区块的所有哈希值,并且修改后,必须广播告诉所有网络中的其他节点,以便所有其他节点都可以接受修改。

然而,按照目前计算机的计算能力,短时间内修改区块链从头到尾的所有内容是非常困难的,而且即使修改完成,其他节点也不会接受修改,因为独自一人并不具备让所有节点达成共识的条件。

4、流行的区块链框架和应用(1)公链应用:BTC网络区块链1.0产品。对于比特币,中本聪是这样定义的:它是一个完全通过点对点技术实现的电子现金系统。它使得在线支付可以由一方直接发起并支付给另一方,无需经过任何金融机构。

与所有货币不同,比特币不依赖于特定货币机构的发行。它是根据特定算法通过大量计算生成的。比特币经济采用整个P2P网络中众多节点组成的分布式数据库来确认并记录所有交易行为。并利用密码学设计保证货币流通各环节的安全。随后,人们整理出了基于比特币网络技术解决信任问题的区块链技术体系,比特币网络原理也成为了区块链技术初学者的经典教材。

(2)公链应用:以太坊网络区块链2.0产品的代表。以太坊是一个用于去中心化应用程序(Dapp)的开源区块链平台,具有区块链技术的大部分特征。但与其他区块链不同的是,以太坊是可编程的,开发者可以用它来构建不同的应用程序,通过其专用的加密货币以太坊(简称“ETH”)提供去中心化的以太坊虚拟机(Ethereum Virtual Machine)来处理点对点合约(只是一些脚本代码)。如果将比特币网络视为一组分布式数据库,那么以太坊则更进一步。可以将其视为一台分布式计算机:区块链是计算机的ROM,合约是程序,以太坊的矿工负责计算,扮演CPU的角色。

以太坊的概念是由程序员Vitalik Buterin 在受到比特币启发后于2013 年至2014 年首次提出,大致意思是“下一代加密货币和去中心化应用平台”。虽然以太坊是一个可以开发新应用的平台,但由于以太坊的运行方式与BTC网络相同,采用Token机制,且平台性能不足,因此经常会出现网络拥塞的情况。该平台用于学习、开发和测试。区块链技术是可以的,但是用在实际生产中不太现实。

(3)联盟链开发框架:Hyperledger FabricHyperledger Fabric也称为Hyperledger。它是IBM 向Linux 基金会贡献的商业分布式账本。它是全球最大的企业应用分布式开源项目。与其他区块链技术一样,它也有账本并且可以使用智能合约。 Fabric的智能合约可以有多种架构,并且可以用Go、Java和Javascript等主流语言进行编程。此外,还可以使用Solidity。

迄今为止,Fabric已获得阿里巴巴、AWS、Azure、百度、谷歌、华为、IBM、甲骨文、腾讯等互联网巨头的支持。很多企业区块链平台都使用Fabric作为底层框架,比如Oracle。但由于IBM对区块链的定义强调了区块链的分布式、不可变元素,弱化了共识机制,利用Kafka和Zookeeper的“排序服务”来达成共识,因此也有业内人士称Hyperledger是一个“伪账本”。 “区块链”,但即便如此,也无法抵挡企业对Hyperledger的喜爱。目前,Fabric 2.0版本已经正式发布。

(四)总结目前公链在实际应用中的业务场景并不多。其中大部分是采矿题材或在线宠物养成游戏。并且由于数字货币的匿名性,一些不法分子利用这一特性,利用数字货币进行洗钱、暗网交易等非法活动,是各国打击的对象。我国的政策法规也严格禁止。因此,对于技术人员来说,公链可以作为研究和学习的对象。其他暂时没有太大的实际意义。

目前大多数区块链公司的研究方向主要针对企业联盟链和私有链,国家层面也在大力支持区块链技术的发展,特别是区块链底层核心技术的研发,倡导区块链技术的发展。区块链作为核心技术自主创新的重要突破口,要明确主攻方向,加大投入,着力攻克一批关键核心技术,加快区块链技术和产业创新发展。然而,市场上大多数主流区块链平台仍由国外公司主导。国内区块链底层核心技术的发展仍需要技术人员加倍努力。

二、区块链技术的Java实现1、区块链技术架构

目前主流的区块链技术架构主要分为五层。数据层是最底层的技术,主要实现数据存储、账户信息、交易信息等模块。数据存储主要基于Merkle树,通过区块和链。它采用公式结构实现,账户和交易基于数字签名、哈希函数、非对称加密技术等多种密码算法和技术,保证区块链中数据的安全。

网络层主要实现网络节点的连接和通信,也称为点对点技术。每个区块链节点通过网络进行通信。共识层采用共识算法,让网络中所有节点就全网所有区块数据的真实性、正确性达成一致,防止拜占庭攻击、51攻击等区块链共识算法攻击。

激励层主要实现区块链代币的发行和分配机制。这是公链的范围,我们不分析。应用层一般以区块链系统为平台,在平台上实现一些去中心化的应用或者智能合约,平台提供虚拟机来运行这些应用。

接下来我们将开发一个基于Java语言的小型区块链系统,实现数据层、网络层、共识层的部分功能。我们将用简单的代码来直观地抽象概念,以加深对上述区块链技术基础的理解。理论理解。

2、基于Java的区块链开发实用(1)开发环境开发工具

VS代码

开发语言

爪哇

JDK版本

JDK1.8或OpenJDK11

开发框架

SpringBoot2.2.1

工程管理

Maven3.6

测试工具

邮差

(2)区块链基本模型构建区块是区块链系统的最小单位。第一步,我们首先实现一个最简单的块结构,新建一个Block.java类,主要包含以下字段: Block.java

/**

* 块结构

*

* @作者贾里德·贾

*

*/

公共类块实现可序列化{

私有静态最终长serialVersionUID=1L;

/**

* 块索引号(块高度)

*/

私有int 索引;

/**

* 当前区块的哈希值,该区块的唯一标识

*/

私有字符串哈希;

/**

* 前一个区块的哈希值

*/

私有字符串previousHash;

/**

* 生成区块的时间戳

*/

私有长时间戳;

/**

* 工作量证明,计算正确哈希值的次数

*/

私有int 随机数;

/**

* 当前区块中存储的业务数据的集合(如转账交易信息、账单信息、合约信息等)

*/

私有ListTransaction交易;

/*** 省略get set方法****/

}区块链是一种由区块按照区块哈希顺序连接而成的数据结构。哈希值是通过哈希算法对区块进行二次哈希计算得到的数字摘要信息(对哈希函数不了解的可以先百度一下SHA算法),用于保证区块的信息安全和整个区块链的有效性。因此,第二步,我们添加了一个新的计算区块Hash值的方法,使用SHA256算法,并通过java实现:CryptoUtil.java

/**

* 密码学工具

*

* @作者贾里德·贾

*

*/

公共类CryptoUtil {

/**

* SHA256哈希函数

* @参数str

* @返回

*/

公共静态字符串SHA256(字符串str){

消息摘要消息摘要;

字符串encodeStr=\’\’;

尝试{

messageDigest=MessageDigest.getInstance(\’SHA-256\’);

messageDigest.update(str.getBytes(\’UTF-8\’));

encodeStr=byte2Hex(messageDigest.digest());

} catch (异常e) {

System.out.println(\’getSHA256 错误\’ + e.getMessage());

}

返回encodeStr;

}

私有静态字符串byte2Hex(byte[] bytes) {

StringBuilder 构建器=new StringBuilder();

字符串临时;

for (int i=0; i bytes.length; i++) {

temp=Integer.toHexString(bytes[i]0xFF);

if (temp.length()==1) {

生成器.append(\’0\’);

}

生成器.append(temp);

}

返回builder.toString();

}

}第三步,创建链结构对象并保存区块对象,形成有序的区块链列表。考虑到线程安全问题,采用CopyOnWriteArrayList来实现。为了方便测试,我们暂时把区块链结构存储在本地缓存中。实际的区块链网络最终会实现持久层的功能,将区块链数据保存到数据库中。例如BTC核心网使用K-V数据库LevelDB:BlockCache.java

公共类块缓存{

/**

* 当前节点的区块链结构

*/

私有ListBlock blockChain=new CopyOnWriteArrayListBlock();

公共ListBlock getBlockChain() {

返回区块链;

}

公共无效setBlockChain(ListBlock块链){

this.blockChain=区块链;

}

}第四步,有了区块链结构之后,我们需要添加一个新的方法来向区块链添加区块。同时,我们每添加一个区块,都需要验证新区块的有效性,比如Hash值是否正确。新块中前一个块的Hash属性值是否等于前一个块的Hash值。

此外,区块链中必须有一个创世块。我们直接通过硬编码的方式实现:BlockService.java。

/**

* 区块链核心服务

*

* @作者贾里德·贾

*

*/

@服务

公共类块服务{

@Autowired

块缓存块缓存;

/**

*创建创世块

* @返回

*/

公共字符串createGenesisBlock(){

区块创世区块=new Block();

//设置创世块高度为1

genesisBlock.setIndex(1);

genesisBlock.setTimestamp(System.currentTimeMillis());

genesisBlock.setNonce(1);

//封装业务数据

ListTransaction tsaList=new ArrayListTransaction();

交易tsa=new Transaction();

tsa.setId(\’1\’);

tsa.setBusinessInfo(\’这是创世块\’);

tsaList.add(tsa);

事务tsa2=new Transaction();

tsa2.setId(\’2\’);

tsa2.setBusinessInfo(\’区块链高度为:1\’);

tsaList.add(tsa2);

genesisBlock.setTransactions(tsaList);

//设置创世块的哈希值

genesisBlock.setHash(calculateHash(\’\’,tsaList,1));

//添加到打包保存的业务数据集合中

blockCache.getPackedTransactions().addAll(tsaList);

//添加到区块链

blockCache.getBlockChain().add(genesisBlock);

返回JSON.toJSONString(genesisBlock);

}

/**

* 创建新块

* @参数随机数

* @param previousHash

* @参数哈希

* @param blockTxs

* @返回

*/

公共块createNewBlock(int nonce, String previousHash, String hash, ListTransaction blockTxs) {

块块=新块();

block.setIndex(blockCache.getBlockChain().size() + 1);

//时间戳

block.setTimestamp(System.currentTimeMillis());

block.setTransactions(blockTxs);

//工作量证明,计算计算正确哈希值的次数

block.setNonce(nonce);

//前一个区块的哈希值

block.setPreviousHash(previousHash);

//当前区块的哈希值

块.setHash(哈希);

if (addBlock(块)) {

返回块;

}

返回空值;

}

/**

* 添加新的区块到当前节点的区块链中

*

* @param newBlock

*/

公共布尔addBlock(块newBlock){

//先验证新区块的合法性

if (isValidNewBlock(newBlock, blockCache.getLatestBlock())) {

blockCache.getBlockChain().add(newBlock);

//新区块的业务数据需要添加到打包的业务数据集合中。

blockCache.getPackedTransac

tions().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
@Controller
public 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
*
*/
@Service
public 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
*
*/
@Component
public 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
*
*/
@Component
public 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.jarjava -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等等)。
如果你对这个感兴趣,小编可以免费分享。重要的事情说三遍,转发+转发+转发,一定要记得点赞转发哦!!!

上一篇
下一篇

为您推荐

手机访问
手机扫一扫打开网站

手机扫一扫打开网站

返回顶部