目前网络上关于区块链入门、科普的文章不少,本文就不再赘述区块链的基本概念了,如果对区块链不是很了解的话,可以看一下我之前收集的一些入门学习资源:http://blog.51cto.com/zero01/2066321

对区块链技术感到新奇的我们,都想知道区块链在代码上是怎么实现的,所以本文是实战向的,毕竟理论我们都看了不少,但是对于区块链具体的实现还不是很清楚,本文就使用Java语言来实现一个简单的区块链。

但是要完全搞懂区块链并非易事,对于一门较为陌生的技术,我们需要在理论+实践中学习,通过写代码来学习技术会掌握得更牢固,构建一个区块链可以加深对区块链的理解。

准备工作

掌握基本的JavaSE以及JavaWeb开发,能够使用Java开发简单的项目,并且需要了解HTTP协议。

我们知道区块链是由区块的记录构成的不可变、有序的链结构,记录可以是交易、文件或任何你想要的数据,重要的是它们是通过哈希值(hashes)链接起来的。

如果你还不是很了解哈希是什么,可以查看这篇文章:https://learncryptography.com/hash-functions/what-are-hash-functions

环境描述

  • JDK1.8
  • Tomcat 9.0
  • Maven 3.5
  • JSON 20160810
  • javaee-api 7.0

pom.xml文件配置内容:

<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>

然后还需要一个HTTP客户端,比如Postman,Linux命令行下的curl或其它客户端,我这里使用的是Postman。

Blockchain类

首先创建一个Blockchain类,在构造器中创建了两个主要的集合,一个用于储存区块链,一个用于储存交易列表,本文中所有核心的主要代码都写在这个类里,方便随时查看,在实际开发则不宜这么做,应该把代码拆分仔细降低耦合度。

以下是Blockchain类的框架代码:

package org.zero01.core;

import java.util.ArrayList;

import java.util.HashMap;

import java.util.List;

public class BlockChain {

// 存储区块链

private List<Object> chain;

// 该实例变量用于当前的交易信息列表

private List<Object> currentTransactions;

public BlockChain() {

// 初始化区块链以及当前的交易信息列表

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类用来管理区块链,它能存储交易,加入新块等,下面我们来进一步完善这些方法。

区块的结构

首先需要说明一下区块的结构,每个区块包含属性:索引(index),时间戳(timestamp),交易列表(transactions),工作量证明(稍后解释)以及前一个区块的Hash值。

以下是一个区块的结构:

block = {

'index': 1,

'timestamp': 1506057125.900785,

'transactions': [

{

'sender': "8527147fe1f5426f9dd545de4b27ee00",

'recipient': "a77f5cdfa2934df3954a5c7c7da5df1f",

'amount': 5,

}

],

'proof': 324984774000,

'previous_hash': "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"

}

到这里,区块链的概念就清楚了,每个新的区块都包含上一个区块的Hash,这是关键的一点,它保障了区块链不可变性。如果***者破坏了前面的某个区块,那么后面所有区块的Hash都会变得不正确。不理解的话,慢慢消化,可以参考区块链记账原理(https://learnblockchain.cn/2017/10/25/whatbc/)。

由于需要计算区块的hash,所以我们得先编写一个用于计算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 加密开始

// 创建加密对象,传入加密类型

MessageDigest messageDigest = MessageDigest.getInstance(strType);

// 传入要加密的字符串

messageDigest.update(strText.getBytes());

// 得到 byte 数组

byte byteBuffer[] = messageDigest.digest();

// 將 byte 数组转换 string 类型

StringBuffer strHexString = new StringBuffer();

// 遍历 byte 数组

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

// 转换成16进制并存储在字符串中

String hex = Integer.toHexString(0xff & byteBuffer[i]);

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

strHexString.append('0');

}

strHexString.append(hex);

}

// 得到返回結果

strResult = strHexString.toString();

} catch (NoSuchAlgorithmException e) {

e.printStackTrace();

}

}

return strResult;

}

}

加入交易功能

