hejin 9 maanden geleden
commit
a4063366e8

+ 97 - 0
pom.xml

@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>2.2.1.RELEASE</version>
+        <relativePath/> <!-- lookup parent from repository -->
+    </parent>
+    <groupId>com.dce</groupId>
+    <artifactId>dce-blockchain</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <name>dce-blockchain</name>
+    <description>Blockchain Demo project for Java</description>
+
+    <properties>
+        <java.version>1.8</java.version>
+    </properties>
+
+    <packaging>jar</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-thymeleaf</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-websocket</artifactId>
+        </dependency>
+		<dependency>
+            <groupId>org.java-websocket</groupId>
+            <artifactId>Java-WebSocket</artifactId>
+            <version>1.3.4</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-devtools</artifactId>
+            <scope>runtime</scope>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-configuration-processor</artifactId>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+            <version>1.2.62</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <finalName>dce-blockchain</finalName>
+
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+            </plugin>
+        </plugins>
+
+        <resources>
+            <resource>
+                <directory>src/main/java</directory>
+                <includes>
+                    <include>**/*.xml</include>
+                </includes>
+            </resource>
+            <!-- 将resources目录下的配置文件编译进classes文件 -->
+            <resource>
+                <directory>src/main/resources</directory>
+            </resource>
+        </resources>
+    </build>
+
+</project>

+ 13 - 0
src/main/java/com/dce/blockchain/DceBlockchainApplication.java

@@ -0,0 +1,13 @@
+package com.dce.blockchain;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class DceBlockchainApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(DceBlockchainApplication.class, args);
+    }
+
+}

+ 68 - 0
src/main/java/com/dce/blockchain/web/controller/BlockController.java

@@ -0,0 +1,68 @@
+package com.dce.blockchain.web.controller;
+
+import javax.annotation.Resource;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import com.alibaba.fastjson.JSON;
+import com.dce.blockchain.web.service.BlockService;
+import com.dce.blockchain.web.service.PowService;
+import com.dce.blockchain.web.util.BlockCache;
+
+@Controller
+public class BlockController {
+
+	@Resource
+	BlockService blockService;
+	
+	@Resource
+	PowService powService;
+	
+	@Autowired
+	BlockCache blockCache;
+	
+	/**
+	 * 查看当前节点区块链数据
+	 * @return
+	 */
+	@GetMapping("/scan")
+	@ResponseBody
+	public String scanBlock() {
+		return JSON.toJSONString(blockCache.getBlockChain());
+	}
+	
+	/**
+	 * 查看当前节点区块链数据
+	 * @return
+	 */
+	@GetMapping("/data")
+	@ResponseBody
+	public String scanData() {
+		return JSON.toJSONString(blockCache.getPackedTransactions());
+	}
+	
+	/**
+	 * 创建创世区块
+	 * @return
+	 */
+	@GetMapping("/create")
+	@ResponseBody
+	public String createFirstBlock() {
+		blockService.createGenesisBlock();
+		return JSON.toJSONString(blockCache.getBlockChain());
+	}
+	
+	/**
+	 * 工作量证明PoW
+	 * 挖矿生成新的区块 
+	 */
+	@GetMapping("/mine")
+	@ResponseBody
+	public String createNewBlock() {
+		powService.mine();
+		return JSON.toJSONString(blockCache.getBlockChain());
+	}
+}

+ 87 - 0
src/main/java/com/dce/blockchain/web/model/Block.java

@@ -0,0 +1,87 @@
+package com.dce.blockchain.web.model;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 区块结构
+ * 
+ * @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;
+
+	public int getIndex() {
+		return index;
+	}
+
+	public void setIndex(int index) {
+		this.index = index;
+	}
+
+	public long getTimestamp() {
+		return timestamp;
+	}
+
+	public void setTimestamp(long timestamp) {
+		this.timestamp = timestamp;
+	}
+
+	public List<Transaction> getTransactions() {
+		return transactions;
+	}
+
+	public void setTransactions(List<Transaction> transactions) {
+		this.transactions = transactions;
+	}
+
+	public int getNonce() {
+		return nonce;
+	}
+
+	public void setNonce(int nonce) {
+		this.nonce = nonce;
+	}
+
+	public String getPreviousHash() {
+		return previousHash;
+	}
+
+	public void setPreviousHash(String previousHash) {
+		this.previousHash = previousHash;
+	}
+
+	public String getHash() {
+		return hash;
+	}
+
+	public void setHash(String hash) {
+		this.hash = hash;
+	}
+}

+ 50 - 0
src/main/java/com/dce/blockchain/web/model/Message.java

