下面开始根据上一篇文章说的区块链的属性来用Java实现一个简单版的区块链比特币逻辑,逻辑大概要实现如下功能。
1、生成账户
2、挖矿获得比特币
3、查询所有区块链
4、转账交易
我们知道,转账后交易还未记账的,需要先进行记账才能生效,而记账是通过挖矿来记账的。
下面简单介绍一下项目的组成。
1、SpringBoot
这里是简单的SpringBoot项目Contrller层暴露上面1、生成账户;2、挖矿获得比特币;3、查询所有区块链;4、转账交易这些接口。
2、WebSocket
这里是通过WebSocket来实现节点之间的通信,区块链的最大的优势就是去中心化。
3、关键源码如下
钱包
public class Wallet {//公钥private String publicKey;//私钥private String privateKey;public String getPublicKey() {return publicKey;}public void setPublicKey(String publicKey) {this.publicKey = publicKey;}public String getPrivateKey() {return privateKey;}public void setPrivateKey(String privateKey) {this.privateKey = privateKey;}public Wallet() {//构造参数会生成一个公钥,一个私钥,这里用RSA算法//1、生成一队公钥私钥Map<String,String> keyMap = RSA.getKeyMap();//获得公钥this.publicKey=keyMap.get(RSA.PUBLIC_KEY);this.privateKey= keyMap.get(RSA.PRIVATE_KEY);}//交易的输入需要公钥的hash,所以这里需要获取公钥的hashpublic static String getPublicKeyHash(String publicKey) {//这里用SHA256return SHA256.encrypt(publicKey);}//当然上面那个方法是用来验证交易输出是否是这个用户的,这里还有一个自己的方法public String getPublicKeyHash() {return Wallet.getPublicKeyHash(publicKey);}//下面这里还需要获取地址,根据这个地址可以找到这个钱包,这里用MD5不用那么长public String getAdress() {return MD5.encrypt(getPublicKeyHash());}@Overridepublic String toString() {return "Wallet [publicKey=" + publicKey + ", privateKey=" + privateKey + ", getPublicKeyHash()="+ getPublicKeyHash() + ", getAdress()=" + getAdress() + "]";}public static void main(String[] args) {//生成一个钱包Wallet wallet = new Wallet();System.out.println(wallet);}}
钱包有私钥和公钥属性以及生成公钥Hash和钱包地址的方法。
区块
public class Block {//区块索引号private String index;//当前区块的唯一标识private String hash;//前一区块的唯一标识private String preHash;//生成区块的时间戳private String timestamp;//交易集合private List<Transaction> transactions;//计算次数private String num;//辅助计算值:目的是增加随机性private String uuid;public String getNum() {return num;}public void setNum(String num) {this.num = num;}public String getUuid() {return uuid;}public void setUuid(String uuid) {this.uuid = uuid;}public String getIndex() {return index;}public void setIndex(String index) {this.index = index;}public String getHash() {return hash;}public void setHash(String hash) {this.hash = hash;}public String getPreHash() {return preHash;}public void setPreHash(String preHash) {this.preHash = preHash;}public String getTimestamp() {return timestamp;}public void setTimestamp(String timestamp) {this.timestamp = timestamp;}public List<Transaction> getTransactions() {return transactions;}public void setTransactions(List<Transaction> transactions) {this.transactions = transactions;}public static void main(String[] args) {// TODO Auto-generated method stub}}
区块有区块索引号、当前区块的唯一标识hash、生产区块的时间戳、前一区块的唯一标识、交易集合。
交易输入
public class TransactionInput {//前一个交易的IDprivate String preId;//比特币数量private String value;//发送方的公钥private String senderPublicKey;//交易签名//对发送者和接收者的公钥哈希以及整个交易签名private String sign;public String getPreId() {return preId;}public void setPreId(String preId) {this.preId = preId;}public String getValue() {return value;}public void setValue(String value) {this.value = value;}public String getSenderPublicKey() {return senderPublicKey;}public void setSenderPublicKey(String senderPublicKey) {this.senderPublicKey = senderPublicKey;}public String getSign() {return sign;}public void setSign(String sign) {this.sign = sign;}}
交易输入有前一个交易的ID、比特币数量、发送方的公钥、交易签名(对发送者和接收者的公钥哈希以及整个交易签名)
交易输出
public class TransactionOutput {//比特币数量private String value;//接收者的公钥hashprivate String publicKeyHash;public String getValue() {return value;}public void setValue(String value) {this.value = value;}public String getPublicKeyHash() {return publicKeyHash;}public void setPublicKeyHash(String publicKeyHash) {this.publicKeyHash = publicKeyHash;}public TransactionOutput(String value, String publicKeyHash) {super();this.value = value;this.publicKeyHash = publicKeyHash;}}
只有比特币数量和接收者的公钥Hash
交易
public class Transaction {//唯一IDprivate String id;private TransactionInput transactionInput;private TransactionOutput transactionOutput;public String getId() {return id;}public void setId(String id) {this.id = id;}public TransactionInput getTransactionInput() {return transactionInput;}public void setTransactionInput(TransactionInput transactionInput) {this.transactionInput = transactionInput;}public TransactionOutput getTransactionOutput() {return transactionOutput;}public void setTransactionOutput(TransactionOutput transactionOutput) {this.transactionOutput = transactionOutput;}//对交易进行签名:对发送者和接收者的公钥hash以及整个交易签名public void sign(String senderPrivateKey,String senderPublicKeyHash) {//这里签名用的发送者的私钥,在签名之前,//签名字段为空,所以生成签名之前,可以把签名设置成发送者公钥hash,这样子就全了this.getTransactionInput().setSign(senderPublicKeyHash);//获取签名,签名是对摘要进行签名的String hash = SHA256.encrypt(new Gson().toJson(this));System.out.println("hash:"+hash);String sign = RSA.rsaEncrypt(senderPrivateKey, hash);this.getTransactionInput().setSign(sign);System.out.println(sign);}//验证签名//对交易进行签名:对发送者和接收者的公钥hash以及整个交易签名public boolean verify(Transaction preTransaction) {if(!this.getTransactionInput().getPreId().equals("0")) {//这里表明该交易不是系统给的,有上一个交易if(!this.getTransactionInput().getPreId().equals(preTransaction.getId())) {//上一个交易的id和本交易的上一个交易的id不同return false;}}else {//该交易是系统给的return true;}//1、先获取发送者的公钥HashString senderPublicKey = this.getTransactionInput().getSenderPublicKey();String senderPublicKeyHash = Wallet.getPublicKeyHash(senderPublicKey);//判断引用的输出是不是发送者的if(!preTransaction.getTransactionOutput().getPublicKeyHash().equals(senderPublicKeyHash)) {//上一个交易的输出不是发送者的return false;}//判断是否是发送者发送的//2、暂存签名String oldSign = this.getTransactionInput().getSign();System.out.println("oldSign:"+oldSign);//设置公钥Hashthis.getTransactionInput().setSign(senderPublicKeyHash);//获取签名,签名是对摘要进行签名的String hash = SHA256.encrypt(new Gson().toJson(this));System.out.println("hash:"+hash);//用发送者的公钥解密String newHash = RSA.rsaDecode(senderPublicKey, oldSign);System.out.println("newHash:"+newHash);//将签名设置回去this.getTransactionInput().setSign(oldSign);if(hash.equals(newHash)) {System.out.println("是发送者发送的");return true;}else {System.out.println("不是发送者发送的");return false;}}@Overridepublic String toString() {return "Transaction [id=" + id + ", transactionInput=" + transactionInput + ", transactionOutput="+ transactionOutput + "]";}}
交易包括交易ID和交易输入以及输出,因为交易输入的签名包括整个交易,所以交易的验证就放到交易对象本身
挖矿
public Block mine() {//生成一个区块Block block = new Block();//1、获取当前区块链的最后一个区块if(blockChain.size()==0) {block.setIndex("0");//创世区块block.setPreHash("0");}else {Block lastBlock = blockChain.get(blockChain.size()-1);block.setIndex((Integer.parseInt(lastBlock.getIndex())+1)+"");block.setPreHash(lastBlock.getHash());}List<Transaction> transactions = new ArrayList<Transaction>();//生成一个交易,这个交易是区块链这个系统给的//交易输入TransactionInput transactionInput = new TransactionInput();//上一个交易的ID。系统发的,所以为0transactionInput.setPreId("0");//这个系统发的。所以发送方的公钥为空transactionInput.setSenderPublicKey("");transactionInput.setValue("10");//交易输出:发给自己TransactionOutput transactionOutput = new TransactionOutput("10", myWallet.getPublicKeyHash());//构建交易Transaction transaction = new Transaction();//唯一IDtransaction.setId(MD5.encrypt(UUID.randomUUID().toString()));//交易输入transaction.setTransactionInput(transactionInput);//交易输出transaction.setTransactionOutput(transactionOutput);//签名transaction.sign(myWallet.getPrivateKey(), myWallet.getPublicKeyHash());transactions.add(transaction);//获得所有未花费的交易for (Transaction notPackedTransaction : notPackedTransactions) {Transaction preTransaction = preTransaction(notPackedTransaction);if(preTransaction!=null) {if(notPackedTransaction.verify(preTransaction)) {//加入挖矿中transactions.add(notPackedTransaction);}}}block.setTransactions(transactions);int num = 0;//挖矿while(true) {//时间搓String timestamp = System.currentTimeMillis()+"";//numnum++;//UUIDString uuid = UUID.randomUUID().toString();block.setNum(num+"");block.setTimestamp(timestamp);block.setUuid(uuid);block.setHash("");//用SHA256计算目标值String value = SHA256.encrypt(gson.toJson(block));log.info("挖矿中:"+value);if("0000".equals(value.substring(0, 4))) {log.info("挖矿成功:"+value+"block="+gson.toJson(block));block.setHash(value);break;}}//挖矿成功blockChain.add(block);//从已打包交易中移除for(Transaction t : block.getTransactions()) {notPackedTransactions.remove(t);}//这里需要将该区块广播出去:Message message = new Message(Const.BROATCAST_BLOCK);message.setBody(block);//不管是客户端还是服务器,全部广播p2pService.broatcast(gson.toJson(message));//把剩下的交易也广播出去Message message2 = new Message(Const.RESPONSE_NOT_PACKET_TRANSACTIONS);message2.setBody(notPackedTransactions);//不管是客户端还是服务器,全部广播p2pService.broatcast(gson.toJson(message2));return block;}
挖矿需要打包所有未打包的交易,然后做工作了证明,这里是计算一个固定的hash值规律,真正的比特币实现这个难度是可以动态变化的。
获得账户比特币数目
比特币是所有未花费的交易输出,计算方式如下
public String balance() {int value = 0;//获取该账户所有未花费的交易输出List<Transaction> unspentTransactions = this.getUnspentTransactions();for (Transaction transaction : unspentTransactions) {//获得交易输出TransactionOutput transactionOutput = transaction.getTransactionOutput();value=value+Integer.parseInt(transactionOutput.getValue());}return value+"";}//这里的实现效率是比较慢的,真正的比特币是借助一种merkle树的结构来提高效率private List<Transaction> getUnspentTransactions(){//获取未打包的交易中属于自己的交易输入的IDList<String> ids =new ArrayList<String>();for (Transaction transaction : notPackedTransactions) {//该交易的输入是否是自己TransactionInput transactionInput = transaction.getTransactionInput();String senderPublicKey = transactionInput.getSenderPublicKey();if(senderPublicKey.equals(myWallet.getPublicKey())) {//该交易的是自己产生的,用的是自己的交易输出所在的交易ids.add(transactionInput.getPreId());}}//获取所有已打包的交易中数以自己的交易输入的IDfor (Block block : blockChain) {//获取交易List<Transaction> transactions = block.getTransactions();//获取属于自己的交易输入for (Transaction transaction : transactions) {//该交易的输入是否是自己TransactionInput transactionInput = transaction.getTransactionInput();String senderPublicKey = transactionInput.getSenderPublicKey();if(senderPublicKey.equals(myWallet.getPublicKey())) {//该交易的是自己产生的,用的是自己的交易输出所在的交易ids.add(transactionInput.getPreId());}}}//获取为话费的交易输出,也就是这个交易的输出是自己,但是交易未花费List<Transaction> unspentTransactions = new ArrayList<Transaction>();for (Block block : blockChain) {//获取交易List<Transaction> transactions = block.getTransactions();//获取属于自己的交易输入for (Transaction transaction : transactions) {//获得交易输出TransactionOutput transactionOutput = transaction.getTransactionOutput();//判断是否是自己的if(transactionOutput.getPublicKeyHash().equals(myWallet.getPublicKeyHash())) {//检查这个交易有没有花费if(!ids.contains(transaction.getId())) {unspentTransactions.add(transaction);}}}}return unspentTransactions;}
转账
public Map<String, Object> transaction(String value, String address) {Map<String, Object> result= new HashMap<String, Object>();if(address.equals(myWallet.getAdress())) {result.put("status", "不能给自己转账");return result;}//获取当前用户的价值数目List<Transaction> unspentTransactions = this.getUnspentTransactions();for (Transaction transaction : unspentTransactions) {//获得交易输出TransactionOutput transactionOutput = transaction.getTransactionOutput();//先实现整个转if(transactionOutput.getValue().equals(value)) {//生成一个交易//交易输入TransactionInput transactionInput = new TransactionInput();//上一个交易的IDtransactionInput.setPreId(transaction.getId());//这个系统发的。所以发送方的公钥为空transactionInput.setSenderPublicKey(myWallet.getPublicKey());transactionInput.setValue(value);String receivePublicHashKey = "";//接受者公钥Hash//获取接受者的钱包for (Wallet wallet : wallets) {if(address.equals(wallet.getAdress())) {receivePublicHashKey = wallet.getPublicKeyHash();break;}}if(StringUtils.isBlank(receivePublicHashKey)) {result.put("status", "接受者不存在");return result;}//交易输出:发给自己TransactionOutput transactionOutput2 = new TransactionOutput(value, receivePublicHashKey);//构建交易Transaction transaction2 = new Transaction();//唯一IDtransaction2.setId(MD5.encrypt(UUID.randomUUID().toString()));//交易输入transaction2.setTransactionInput(transactionInput);//交易输出transaction2.setTransactionOutput(transactionOutput2);//签名transaction2.sign(myWallet.getPrivateKey(), myWallet.getPublicKeyHash());log.info("交易生成成功:"+gson.toJson(transaction2));//加入到未打包交易中notPackedTransactions.add(transaction2);result.put("status", "success");//把交易广播出去//这里需要将该区块广播出去:Message message = new Message(Const.BROATCAST_TRANSACTION);message.setBody(transaction2);//不管是客户端还是服务器,全部广播p2pService.broatcast(gson.toJson(message));return result;}}result.put("status", "不够钱");return result;}
转账得先判断有没有足够的比特币,有才能够转账,这里实现的是刚好10个比特币,没有说扣不完的情况,如果要实现用户有10个比特币,但是只转账5个,那么要实现
找零的逻辑,其实也很简单,比特币是没有说只用5个的,每个交易输出一定是全部用完,比如我有一个未花费的交易输出为10,也就是10个未花费的比特币,如果我要转账给张三5个,那么这个交易输出将会全部用完,会生成两个交易输出,一个是给张三的5,另一个是给自己的5.
P2P的实现
这个其实比较简单,这里借助WebSocket来实现即可
客户端
@Component("p2pClient")public class P2PClient {private static Gson gson = new Gson();//ws服务 ws://127.0.0.1:port@Value("${P2P_CLIENT_PEER}")private String P2P_SERVER_PORT;@Autowiredprivate P2PService p2pService;public void connectToPeer() {if("".equals(P2P_SERVER_PORT)) {return;}try {final WebSocketClient socketClient = new WebSocketClient(new URI(P2P_SERVER_PORT)) {@Overridepublic void onOpen(ServerHandshake serverHandshake) {//向服务器发送获取区块链,未打包交易,钱包集合Message QUERY_BLOCKCHAIN = new Message(Const.QUERY_BLOCKCHAIN);Message QUERY_NOT_PACKET_TRANSACTIONS = new Message(Const.QUERY_NOT_PACKET_TRANSACTIONS);Message QUERY_WALLETS = new Message(Const.QUERY_WALLETS);p2pService.sockets.add(this);p2pService.write(this, gson.toJson(QUERY_BLOCKCHAIN));p2pService.write(this, gson.toJson(QUERY_NOT_PACKET_TRANSACTIONS));p2pService.write(this, gson.toJson(QUERY_WALLETS));}@Overridepublic void onMessage(String msg) {System.out.println("收到服务端发送的消息:" + msg);p2pService.handleMessage(this, msg);}@Overridepublic void onClose(int i, String msg, boolean b) {System.out.println("connection failed");p2pService.sockets.remove(this);}@Overridepublic void onError(Exception e) {System.out.println("connection failed");p2pService.sockets.remove(this);}};socketClient.connect();} catch (URISyntaxException e) {System.out.println("p2p connect is error:" + e.getMessage());}}}
服务端
@Component("p2pServer")public class P2PServer {//端口@Value("${P2P_SERVER_PORT}")private String P2P_SERVER_PORT;@Autowiredprivate P2PService p2pService;public void initP2PServer() {int port = Integer.parseInt(P2P_SERVER_PORT);System.out.println("PORT:"+port);final WebSocketServer socketServer = new WebSocketServer(new InetSocketAddress(port)) {public void onOpen(WebSocket webSocket, ClientHandshake clientHandshake) {//write(webSocket, "服务端连接成功");p2pService.sockets.add(webSocket);}public void onClose(WebSocket webSocket, int i, String s, boolean b) {System.out.println("connection failed to peer:" + webSocket.getRemoteSocketAddress());p2pService.sockets.remove(webSocket);}public void onMessage(WebSocket webSocket, String msg) {System.out.println("接收到客户端消息:" + msg);p2pService.handleMessage(webSocket,msg);}public void onError(WebSocket webSocket, Exception e) {System.out.println("connection failed to peer:" + webSocket.getRemoteSocketAddress());p2pService.sockets.remove(webSocket);}public void onStart() {}};socketServer.start();System.out.println("listening websocket p2p port on: " + port);}}
因为是点对点通信,所以自己又是客户端,又是服务端,当然对应的比特币的就是部署在服务器上的能够通过公网IP向外提供服务的全节点啦。
主要的实现逻辑是通过P2PService
@Component("p2pService")public class P2PService {public List<WebSocket> sockets = new ArrayList<WebSocket>();private static Gson gson = new Gson();@Autowiredprivate CommonService commonService;/*** 获取区块链* @return*/private String getBlockChain() {Message message = new Message(Const.RESPONSE_BLOCKCHAIN);message.setBody(commonService.blockChain);return gson.toJson(message);}/*** 获取未打包交易* @return*/private String getNotPackedTransactions() {Message message = new Message(Const.RESPONSE_NOT_PACKET_TRANSACTIONS);message.setBody(commonService.notPackedTransactions);return gson.toJson(message);}/*** 查询钱包的信息* @return*/private String getWallets() {Message message = new Message(Const.RESPONSE_WALLETS);message.setBody(commonService.wallets);return gson.toJson(message);}/*** 消息处理* @param webSocket* @param msg*/public void handleMessage(WebSocket webSocket, String msg) {//将msg转变成MessageMessage message =gson.fromJson(msg, Message.class);System.out.println("服务器或者客户端收到的消息:"+message);Object body = message.getBody();String str = gson.toJson(body);switch (message.getType()) {case Const.QUERY_BLOCKCHAIN://客户端发来查询区块链的信息this.write(webSocket, getBlockChain());break;case Const.QUERY_NOT_PACKET_TRANSACTIONS://客户端发来查询未打包交易的信息this.write(webSocket, getNotPackedTransactions());break;case Const.QUERY_WALLETS://客户端发来查询钱包的信息this.write(webSocket, getWallets());break;case Const.RESPONSE_BLOCKCHAIN:Type type = new TypeToken<List<Block>>(){}.getType() ;List<Block> bc = gson.fromJson(str, type);if(bc.size()>commonService.blockChain.size()) {commonService.blockChain = bc;System.out.println(commonService.blockChain );}break;case Const.RESPONSE_NOT_PACKET_TRANSACTIONS:Type type2 = new TypeToken<List<Transaction>>(){}.getType() ;commonService.notPackedTransactions = gson.fromJson(str,type2);System.out.println(commonService.notPackedTransactions );break;case Const.RESPONSE_WALLETS:Type type3 = new TypeToken<List<Wallet>>(){}.getType() ;commonService.wallets = gson.fromJson(str,type3);System.out.println(commonService.wallets );break;case Const.BROATCAST_BLOCK://区块也一样Block block = gson.fromJson(str, Block.class);//先校验区块,获取区块的hash值String hash = block.getHash();block.setHash("");//用SHA256计算目标值String value = SHA256.encrypt(gson.toJson(block));if("0000".equals(value.substring(0, 4))&&hash.equals(value)) {//挖矿成功,获取当前最长的链if(commonService.blockChain.size()==0) {commonService.blockChain.add(block);}else {//检查是否在区块中boolean flag1 = false;for(Block b:commonService.blockChain) {if(b.getHash().equals(block.getHash())) {//在区块中flag1=true;}}if(!flag1) {//获取最后一个区块Block lastBlock = commonService.blockChain.get(commonService.blockChain.size()-1);if(Integer.parseInt(lastBlock.getIndex())+1==Integer.parseInt(block.getIndex())) {//加入新的区块中commonService.blockChain.add(block);//广播出去//把交易广播出去//这里需要将该区块广播出去:Message m = new Message(Const.BROATCAST_TRANSACTION);m.setBody(block);//不管是客户端还是服务器,全部广播broatcast(gson.toJson(m));}else {//获取最新的链Message QUERY_BLOCKCHAIN = new Message(Const.QUERY_BLOCKCHAIN);//向所有节点broatcast(gson.toJson(QUERY_BLOCKCHAIN));}}}}break;case Const.BROATCAST_TRANSACTION:Transaction transaction = gson.fromJson(str, Transaction.class);//先验证交易,先获取上一个交易System.out.println("transaction:"+transaction);Transaction preTransaction = commonService.preTransaction(transaction);System.out.println("preTransaction:"+preTransaction);Boolean check = transaction.verify(preTransaction);//交易正确if(check) {boolean flag1 = false;for(Transaction t:commonService.notPackedTransactions) {if(transaction.getId().equals(t.getId())) {//在未打包的交易中flag1=true;}}if(!flag1) {commonService.notPackedTransactions.add(transaction);//把交易广播出去//这里需要将该区块广播出去:Message m = new Message(Const.BROATCAST_TRANSACTION);m.setBody(transaction);//不管是客户端还是服务器,全部广播broatcast(gson.toJson(m));}}break;case Const.BROATCAST_WALLET:Wallet wallet = gson.fromJson(str, Wallet.class);//检查该钱包有没有加入,若已经加入则不处理,否则加入钱包集合中,广播出去boolean flag = false;for(Wallet w:commonService.wallets) {if(w.getAdress().equals(wallet.getAdress())) {//在钱包中flag=true;}}if(!flag) {//加入钱包commonService.wallets.add(wallet);//广播出去,这里要给把自己当作服务其的用户广播//这里需要将该钱包广播出去:Message m = new Message(Const.BROATCAST_WALLET);m.setBody(wallet);//这里不需要过滤掉客户端,因为都会做上面的逻辑判断,如果钱包已经在集合中了就//不会再加入,也不会再广播,当然为了提高效率也可以做个过滤broatcast(gson.toJson(message));}break;}}public void write(WebSocket ws, String message) {System.out.println("发送给" + ws.getRemoteSocketAddress().getPort() + "的p2p消息:" + message);ws.send(message);}public void broatcast(String message) {if (sockets.size() == 0) {return;}System.out.println("======广播消息开始:");for (WebSocket socket : sockets) {this.write(socket, message);}System.out.println("======广播消息结束");}}
然后我们看下springboot引用的相关包
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.0.5.RELEASE</version></parent><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-freemarker</artifactId></dependency><!-- AOP依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId></dependency><dependency><groupId>commons-lang</groupId><artifactId>commons-lang</artifactId><version>2.6</version></dependency><!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient --><dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId><version>4.5.5</version></dependency><!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpcore --><dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpcore</artifactId><version>4.4.9</version></dependency><!-- email --><dependency><groupId>org.apache.commons</groupId><artifactId>commons-email</artifactId><version>1.4</version></dependency><!-- https://mvnrepository.com/artifact/dom4j/dom4j --><dependency><groupId>dom4j</groupId><artifactId>dom4j</artifactId><version>1.6.1</version></dependency><!-- https://mvnrepository.com/artifact/org.java-websocket/Java-WebSocket --><dependency><groupId>org.java-websocket</groupId><artifactId>Java-WebSocket</artifactId><version>1.5.2</version></dependency>
以及配置文件
###服务启动端口号server:###节点1#port: 8001###节点2port: 8002servlet:session:timeout: 7200max-http-header-size: 10000000tomcat:basedir: tempspring:servlet:multipart:max-file-size: 10MBmax-request-size: 100MBapplication:name: miniblog###当前p2p服务端口号:节点1#P2P_SERVER_PORT: 7001###p2p服务#P2P_CLIENT_PEER:###当前p2p服务端口号:节点2P2P_SERVER_PORT: 7002###p2p服务P2P_CLIENT_PEER: ws://127.0.0.1:7001
3、启动测试的方式为
修改配置文件,将节点1的端口以及websocket的端口放开,启动完后切换到节点2的端口,然后访问如下api即可。
#节点1http://localhost:8001/getWallet 获得钱包http://localhost:8001/mine 挖矿http://localhost:8001/balance 查询余额http://localhost:8001/getBlockChain 获得区块链http://localhost:8001/transaction?value=10&address=751f520e1a3c3de161003a32b616d03d 交易#节点2http://localhost:8002/getWallet 获得钱包http://localhost:8002/mine 挖矿http://localhost:8002/balance 查询余额http://localhost:8002/getBlockChain 获得区块链
转账后必须通过挖矿才能生效。
好了一个特别简陋,没有完善的区块链原型就实现了,当然应该还有无数的漏洞。
有兴趣可以参考下:https://www.suibibk.com/fileupload/files/minichain.zip