接下来我们需要实现一个交易记账功能,所以来完善newTransactions以及lastBlock方法:

/**

* @return 得到区块链中的最后一个区块

*/

public HashMap<String, Object> lastBlock() {

return getChain().get(getChain().size() - 1);

}

/**

* 生成新交易信息,信息将加入到下一个待挖的区块中

*

* @param sender

* 发送方的地址

* @param recipient

* 接收方的地址

* @param amount

* 交易数量

* @return 返回存储该交易事务的块的索引

*/

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方法向列表中添加一个交易记录,并返回该记录将被添加到的区块 (下一个待挖掘的区块)的索引,等下在用户提交交易时会有用。

创建新块

当Blockchain实例化后,我们需要构造一个创世区块(没有前区块的第一个区块),并且给它加上一个工作量证明。

每个区块都需要经过工作量证明,俗称挖矿,稍后会继续讲解。

为了构造创世块,我们还需要完善剩下的几个方法,并且把该类设计为单例:

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 {

// 存储区块链

private List<Map<String, Object>> chain;

// 该实例变量用于当前的交易信息列表

private List<Map<String, Object>> currentTransactions;

private static BlockChain blockChain = null;

private BlockChain() {

// 初始化区块链以及当前的交易信息列表

chain = new ArrayList<Map<String, Object>>();

currentTransactions = new ArrayList<Map<String, Object>>();

// 创建创世区块

newBlock(100, "0");

}

// 创建单例对象

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 得到区块链中的最后一个区块

*/

public Map<String, Object> lastBlock() {

return getChain().get(getChain().size() - 1);

}

/**

* 在区块链上新建一个区块

*

* @param proof

* 新区块的工作量证明

* @param previous_hash

* 上一个区块的hash值

* @return 返回新建的区块

*/

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);

// 如果没有传递上一个区块的hash就计算出区块链中最后一个区块的hash

block.put("previous_hash", previous_hash != null ? previous_hash : hash(getChain().get(getChain().size() - 1)));

// 重置当前的交易信息列表

setCurrentTransactions(new ArrayList<Map<String, Object>>());

getChain().add(block);

return block;

}

/**

* 生成新交易信息,信息将加入到下一个待挖的区块中

*

* @param sender

* 发送方的地址

* @param recipient

* 接收方的地址

* @param amount

* 交易数量

* @return 返回该交易事务的块的索引

*/

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;

}

/**

* 生成区块的 SHA-256格式的 hash值

*

* @param block

* 区块

* @return 返回该区块的hash

*/

public static Object hash(Map<String, Object> block) {

return new Encrypt().getSHA256(new JSONObject(block).toString());

}

}

通过上面的代码和注释可以对区块链有直观的了解,接下来我们来编写一些简单的测试代码来测试一下这些代码能否正常工作:

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();

// 一个区块中可以不包含任何交易记录

Map<String, Object> block = blockChain.newBlock(300, null);

System.out.println(new JSONObject(block));

// 一个区块中可以包含一笔交易记录

blockChain.newTransactions("123", "222", 33);

Map<String, Object> block1 = blockChain.newBlock(500, null);

System.out.println(new JSONObject(block1));

// 一个区块中可以包含多笔交易记录

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));

// 查看整个区块链

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));

}

}

运行结果:

// 挖出来的新区块

{

"index": 2,

"transactions": [],

"proof": 300,

"timestamp": 1519478559703,

"previous_hash": "185b62ca1fc31285bce8878acfc970983cb561f19c63b65120d2c95148cf151f"

}

// 包含一笔交易的区块

{

"index": 3,

"transactions": [

{

"amount": 33,

"sender": "123",

"recipient": "222"

}

],

"proof": 500,

"timestamp": 1519478559728,

"previous_hash": "bce15693c0a028b1fc6d7d1c1d30494f97ef37b8b3384865559ceed9b5ff798b"

}

// 包含多笔交易的区块

{

"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"

}

// 整个区块链,第一个是创始区块

{

"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

}