@@ -0,0 +1,50 @@
+package com.dce.blockchain.web.model;
+
+import java.io.Serializable;
+
+/**
+ * p2p通讯消息
+ *
+ * @author Jared Jia
+ * 
+ */
+public class Message implements Serializable {
+	
+	private static final long serialVersionUID = 1L;
+	/**
+	 * 消息类型
+	 */
+	private int type;
+	/**
+	 * 消息内容
+	 */
+	private String data;
+
+	public Message() {
+	}
+
+	public Message(int type) {
+		this.type = type;
+	}
+
+	public Message(int type, String data) {
+		this.type = type;
+		this.data = data;
+	}
+
+	public int getType() {
+		return type;
+	}
+
+	public void setType(int type) {
+		this.type = type;
+	}
+
+	public String getData() {
+		return data;
+	}
+
+	public void setData(String data) {
+		this.data = data;
+	}
+}

+ 36 - 0
src/main/java/com/dce/blockchain/web/model/Transaction.java

@@ -0,0 +1,36 @@
+package com.dce.blockchain.web.model;
+
+import java.io.Serializable;
+
+/**
+ * 业务数据模型
+ * 
+ * @author Jared Jia
+ *
+ */
+public class Transaction implements Serializable {
+
+	private static final long serialVersionUID = 1L;
+	/**
+	 * 唯一标识
+	 */
+	private String id;
+	/**
+	 * 业务数据
+	 */
+	private String businessInfo;
+	
+	public String getId() {
+		return id;
+	}
+	public void setId(String id) {
+		this.id = id;
+	}
+	public String getBusinessInfo() {
+		return businessInfo;
+	}
+	public void setBusinessInfo(String businessInfo) {
+		this.businessInfo = businessInfo;
+	}
+
+}

+ 191 - 0
src/main/java/com/dce/blockchain/web/service/BlockService.java

@@ -0,0 +1,191 @@
+package com.dce.blockchain.web.service;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import com.alibaba.fastjson.JSON;
+import com.dce.blockchain.web.model.Block;
+import com.dce.blockchain.web.model.Transaction;
+import com.dce.blockchain.web.util.BlockCache;
+import com.dce.blockchain.web.util.CryptoUtil;
+
+/**
+ * 区块链核心服务
+ *
+ * @author Jared Jia
+ *
+ */
+@Service
+public 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("这是创世区块2");
+		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;
+	}
+
+	/**
+	 * 验证hash值是否满足系统条件
+	 *
+	 * @param hash
+	 * @return
+	 */
+	public boolean isValidHash(String hash) {
+		return hash.startsWith("0000");
+	}
+
+	/**
+	 * 验证整个区块链是否有效
+	 * @param chain
+	 * @return
+	 */
+	public boolean isValidChain(List<Block> chain) {
+		Block block = null;
+		Block lastBlock = chain.get(0);
+		int currentIndex = 1;
+		while (currentIndex < chain.size()) {
+			block = chain.get(currentIndex);
+
+			if (!isValidNewBlock(block, lastBlock)) {
+				return false;
+			}
+
+			lastBlock = block;
+			currentIndex++;
+		}
+		return true;
+	}
+
+	/**
+	 * 替换本地区块链
+	 *
+	 * @param newBlocks
+	 */
+	public void replaceChain(List<Block> newBlocks) {
+		List<Block> localBlockChain = blockCache.getBlockChain();
+		List<Transaction> localpackedTransactions = blockCache.getPackedTransactions();
+		if (isValidChain(newBlocks) && newBlocks.size() > localBlockChain.size()) {
+			localBlockChain = newBlocks;
+			//替换已打包保存的业务数据集合
+			localpackedTransactions.clear();
+			localBlockChain.forEach(block -> {
+				localpackedTransactions.addAll(block.getTransactions());
+			});
+			blockCache.setBlockChain(localBlockChain);
+			blockCache.setPackedTransactions(localpackedTransactions);
+			System.out.println("替换后的本节点区块链:"+JSON.toJSONString(blockCache.getBlockChain()));
+		} else {
+			System.out.println("接收的区块链无效");
+		}
+	}
+
+	/**
+	 * 计算区块的hash
+	 *
+	 * @param previousHash
+	 * @param currentTransactions
+	 * @param nonce
+	 * @return
+	 */
+	public String calculateHash(String previousHash, List<Transaction> currentTransactions, int nonce) {
+		return CryptoUtil.SHA256(previousHash + JSON.toJSONString(currentTransactions) + nonce);
+	}
+
+}

