diff --git a/.gitignore b/.gitignore
new file mode 100755
index 0000000..5ff6309
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,38 @@
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### IntelliJ IDEA ###
+.idea/modules.xml
+.idea/jarRepositories.xml
+.idea/compiler.xml
+.idea/libraries/
+*.iws
+*.iml
+*.ipr
+
+### Eclipse ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+
+### Mac OS ###
+.DS_Store
\ No newline at end of file
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100755
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/checkstyle-idea.xml b/.idea/checkstyle-idea.xml
new file mode 100755
index 0000000..d8d6852
--- /dev/null
+++ b/.idea/checkstyle-idea.xml
@@ -0,0 +1,15 @@
+
+
+
+ 10.3.4
+ JavaOnly
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
new file mode 100755
index 0000000..aa00ffa
--- /dev/null
+++ b/.idea/encodings.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100755
index 0000000..79a352f
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml
new file mode 100755
index 0000000..2b63946
--- /dev/null
+++ b/.idea/uiDesigner.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100755
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/blockchain.sh b/blockchain.sh
new file mode 100755
index 0000000..69b3e33
--- /dev/null
+++ b/blockchain.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+set -e
+# Check if the jar has been built.
+if [ ! -e target/blockchain-jar-with-dependencies.jar ]; then
+ echo "Compiling blockchain project to a JAR"
+ mvn package -DskipTests
+fi
+java -jar target/blockchain-jar-with-dependencies.jar "$@"
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100755
index 0000000..bccbd72
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,124 @@
+
+
+
+ 4.0.0
+
+ org.example
+ blockchain
+ 1.0-SNAPSHOT
+ jar
+
+ blockchain Maven Webapp
+
+ http://www.example.com
+
+
+ UTF-8
+ 1.8
+ 1.8
+
+
+
+
+ junit
+ junit
+ 4.11
+ test
+
+
+ org.projectlombok
+ lombok
+ 1.16.20
+
+
+ org.apache.commons
+ commons-lang3
+ 3.7
+
+
+ commons-codec
+ commons-codec
+ 1.11
+
+
+ commons-cli
+ commons-cli
+ 1.4
+
+
+ com.esotericsoftware
+ kryo
+ 4.0.1
+
+
+ org.rocksdb
+ rocksdbjni
+ 5.9.2
+
+
+
+
+ blockchain
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+ 3.1.0
+
+
+
+ true
+ lib/
+ com.example.blockchain.Main
+
+
+
+ jar-with-dependencies
+
+
+
+
+ make-assembly
+
+ package
+
+
+ single
+
+
+
+
+
+
+
diff --git a/src/main/java/com/example/blockchain/Main.java b/src/main/java/com/example/blockchain/Main.java
new file mode 100755
index 0000000..a107905
--- /dev/null
+++ b/src/main/java/com/example/blockchain/Main.java
@@ -0,0 +1,10 @@
+package com.example.blockchain;
+
+import com.example.blockchain.cli.CLI;
+
+public class Main {
+ public static void main(String[] args) {
+ CLI cli = new CLI(args);
+ cli.run();
+ }
+}
diff --git a/src/main/java/com/example/blockchain/block/Block.java b/src/main/java/com/example/blockchain/block/Block.java
new file mode 100755
index 0000000..99e0211
--- /dev/null
+++ b/src/main/java/com/example/blockchain/block/Block.java
@@ -0,0 +1,77 @@
+package com.example.blockchain.block;
+
+import com.example.blockchain.pow.PowResult;
+import com.example.blockchain.pow.ProofOfWork;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.apache.commons.codec.binary.Hex;
+
+import java.time.Instant;
+
+@AllArgsConstructor
+@NoArgsConstructor
+@Data
+public class Block {
+
+ /**
+ * 区块hash值
+ */
+ private String hash;
+
+ /**
+ * 前一个区块的hash值
+ */
+ private String prevBlockHash;
+
+ /**
+ * 区块数据,未来会替换为交易
+ */
+ private String data;
+
+ /**
+ * 时间戳,单位秒
+ */
+ private long timeStamp;
+
+ /**
+ * 区块的高度
+ */
+ private long height;
+
+ /**
+ * 工作量证明计数器
+ */
+ private long nonce;
+
+
+
+
+ /**
+ * 创建新的区块
+ *
+ * @param previousHash
+ * @param data
+ * @return
+ */
+ public static Block newBlock(String previousHash, String data,long height) {
+ Block block = new Block("", previousHash, data, Instant.now().getEpochSecond(),height,0);
+ ProofOfWork pow = ProofOfWork.newProofOfWork(block);
+ PowResult powResult = pow.run();
+ block.setHash(powResult.getHash());
+ block.setNonce(powResult.getNonce());
+ return block;
+ }
+
+ private static final String ZERO_HASH = Hex.encodeHexString(new byte[32]);
+
+ /**
+ * 创建创世区块
+ * @return
+ */
+ public static Block newGenesisBlock() {
+ return Block.newBlock(ZERO_HASH, "Genesis Block",0);
+ }
+
+
+}
diff --git a/src/main/java/com/example/blockchain/block/Blockchain.java b/src/main/java/com/example/blockchain/block/Blockchain.java
new file mode 100755
index 0000000..93e420e
--- /dev/null
+++ b/src/main/java/com/example/blockchain/block/Blockchain.java
@@ -0,0 +1,129 @@
+package com.example.blockchain.block;
+
+import com.example.blockchain.store.RocksDBUtils;
+import com.example.blockchain.util.ByteUtils;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import org.apache.commons.lang3.StringUtils;
+
+@Data
+@AllArgsConstructor
+public class Blockchain {
+
+
+ /**
+ * 最后一个区块的hash
+ */
+ private String lastBlockHash;
+
+
+ /**
+ * 创建区块链
+ * @return
+ */
+ public static Blockchain newBlockchain() {
+
+ String lastBlockHash = RocksDBUtils.getInstance().getLastBlockHash();
+ if (StringUtils.isBlank(lastBlockHash)){
+ //对应的bucket不存在,说明是第一次获取区块链实例
+ Block genesisBlock = Block.newGenesisBlock();
+ lastBlockHash = genesisBlock.getHash();
+ RocksDBUtils.getInstance().putBlock(genesisBlock);
+ RocksDBUtils.getInstance().putLastBlockHash(lastBlockHash);
+
+ }
+ return new Blockchain(lastBlockHash);
+ }
+
+ /**
+ * 根据block,添加区块
+ * @param block
+ */
+ public void addBlock(Block block) {
+
+ RocksDBUtils.getInstance().putLastBlockHash(block.getHash());
+ RocksDBUtils.getInstance().putBlock(block);
+ this.lastBlockHash = block.getHash();
+
+ }
+
+ /**
+ * 根据data添加区块
+ * @param data
+ */
+ public void addBlock(String data) throws Exception{
+
+ String lastBlockHash = RocksDBUtils.getInstance().getLastBlockHash();
+ Block lastBlock = RocksDBUtils.getInstance().getBlock(lastBlockHash);
+ if (StringUtils.isBlank(lastBlockHash)){
+ throw new Exception("还没有数据库,无法直接添加区块。。");
+ }
+ this.addBlock(Block.newBlock(lastBlockHash,data,lastBlock.getHeight()+1));
+
+ }
+
+
+ /**
+ * 区块链迭代器:内部类
+ */
+ public class BlockchainIterator{
+
+ /**
+ * 当前区块的hash
+ */
+ private String currentBlockHash;
+
+ /**
+ * 构造函数
+ * @param currentBlockHash
+ */
+ public BlockchainIterator(String currentBlockHash) {
+ this.currentBlockHash = currentBlockHash;
+ }
+
+ /**
+ * 判断是否有下一个区块
+ * @return
+ */
+ public boolean hashNext() {
+ if (ByteUtils.ZERO_HASH.equals(currentBlockHash)) {
+ return false;
+ }
+ Block lastBlock = RocksDBUtils.getInstance().getBlock(currentBlockHash);
+ if (lastBlock == null) {
+ return false;
+ }
+ // 如果是创世区块
+ if (ByteUtils.ZERO_HASH.equals(lastBlock.getPrevBlockHash())) {
+ return true;
+ }
+ return RocksDBUtils.getInstance().getBlock(lastBlock.getPrevBlockHash()) != null;
+ }
+
+
+ /**
+ * 迭代获取区块
+ * @return
+ */
+ public Block next() {
+ Block currentBlock = RocksDBUtils.getInstance().getBlock(currentBlockHash);
+ if (currentBlock != null) {
+ this.currentBlockHash = currentBlock.getPrevBlockHash();
+ return currentBlock;
+ }
+ return null;
+ }
+ }
+
+ /**
+ * 添加方法,用于获取迭代器实例
+ * @return
+ */
+ public BlockchainIterator getBlockchainIterator() {
+ return new BlockchainIterator(lastBlockHash);
+ }
+
+
+
+
+}
diff --git a/src/main/java/com/example/blockchain/cli/CLI.java b/src/main/java/com/example/blockchain/cli/CLI.java
new file mode 100755
index 0000000..bc56626
--- /dev/null
+++ b/src/main/java/com/example/blockchain/cli/CLI.java
@@ -0,0 +1,129 @@
+package com.example.blockchain.cli;
+
+import com.example.blockchain.block.Block;
+import com.example.blockchain.block.Blockchain;
+import com.example.blockchain.pow.ProofOfWork;
+import com.example.blockchain.store.RocksDBUtils;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.DefaultParser;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+public class CLI {
+ private String[] args;
+ private Options options = new Options();
+
+
+
+ public CLI(String[] args) {
+ this.args = args;
+
+ Option helpCmd = Option.builder("h").desc("show help").build();
+ options.addOption(helpCmd);
+
+ Option data = Option.builder("data").hasArg(true).desc("add block").build();
+
+ options.addOption(data);
+
+ }
+
+ /**
+ * 打印帮助信息
+ */
+ private void help() {
+ System.out.println("Usage:");
+ System.out.println(" createblockchain -address ADDRESS - Create a blockchain and send genesis block reward to ADDRESS");
+ System.out.println(" addblock -data DATA - Get balance of ADDRESS");
+ System.out.println(" printchain - Print all the blocks of the blockchain");
+ System.exit(0);
+ }
+
+ /**
+ * 验证入参
+ *
+ * @param args
+ */
+ private void validateArgs(String[] args) {
+ if (args == null || args.length < 1) {
+ help();
+ }
+ }
+
+ /**
+ * 命令行解析入口
+ */
+ public void run() {
+ this.validateArgs(args);
+ try {
+ CommandLineParser parser = new DefaultParser();
+ CommandLine cmd = parser.parse(options, args);
+
+ switch (args[0]) {
+ case "createblockchain":
+ createBlockchainWithGenesisBlock();
+ break;
+ case "addblock":
+ String data = cmd.getOptionValue("data");
+ addBlock(data);
+ break;
+ case "printchain":
+ this.printChain();
+ break;
+ case "h":
+ this.help();
+ break;
+ default:
+ this.help();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ } finally {
+ RocksDBUtils.getInstance().closeDB();
+ }
+ }
+
+ /**
+ * 创建创世块
+ */
+ private void createBlockchainWithGenesisBlock(){
+ Blockchain.newBlockchain();
+ }
+
+ /**
+ * 添加区块
+ *
+ * @param data
+ */
+ private void addBlock(String data) throws Exception {
+ Blockchain blockchain = Blockchain.newBlockchain();
+ blockchain.addBlock(data);
+ }
+
+ /**
+ * 打印出区块链中的所有区块
+ */
+ private void printChain() {
+ Blockchain blockchain = Blockchain.newBlockchain();
+ Blockchain.BlockchainIterator iterator = blockchain.getBlockchainIterator();
+ while (iterator.hashNext()) {
+ Block block = iterator.next();
+ System.out.println("第" + block.getHeight() + "个区块信息:");
+
+ if (block != null){
+ boolean validate = ProofOfWork.newProofOfWork(block).validate();
+ System.out.println("validate = " + validate);
+ System.out.println("\tprevBlockHash: " + block.getPrevBlockHash());
+ System.out.println("\tData: " + block.getData());
+ System.out.println("\tHash: " + block.getHash());
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ String date = sdf.format(new Date(block.getTimeStamp() * 1000L));
+ System.out.println("\ttimeStamp:" + date);
+ System.out.println();
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/example/blockchain/pow/PowResult.java b/src/main/java/com/example/blockchain/pow/PowResult.java
new file mode 100755
index 0000000..c480974
--- /dev/null
+++ b/src/main/java/com/example/blockchain/pow/PowResult.java
@@ -0,0 +1,19 @@
+package com.example.blockchain.pow;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+@Data
+@AllArgsConstructor
+public class PowResult {
+
+ /**
+ * 计数器
+ */
+ private long nonce;
+ /**
+ * hash值
+ */
+ private String hash;
+
+}
diff --git a/src/main/java/com/example/blockchain/pow/ProofOfWork.java b/src/main/java/com/example/blockchain/pow/ProofOfWork.java
new file mode 100755
index 0000000..6d19839
--- /dev/null
+++ b/src/main/java/com/example/blockchain/pow/ProofOfWork.java
@@ -0,0 +1,117 @@
+package com.example.blockchain.pow;
+
+import com.example.blockchain.block.Block;
+import com.example.blockchain.util.ByteUtils;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import java.math.BigInteger;
+
+@Data
+@AllArgsConstructor
+public class ProofOfWork {
+
+ /**
+ * 难度目标位
+ * 0000 0000 0000 0000 1001 0001 0000 .... 0001
+ * 256位Hash里面前面至少有16个零
+ */
+ public static final int TARGET_BITS = 16;
+
+ /**
+ * 要验证的区块
+ */
+ private Block block;
+
+ /**
+ * 难度目标值
+ */
+ private BigInteger target;
+
+
+ /**
+ * 创建新的工作量证明对象
+ *
+ * 对1进行移位运算,将1向左移动 (256 - TARGET_BITS) 位,得到我们的难度目标值
+ * @param block
+ * @return
+ */
+ public static ProofOfWork newProofOfWork(Block block) {
+ /*
+ 1.创建一个BigInteger的数值1.
+ 0000000.....00001
+ 2.左移256-bits位
+
+ 以8 bit为例
+ 0000 0001
+ 0010 0000
+
+ 8-6
+ */
+
+ BigInteger targetValue = BigInteger.ONE.shiftLeft((256 - TARGET_BITS));
+ return new ProofOfWork(block, targetValue);
+ }
+
+ /**
+ * 运行工作量证明,开始挖矿,找到小于难度目标值的Hash
+ * @return
+ */
+ public PowResult run() {
+ long nonce = 0;
+ String shaHex = "";
+ System.out.printf("开始进行挖矿:%s \n", this.getBlock().getData());
+ long startTime = System.currentTimeMillis();
+ while (nonce < Long.MAX_VALUE) {
+ byte[] data = this.prepareData(nonce);
+ shaHex = DigestUtils.sha256Hex(data);
+ System.out.printf("\r%d: %s",nonce,shaHex);
+ if (new BigInteger(shaHex, 16).compareTo(this.target) == -1) {
+ System.out.println();
+ System.out.printf("耗时 Time: %s seconds \n", (float) (System.currentTimeMillis() - startTime) / 1000);
+ System.out.printf("当前区块Hash: %s \n\n", shaHex);
+ break;
+ } else {
+ nonce++;
+ }
+ }
+ return new PowResult(nonce, shaHex);
+ }
+
+ /**
+ * 根据block的数据,以及nonce,生成一个byte数组
+ *
+ * 注意:在准备区块数据时,一定要从原始数据类型转化为byte[],不能直接从字符串进行转换
+ * @param nonce
+ * @return
+ */
+ private byte[] prepareData(long nonce) {
+ byte[] prevBlockHashBytes = {};
+ if (StringUtils.isNoneBlank(this.getBlock().getPrevBlockHash())) {
+ prevBlockHashBytes = new BigInteger(this.getBlock().getPrevBlockHash(), 16).toByteArray();
+ }
+
+ return ByteUtils.merge(
+ prevBlockHashBytes,
+ this.getBlock().getData().getBytes(),
+ ByteUtils.toBytes(this.getBlock().getTimeStamp()),
+ ByteUtils.toBytes(TARGET_BITS),
+ ByteUtils.toBytes(nonce)
+ );
+
+ }
+
+ /**
+ * 验证区块是否有效
+ *
+ * @return
+ */
+ public boolean validate() {
+ byte[] data = this.prepareData(this.getBlock().getNonce());
+ return new BigInteger(DigestUtils.sha256Hex(data), 16).compareTo(this.target) == -1;
+ }
+
+}
diff --git a/src/main/java/com/example/blockchain/store/RocksDBUtils.java b/src/main/java/com/example/blockchain/store/RocksDBUtils.java
new file mode 100755
index 0000000..c013c5f
--- /dev/null
+++ b/src/main/java/com/example/blockchain/store/RocksDBUtils.java
@@ -0,0 +1,153 @@
+package com.example.blockchain.store;
+
+import com.example.blockchain.block.Block;
+import com.example.blockchain.util.SerializeUtils;
+import org.rocksdb.RocksDB;
+import org.rocksdb.RocksDBException;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class RocksDBUtils {
+ /**
+ * 区块链数据文件
+ */
+ private static final String DB_FILE = "blockchain.db";
+
+ /**
+ * 区块桶前缀
+ */
+ private static final String BLOCKS_BUCKET_KEY = "blocks";
+
+ /**
+ * 最新一个区块的hash
+ */
+ private static final String LAST_BLOCK_KEY = "l";
+
+
+
+ private volatile static RocksDBUtils instance;
+
+
+ /**
+ * 获取RocksDBUtils的单例
+ * @return
+ */
+ public static RocksDBUtils getInstance() {
+ if (instance == null) {
+ synchronized (RocksDBUtils.class) {
+ if (instance == null) {
+ instance = new RocksDBUtils();
+ }
+ }
+ }
+ return instance;
+ }
+
+
+ private RocksDBUtils() {
+ openDB();
+ initBlockBucket();
+ }
+
+
+ private RocksDB db;
+
+ /**
+ * block buckets
+ */
+ private Map blocksBucket;
+
+
+ /**
+ * 打开数据库
+ */
+ private void openDB() {
+ try {
+ db = RocksDB.open(DB_FILE);
+ } catch (RocksDBException e) {
+ throw new RuntimeException("打开数据库失败。。 ! ", e);
+ }
+ }
+ /**
+ * 初始化 blocks 数据桶
+ */
+ private void initBlockBucket() {
+ try {
+ //
+ byte[] blockBucketKey = SerializeUtils.serialize(BLOCKS_BUCKET_KEY);
+ byte[] blockBucketBytes = db.get(blockBucketKey);
+ if (blockBucketBytes != null) {
+ blocksBucket = (Map) SerializeUtils.deserialize(blockBucketBytes);
+ } else {
+ blocksBucket = new HashMap<>();
+ db.put(blockBucketKey, SerializeUtils.serialize(blocksBucket));
+ }
+ } catch (RocksDBException e) {
+ throw new RuntimeException("初始化block的bucket失败。。! ", e);
+ }
+ }
+
+ /**
+ * 保存区块
+ *
+ * @param block
+ */
+ public void putBlock(Block block) {
+ try {
+ blocksBucket.put(block.getHash(), SerializeUtils.serialize(block));
+ db.put(SerializeUtils.serialize(BLOCKS_BUCKET_KEY), SerializeUtils.serialize(blocksBucket));
+ } catch (RocksDBException e) {
+ throw new RuntimeException("存储区块失败。。 ", e);
+ }
+ }
+
+ /**
+ * 查询区块
+ *
+ * @param blockHash
+ * @return
+ */
+ public Block getBlock(String blockHash) {
+ return (Block) SerializeUtils.deserialize(blocksBucket.get(blockHash));
+ }
+
+ /**
+ * 保存最新一个区块的Hash值
+ *
+ * @param tipBlockHash
+ */
+ public void putLastBlockHash(String tipBlockHash) {
+ try {
+ blocksBucket.put(LAST_BLOCK_KEY, SerializeUtils.serialize(tipBlockHash));
+ db.put(SerializeUtils.serialize(BLOCKS_BUCKET_KEY), SerializeUtils.serialize(blocksBucket));
+ } catch (RocksDBException e) {
+ throw new RuntimeException("数据库存储最新区块hash失败。。 ", e);
+ }
+ }
+
+ /**
+ * 查询最新一个区块的Hash值
+ *
+ * @return
+ */
+ public String getLastBlockHash() {
+ byte[] lastBlockHashBytes = blocksBucket.get(LAST_BLOCK_KEY);
+ if (lastBlockHashBytes != null) {
+ return (String) SerializeUtils.deserialize(lastBlockHashBytes);
+ }
+ return "";
+ }
+
+ /**
+ * 关闭数据库
+ */
+ public void closeDB() {
+ try {
+ db.close();
+ } catch (Exception e) {
+ throw new RuntimeException("关闭数据库失败。。 ", e);
+ }
+ }
+
+}
diff --git a/src/main/java/com/example/blockchain/util/ByteUtils.java b/src/main/java/com/example/blockchain/util/ByteUtils.java
new file mode 100755
index 0000000..0a1468d
--- /dev/null
+++ b/src/main/java/com/example/blockchain/util/ByteUtils.java
@@ -0,0 +1,36 @@
+package com.example.blockchain.util;
+
+import org.apache.commons.codec.binary.Hex;
+import org.apache.commons.lang3.ArrayUtils;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.stream.Stream;
+
+public class ByteUtils {
+
+ public static final String ZERO_HASH = Hex.encodeHexString(new byte[32]);
+
+
+ /**
+ * 将多个字节数组合并成一个字节数组
+ * @param bytes
+ * @return
+ */
+ public static byte[] merge(byte[]... bytes) {
+ Stream stream = Stream.of();
+ for (byte[] b : bytes) {
+ stream = Stream.concat(stream, Arrays.stream(ArrayUtils.toObject(b)));
+ }
+ return ArrayUtils.toPrimitive(stream.toArray(Byte[]::new));
+ }
+
+ /**
+ * long 转化为 byte[]
+ * @param val
+ * @return
+ */
+ public static byte[] toBytes(long val) {
+ return ByteBuffer.allocate(Long.BYTES).putLong(val).array();
+ }
+}
diff --git a/src/main/java/com/example/blockchain/util/SerializeUtils.java b/src/main/java/com/example/blockchain/util/SerializeUtils.java
new file mode 100755
index 0000000..33c680c
--- /dev/null
+++ b/src/main/java/com/example/blockchain/util/SerializeUtils.java
@@ -0,0 +1,33 @@
+package com.example.blockchain.util;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+public class SerializeUtils {
+
+ /**
+ * 序列化
+ * @param object 需要序列化的对象
+ * @return
+ */
+ public static byte[] serialize(Object object) {
+ Output output = new Output(4096, -1);
+ new Kryo().writeClassAndObject(output, object);
+ byte[] bytes = output.toBytes();
+ output.close();
+ return bytes;
+ }
+
+ /**
+ * 反序列化
+ * @param bytes 对象对应的字节数组
+ * @return
+ */
+ public static Object deserialize(byte[] bytes) {
+ Input input = new Input(bytes);
+ Object obj = new Kryo().readClassAndObject(input);
+ input.close();
+ return obj;
+ }
+}
diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml
new file mode 100755
index 0000000..9f88c1f
--- /dev/null
+++ b/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,7 @@
+
+
+
+ Archetype Created Web Application
+
diff --git a/src/main/webapp/index.jsp b/src/main/webapp/index.jsp
new file mode 100755
index 0000000..c38169b
--- /dev/null
+++ b/src/main/webapp/index.jsp
@@ -0,0 +1,5 @@
+
+
+Hello World!
+
+