通过以上的测试,可以很直观的看到区块链的数据,但是现在只是完成了初步的代码编写,还有几件事情还没做,接下来我们看看区块是怎么挖出来的。

理解工作量证明

新的区块依赖工作量证明算法(PoW)来构造。PoW的目标是找出一个符合特定条件的数字,这个数字很难计算出来,但容易验证。这就是工作量证明的核心思想。

为了方便理解,举个例子:

假设一个整数 x 乘以另一个整数 y 的积的 Hash 值必须以 0 结尾,即 hash(x * y) = ac23dc…0。设变量 x = 5,求 y 的值?

用Java实现如下:

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);

}

}

结果是 y=21 ,因为:

hash(5 * 21) = 1253e9373e...5e3600155e860

在比特币中,使用称为Hashcash的工作量证明算法,它和上面的问题很类似。矿工们为了争夺创建区块的权利而争相计算结果。通常,计算难度与目标字符串需要满足的特定字符的数量成正比,矿工算出结果后,会获得比特币奖励。

当然,在网络上非常容易验证这个结果。

实现工作量证明

让我们来实现一个相似PoW算法,规则是:寻找一个数 p,使得它与前一个区块的 proof 拼接成的字符串的 Hash 值以 4 个零开头:

...

/**

* 简单的工作量证明:

* - 查找一个 p' 使得 hash(pp') 以4个0开头

* - p 是上一个块的证明, p' 是当前的证明

*

* @param last_proof

* 上一个块的证明

* @return

*/

public long proofOfWork(long last_proof) {

long proof = 0;

while (!validProof(last_proof, proof)) {

proof += 1;

}

return proof;

}

/**

* 验证证明: 是否hash(last_proof, proof)以4个0开头?

*

* @param last_proof

* 上一个块的证明

* @param proof

* 当前的证明

* @return 以4个0开头返回true,否则返回false

*/

public boolean validProof(long last_proof, long proof) {

String guess = last_proof + "" + proof;

String guess_hash = new Encrypt().getSHA256(guess);

return guess_hash.startsWith("0000");

}

衡量算法复杂度的办法是修改零开头的个数。使用4个来用于演示,你会发现多一个零都会大大增加计算出结果所需的时间。

现在Blockchain类基本已经完成了,接下来使用Servlet接收HTTP请求来进行交互。

Blockchain作为API接口

我们将使用Java Web中的Servlet来接收用户的HTTP请求,通过Servlet我们可以方便的将网络请求的数据映射到相应的方法上进行处理,现在我们来让Blockchain运行在基于Java Web上。

我们将创建三个接口:

  • /transactions/new 创建一个交易并添加到区块
  • /mine 告诉服务器去挖掘新的区块
  • /chain 返回整个区块链

注册节点ID

我们的“Tomcat服务器”将扮演区块链网络中的一个节点,而每个节点都需要有一个唯一的标识符,也就是id。在这里我们使用UUID来作为节点ID,我们需要在服务器启动时,将UUID设置到ServletContext属性中,这样我们的服务器就拥有了唯一标识,这一步我们可以配置监听类来完成,首先配置web.xml文件内容如下:

<?xml version="1.0" encoding="UTF-8"?>

<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">

<listener>

<listener-class>org.zero01.servlet.InitialID</listener-class>

</listener>

</web-app>

然后编写一个类实现ServletContextListener接口,在初始化方法中把uuid设置到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) {

}

}

创建Servlet类

我们这里没有使用任何框架,所以我们需要通过最基本的Servlet来接收并处理用户的HTTP请求:

package org.zero01.servlet;

import java.io.IOException;

import javax.servlet.ServletException;

import javax.servlet.annotation.WebServlet;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

// 该Servlet用于运行工作算法的证明来获得下一个证明,也就是所谓的挖矿

@WebServlet("/mine")

public class Mine extends HttpServlet{

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

}

}

package org.zero01.servlet;

import java.io.IOException;

import javax.servlet.ServletException;