+ 230 - 0
src/main/java/com/dce/blockchain/web/service/P2PService.java

@@ -0,0 +1,230 @@
+package com.dce.blockchain.web.service;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import org.java_websocket.WebSocket;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+import com.alibaba.fastjson.JSON;
+import com.dce.blockchain.web.model.Block;
+import com.dce.blockchain.web.model.Message;
+import com.dce.blockchain.web.util.BlockCache;
+import com.dce.blockchain.web.util.BlockConstant;
+import com.dce.blockchain.websocket.P2PClient;
+import com.dce.blockchain.websocket.P2PServer;
+
+/**
+ * p2p网络服务类
+ * 
+ * @author Jared Jia
+ *
+ */
+@Service
+public class P2PService implements ApplicationRunner {
+	
+	@Autowired
+	BlockService blockService;
+	
+	@Autowired
+	BlockCache blockCache;
+	
+	@Autowired
+	P2PServer p2PServer;
+	
+	@Autowired
+	P2PClient p2PClient;
+
+	/**
+	 * 客户端和服务端共用的消息处理方法
+	 * @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());
+		}
+	}
+
+	/**
+	 * 处理其它节点发送过来的区块信息
+	 * @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);
+					}
+				}
+			}
+		}
+	}
+	
+	/**
+	 * 全网广播消息
+	 * @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("======全网广播消息结束");
+	}
+	
+	/**
+	 * 向其它节点发送消息
+	 * @param ws
+	 * @param message
+	 */
+	public void write(WebSocket ws, String message) {
+		System.out.println("发送给IP地址为:" +ws.getRemoteSocketAddress().getAddress().toString() 
+			+ ",端口号为:"+ws.getRemoteSocketAddress().getPort() + " 的p2p消息:" + message);
+		ws.send(message);
+	}
+
+	/**
+	 * 查询整条区块链
+	 * @return
+	 */
+	public String queryBlockChainMsg() {
+		return JSON.toJSONString(new Message(BlockConstant.QUERY_BLOCKCHAIN));
+	}
+	
+	/**
+	 * 返回整条区块链数据
+	 * @return
+	 */
+	public String responseBlockChainMsg() {
+		Message msg = new Message();
+		msg.setType(BlockConstant.RESPONSE_BLOCKCHAIN);
+		msg.setData(JSON.toJSONString(blockCache.getBlockChain()));
+		return JSON.toJSONString(msg);
+	}
+
+	/**
+	 * 查询最新的区块
+	 * @return
+	 */
+	public String queryLatestBlockMsg() {
+		return JSON.toJSONString(new Message(BlockConstant.QUERY_LATEST_BLOCK));
+	}
+	
+	/**
+	 * 返回最新的区块
+	 * @return
+	 */
+	public String responseLatestBlockMsg() {
+		Message msg = new Message();
+		msg.setType(BlockConstant.RESPONSE_LATEST_BLOCK);
+		Block b = blockCache.getLatestBlock();
+		msg.setData(JSON.toJSONString(b));
+		return JSON.toJSONString(msg);
+	}
+	
+	public List<WebSocket> getSockets(){
+		return blockCache.getSocketsList();
+	}
+
+	@Override
+	public void run(ApplicationArguments args) throws Exception {
+		p2PServer.initP2PServer(blockCache.getP2pport());
+		p2PClient.connectToPeer(blockCache.getAddress());
+		System.out.println("*****难度系数******"+blockCache.getDifficulty());
+		System.out.println("*****端口号******"+blockCache.getP2pport());
+		System.out.println("*****节点地址******"+blockCache.getAddress());
+		
+	}
+	
+}

+ 81 - 0
src/main/java/com/dce/blockchain/web/service/PowService.java

@@ -0,0 +1,81 @@
+package com.dce.blockchain.web.service;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.alibaba.fastjson.JSON;
+import com.dce.blockchain.web.model.Block;
+import com.dce.blockchain.web.model.Message;
+import com.dce.blockchain.web.model.Transaction;
+import com.dce.blockchain.web.util.BlockCache;
+import com.dce.blockchain.web.util.BlockConstant;
+import com.dce.blockchain.web.util.CommonUtil;
+
+/**
+ * 共识机制
+ * 采用POW即工作量证明实现共识
+ * @author Administrator
+ *
+ */
+@Service
+public class PowService {
+
+	@Autowired
+	BlockCache blockCache;
+	
+	@Autowired
+	BlockService blockService;
+	
+	@Autowired
+	P2PService p2PService;
+	
+	/**
+	 * 通过“挖矿”进行工作量证明,实现节点间的共识
+	 * 
+	 * @return
+	 * @throws UnknownHostException
+	 */
+	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);
+		
+		//创建成功后,全网广播出去
+		Message msg = new Message();
+		msg.setType(BlockConstant.RESPONSE_LATEST_BLOCK);
+		msg.setData(JSON.toJSONString(block));
+		p2PService.broatcast(JSON.toJSONString(msg));
+		
+		return block;
+	}
+	
+}