import javax.servlet.annotation.WebServlet;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

// 该Servlet用于接收并处理新的交易信息

@WebServlet("/transactions/new")

public class NewTransaction extends HttpServlet{

protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

}

}

package org.zero01.servlet;

import java.io.IOException;

import javax.servlet.ServletException;

import javax.servlet.annotation.WebServlet;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

// 该Servlet用于输出整个区块链的数据

@WebServlet("/chain")

public class FullChain extends HttpServlet{

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

}

}

我们先来完成最简单的FullChain的代码,这个Servlet用于向客户端输出整个区块链的数据(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用于输出整个区块链的数据

@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();

}

}

发送交易

然后是记录交易数据的功能,每一个区块都可以记录交易数据,发送到节点的交易数据结构如下:

{

"sender": "my address",

"recipient": "someone else's address",

"amount": 5

}

实现代码如下:

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");

// 读取客户端传递过来的数据并转换成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)) {

// 如果没有需要的字段就返回错误信息

resp.sendError(400, "Missing values");

}

}

// 新建交易信息

BlockChain blockChain = BlockChain.getInstance();

int index = blockChain.newTransactions(jsonValues.getString("sender"), jsonValues.getString("recipient"),

jsonValues.getLong("amount"));

// 返回json格式的数据给客户端

resp.setContentType("application/json");

PrintWriter printWriter = resp.getWriter();

printWriter.println(new JSONObject().append("message", "Transaction will be added to Block " + index));

printWriter.close();

}

}

挖矿

挖矿正是神奇所在,它很简单,只做了以下三件事:

  • 计算工作量证明PoW
  • 通过新增一个交易授予矿工(自己)一个币
  • 构造新区块并将其添加到链中

代码实现如下:

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用于运行工作算法的证明来获得下一个证明,也就是所谓的挖矿

@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);

// 给工作量证明的节点提供奖励,发送者为 "0" 表明是新挖出的币

String uuid = (String) this.getServletContext().getAttribute("uuid");

blockChain.newTransactions("0", uuid, 1);

// 构建新的区块

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"));

// 返回新区块的数据给客户端

resp.setContentType("application/json");

PrintWriter printWriter = resp.getWriter();

printWriter.println(new JSONObject(response));

printWriter.close();

}

}

注意交易的接收者是我们自己的服务器节点,我们做的大部分工作都只是围绕Blockchain类的方法进行交互。到此,我们的区块链就算完成了,我们来实际运行下。

运行区块链

由于我们这里也没有写前端的web页面,只写了后端的API,所以只能使用 Postman 之类的软件去和API进行交互。首先启动Tomcat服务器,然后通过post请求 http://localhost:8089/BlockChain_Java/transactions/new 来添加新的交易信息(注意我这里没有使用默认的8080端口,默认的情况下是8080端口):

但是这时候还没有新的区块可以写入这个交易信息,所以我们还需要请求 http://localhost:8089/BlockChain_Java/mine 来进行挖矿,挖出一个新的区块来存储这笔交易:

在挖了两次矿之后,就有3个块了,通过请求 http://localhost:8089/BlockChain_Java/chain 可以得到所有的区块块的信息:

{

"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

}

一致性(共识)

我们已经有了一个基本的区块链可以接受交易和挖矿。但是区块链系统应该是分布式的。既然是分布式的,那么我们究竟拿什么保证所有节点有同样的链呢?这就是一致性问题,我们要想在网络上有多个节点,就必须实现一个一致性的算法。

注册节点

在实现一致性算法之前,我们需要找到一种方式让一个节点知道它相邻的节点。每个节点都需要保存一份包含网络中其它节点的记录。因此让我们新增几个接口:

  • /nodes/register 接收URL形式的新节点列表
  • /nodes/resolve执行一致性算法,解决任何冲突,确保节点拥有正确的链

我们需要修改下BlockChain的构造函数并提供一个注册节点方法:

package org.zero01.core;

...

import java.net.URL;

...

private Set<String> nodes;

private BlockChain() {

...

// 用于存储网络中其他节点的集合

nodes = new HashSet<String>();

...

}

public Set<String> getNodes() {

return nodes;

}

/**

* 注册节点

*

* @param address

* 节点地址

* @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 集合来储存节点,这是一种避免出现重复添加节点的简单方法。

实现共识算法

前面提到,冲突是指不同的节点拥有不同的链,为了解决这个问题,规定最长的、有效的链才是最终的链,换句话说,网络中有效最长链才是实际的链。

我们使用以下算法,来达到网络中的共识:

...

import java.net.HttpURLConnection;

import java.net.MalformedURLException;

import java.net.URL;

...

public class BlockChain {

...

/**

* 检查是否是有效链,遍历每个区块验证hash和proof,来确定一个给定的区块链是否有效

*

* @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(" ------------------------- ");

// 检查block的hash是否正确

if (!block.get("previous_hash").equals(hash(lastBlock))) {

return false;

}

lastBlock = block;

currentIndex++;

}

return true;

}

/**

* 共识算法解决冲突,使用网络中最长的链. 遍历所有的邻居节点,并用上一个方法检查链的有效性, 如果发现有效更长链,就替换掉自己的链

*

* @return 如果链被取代返回true, 否则返回false

* @throws IOException

*/

public boolean resolveConflicts() throws IOException {

Set<String> neighbours = this.nodes;

List<Map<String, Object>> newChain = null;

// 寻找最长的区块链

long maxLength = this.chain.size();

// 获取并验证网络中的所有节点的区块链

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();

// 检查长度是否长,链是否有效

if (length > maxLength && validChain(chain)) {

maxLength = length;

newChain = chain;

}

}

}

// 如果发现一个新的有效链比我们的长,就替换当前的链

if (newChain != null) {

this.chain = newChain;

return true;

}

return false;

}

...

第一个方法 validChain() 用来检查是否是有效链,遍历每个块验证hash和proof.

第2个方法 resolveConflicts() 用来解决冲突,遍历所有的邻居节点,并用上一个方法检查链的有效性, 如果发现有效更长链,就替换掉自己的链

让我们添加两个Servlet,一个用来注册节点,一个用来解决冲突:

我们可以在不同的机器运行节点,或在一台机机开启不同的网络端口来模拟多节点的网络,这里在同一台机器开启不同的端口演示,配置两个不同端口的服务器即可,我这里启动了两个节点:http://localhost:8089 和 http://localhost:8066。

两个节点互相进行注册:

然后在8066节点上挖两个块,确保是更长的链:

接着在8089节点上访问接口/nodes/resolve ,这时8089节点的链会通过共识算法被8066节点的链取代:

通过共识算法保持一致性后,两个节点的区块链数据就都是一致的了:

到此为止我们就完成了一个区块链的开发,虽然这只是一个最基本的区块链,而且在开发的过程中也没有考虑太多的程序设计方面的问题,而是以最基本、原始的方式进行开发的。但是我们不妨以这个简单的区块链为基础,发挥自己的能力动手去重构、扩展、完善这个区块链程序,直至成为自己的一个小项目。