+ 106 - 0
src/main/java/com/dce/blockchain/web/util/BlockCache.java

@@ -0,0 +1,106 @@
+package com.dce.blockchain.web.util;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import org.java_websocket.WebSocket;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+import com.dce.blockchain.web.model.Block;
+import com.dce.blockchain.web.model.Transaction;
+
+@ConfigurationProperties(prefix = "block")
+@Component
+public class BlockCache {
+
+	/**
+	 * 当前节点的区块链结构
+	 */
+	private List<Block> blockChain = new CopyOnWriteArrayList<Block>();
+
+	/**
+	 * 已打包保存的业务数据集合
+	 */
+	private List<Transaction> packedTransactions = new CopyOnWriteArrayList<Transaction>();
+	
+	/**
+	 * 当前节点的socket对象
+	 */
+	private List<WebSocket> socketsList = new CopyOnWriteArrayList<WebSocket>();
+	
+	/**
+	 * 挖矿的难度系数
+	 */
+	@Value("${block.difficulty}")
+	private int difficulty;
+	
+	/**
+	 * 当前节点p2pserver端口号
+	 */
+	@Value("${block.p2pport}")
+	private int p2pport;
+	
+	/**
+	 * 要连接的节点地址
+	 */
+	@Value("${block.address}")
+	private String address;
+	
+	/**
+	 * 获取最新的区块,即当前链上最后一个区块
+	 * 
+	 * @return
+	 */
+	public Block getLatestBlock() {
+		return blockChain.size() > 0 ? blockChain.get(blockChain.size() - 1) : null;
+	}
+
+	public List<Block> getBlockChain() {
+		return blockChain;
+	}
+
+	public void setBlockChain(List<Block> blockChain) {
+		this.blockChain = blockChain;
+	}
+
+	public List<Transaction> getPackedTransactions() {
+		return packedTransactions;
+	}
+
+	public void setPackedTransactions(List<Transaction> packedTransactions) {
+		this.packedTransactions = packedTransactions;
+	}
+
+	public int getDifficulty() {
+		return difficulty;
+	}
+
+	public void setDifficulty(int difficulty) {
+		this.difficulty = difficulty;
+	}
+
+	public List<WebSocket> getSocketsList() {
+		return socketsList;
+	}
+
+	public void setSocketsList(List<WebSocket> socketsList) {
+		this.socketsList = socketsList;
+	}
+
+	public int getP2pport() {
+		return p2pport;
+	}
+
+	public void setP2pport(int p2pport) {
+		this.p2pport = p2pport;
+	}
+
+	public String getAddress() {
+		return address;
+	}
+
+	public void setAddress(String address) {
+		this.address = address;
+	}
+
+}

+ 23 - 0
src/main/java/com/dce/blockchain/web/util/BlockConstant.java

@@ -0,0 +1,23 @@
+package com.dce.blockchain.web.util;
+
+/**
+ * 常量工具类
+ * 
+ * @author JaredJia
+ *
+ */
+public class BlockConstant {
+
+	// 查询最新的区块
+	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;
+
+}

+ 117 - 0
src/main/java/com/dce/blockchain/web/util/Coder.java