挖矿区块链_使用Java语言从零开始创建区块链相关推荐

  1. java开发区块链_使用Java语言从零开始创建区块链

    目前网络上关于区块链入门.科普的文章不少,本文就不再赘述区块链的基本概念了,如果对区块链不是很了解的话,可以看一下我之前收集的一些入门学习资源: 对区块链技术感到新奇的我们,都想知道区块链在代码上是怎 ...

  2. 使用Java语言从零开始创建区块链

    使用Java语言从零开始创建区块链 2018年04月01日 17:08:12 大侠区块链 阅读数:1312 标签: java区块链java区块链 更多 个人分类: 区块链 Java区块链开发与交流群: ...

  3. 使用Java语言从零开始创建区块连

    使用Java语言从零开始创建区块连: http://developer.51cto.com/art/201803/568032.htm

  4. 带你挖矿之旅!Python从零开始创建区块链!提供源码哦!月薪十万

    环境准备 确保已经安装Python3.6+, pip , Flask, requests,安装方法: pip install Flask==0.12.2 requests==2.18.4 同时还需要一 ...

  5. 用Python从零开始创建区块链

    链客,专为开发者而生,有问必答! 此文章来自区块链技术社区,未经允许拒绝转载. 前言 如果你还没有听说过 3 点钟区块链群,说明你还不是链圈的人:如果你还没有加入 3 点钟区块链群,说明你还不是链圈的 ...

  6. gossip 区块链_源代码: 一个最小化的区块链系统

    近期有个国内著名技术协会的约稿,正好向技术圈分享一下我对区块链系统的拙见.我发现一件有趣的事情,即使是有计算机背景,懂编程的同学,都也不怎么清楚区块链到底是怎么回事.今天这里,我打算用计算机语言和大家 ...

  7. 看完此文再不懂区块链算我输:手把手教你用Python从零开始创建区块链

    导读:如果你还没有听说过 3 点钟区块链群,说明你还不是链圈的人:如果你还没有加入 3 点钟区块链群,说明你还不是链圈的大佬:如果你还没有被 3 点钟区块链群刷屏,说明你还体会不到什么是"币 ...

  8. java语言程序设计答案_《java语言程序设计》练习题及答案

    <java语言程序设计>练习题及答案 JAVA 语言程序设计题及部分答案 一.单选题:(每题1分)下列各题A).B).C).D)四个选项中, 只有一个选项是正确的,请将正确选项的标记写在题 ...

  9. 在Xuper链上部署Java语言智能合约和分析存证合约的实现逻辑

    前言 这篇文章咱们先简单的叙述下官方刚发布的最新版本中的native部署java语言编写的智能合约的过程然后再说下存证合约的代码实现逻辑,下一篇文章咱们说下如何根据自己公司的业务逻辑定义合约里面的数据 ...

最新文章

  1. LeetCode Linked List Cycle II
  2. 理科大学二本计算机系,大学最好就业的二本专业:高校男生女生二本理科比较好的专业...
  3. duilib学习领悟(4)
  4. 成本计算引擎动态规则解析技术详解
  5. 《COM原理与应用》学习笔记二——COM对象和COM接口的实现
  6. shell 提示符个性化设置
  7. spring框架中Bean的基本属性及调用外部properties等配置文件的方法介绍
  8. Selenium自动化测试-3.元素定位(3)
  9. 《The C programming language》学习笔记
  10. Java跨域问题以及如何使用Cors解决前后端 分离部署项目所遇到的跨域问题
  11. 基于FPGA 的CRC校验码生成器
  12. 干活,分享!!三套简单有趣的后台登录页面模板分享
  13. jQuery实现打地鼠游戏
  14. java 小技巧_Java中有哪些好用的小技巧?
  15. HDU-4826 Labyrinth(DP)
  16. ThreadAbortException问题
  17. 南下打工潮正在远去!去年千万人返乡,农业的大转折正在到来
  18. vue两个按钮切换_vue点击循环 添加列表 点击来回切换
  19. ipad协议/8.0.37/个微协议
  20. MySQL查询出的时间与实际时间相差八小时

热门文章

  1. Kerberos 基本命令 - 持续更新
  2. ubuntu16.04 计算视觉算法相关软件安装 亲测可用
  3. 后疫情时代企业将加速向云服务迁移
  4. 如何将数据仓库从 AWS Redshift 迁移到阿里云 AnalyticDB for PostgreSQL
  5. K8s中Pod健康检查源代码分析
  6. 安全看得见,阿里云性能监控 ARMS 全真3D拓扑实现一“屏”了然
  7. 【MPS最佳实践】媒体工作流转码
  8. 阿里云容器服务新增支持Kubernetes编排系统,性能重大提升 1
  9. 阿里云智能总裁张建锋:保护客户数据安全是第一原则
  10. Trie 树是什么样的数据结构?有哪些应用场景?