@@ -0,0 +1,117 @@
+package com.dce.blockchain.web.util;
+
+import java.security.MessageDigest;
+import java.util.Base64;
+
+import javax.crypto.KeyGenerator;
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * 基础加密组件
+ *
+ * @author Jared Jia
+ * 
+ */
+public abstract class Coder {
+    public static final String KEY_SHA = "SHA";
+    public static final String KEY_MD5 = "MD5";
+
+    /**
+     * MAC算法可选以下多种算法
+     *
+     * <pre>
+     * HmacMD5
+     * HmacSHA256
+     * </pre>
+     */
+    public static final String KEY_MAC = "HmacMD5";
+
+    /**
+     * BASE64解码
+     *
+     * @param key
+     * @return
+     * @throws Exception
+     */
+    public static byte[] decryptBASE64(String key) throws Exception {
+    	Base64.Decoder decoder = Base64.getDecoder();
+        return decoder.decode(key);
+    }
+
+    /**
+     * BASE64编码
+     *
+     * @param key
+     * @return
+     * @throws Exception
+     */
+    public static String encryptBASE64(byte[] key) throws Exception {
+    	Base64.Encoder encoder = Base64.getEncoder();
+        return encoder.encodeToString(key);
+    }
+
+    /**
+     * MD5加密
+     *
+     * @param data
+     * @return
+     * @throws Exception
+     */
+    public static byte[] encryptMD5(byte[] data) throws Exception {
+
+        MessageDigest md5 = MessageDigest.getInstance(KEY_MD5);
+        md5.update(data);
+
+        return md5.digest();
+
+    }
+
+    /**
+     * SHA加密
+     *
+     * @param data
+     * @return
+     * @throws Exception
+     */
+    public static byte[] encryptSHA(byte[] data) throws Exception {
+
+        MessageDigest sha = MessageDigest.getInstance(KEY_SHA);
+        sha.update(data);
+
+        return sha.digest();
+
+    }
+
+    /**
+     * 初始化HMAC密钥
+     *
+     * @return
+     * @throws Exception
+     */
+    public static String initMacKey() throws Exception {
+        KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_MAC);
+
+        SecretKey secretKey = keyGenerator.generateKey();
+        return encryptBASE64(secretKey.getEncoded());
+    }
+
+    /**
+     * HMAC加密
+     *
+     * @param data
+     * @param key
+     * @return
+     * @throws Exception
+     */
+    public static byte[] encryptHMAC(byte[] data, String key) throws Exception {
+
+        SecretKey secretKey = new SecretKeySpec(decryptBASE64(key), KEY_MAC);
+        Mac mac = Mac.getInstance(secretKey.getAlgorithm());
+        mac.init(secretKey);
+
+        return mac.doFinal(data);
+
+    }
+}

+ 21 - 0
src/main/java/com/dce/blockchain/web/util/CommonUtil.java

@@ -0,0 +1,21 @@
+package com.dce.blockchain.web.util;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+public class CommonUtil{
+
+    /**
+     * 获取本地ip
+     * @return
+     */
+    public static String getLocalIp() {
+		try {
+            InetAddress ip4 = InetAddress.getLocalHost();
+            return ip4.getHostAddress();
+		} catch (UnknownHostException e) {
+			e.printStackTrace();
+        }
+        return "";
+    }
+}

+ 66 - 0
src/main/java/com/dce/blockchain/web/util/CryptoUtil.java

@@ -0,0 +1,66 @@
+package com.dce.blockchain.web.util;
+
+import java.security.MessageDigest;
+import java.util.UUID;
+import org.springframework.util.DigestUtils;
+
+
+/**
+ * 密码学工具类
+ *
+ * @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();
+	}
+
+	public static String MD5(String str) {
+		String resultStr = DigestUtils.md5DigestAsHex(str.getBytes());
+		return resultStr.substring(4, resultStr.length());
+	}
+
+	public static String UUID() {
+		return UUID.randomUUID().toString().replaceAll("\\-", "");
+	}
+
+
+	public static void main(String[] args) {
+		String aa = "是的发顺丰撒;老地方撒撒发23424";
+
+		for (int i=1;i<=100;i++) {
+			System.out.println(SHA256(aa));
+		}
+
+	}
+
+}

+ 62 - 0
src/main/java/com/dce/blockchain/websocket/P2PClient.java

@@ -0,0 +1,62 @@
+package com.dce.blockchain.websocket;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import org.java_websocket.client.WebSocketClient;
+import org.java_websocket.handshake.ServerHandshake;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.dce.blockchain.web.service.P2PService;
+
+/**
+ * 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());
+		}
+	}
+
+}

+ 73 - 0
src/main/java/com/dce/blockchain/websocket/P2PServer.java

@@ -0,0 +1,73 @@
+package com.dce.blockchain.websocket;
+
+import java.net.InetSocketAddress;
+
+import org.java_websocket.WebSocket;
+import org.java_websocket.handshake.ClientHandshake;
+import org.java_websocket.server.WebSocketServer;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.dce.blockchain.web.service.P2PService;
+
+/**
+ * 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);
+	}
+
+}

+ 12 - 0
src/main/resources/application.yml

@@ -0,0 +1,12 @@
+server:
+    port: 8090
+
+block:
+    ##设定哈希值前difficulty位都是0时,满足计算结果
+    difficulty: 4
+    ##当前节点p2pserver端口号
+    p2pport: 7002
+    ##要连接的节点地址
+    address: ws://192.168.0.68:7001
+
+