下面开始根据上一篇文章说的区块链的属性来用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,所以这里需要获取公钥的hash
public static String getPublicKeyHash(String publicKey) {
//这里用SHA256
return SHA256.encrypt(publicKey);
}
//当然上面那个方法是用来验证交易输出是否是这个用户的,这里还有一个自己的方法
public String getPublicKeyHash() {
return Wallet.getPublicKeyHash(publicKey);
}
//下面这里还需要获取地址,根据这个地址可以找到这个钱包,这里用MD5不用那么长
public String getAdress() {
return MD5.encrypt(getPublicKeyHash());
}
@Override
public 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 {
//前一个交易的ID
private 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;
//接收者的公钥hash
private 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 {
//唯一ID
private 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、先获取发送者的公钥Hash
String 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);
//设置公钥Hash
this.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;
}
}
@Override
public 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。系统发的,所以为0
transactionInput.setPreId("0");
//这个系统发的。所以发送方的公钥为空
transactionInput.setSenderPublicKey("");
transactionInput.setValue("10");
//交易输出:发给自己
TransactionOutput transactionOutput = new TransactionOutput("10", myWallet.getPublicKeyHash());
//构建交易
Transaction transaction = new Transaction();
//唯一ID
transaction.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()+"";
//num
num++;
//UUID
String 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(){
//获取未打包的交易中属于自己的交易输入的ID
List<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());
}
}
//获取所有已打包的交易中数以自己的交易输入的ID
for (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();
//上一个交易的ID
transactionInput.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();
//唯一ID
transaction2.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;
@Autowired
private P2PService p2pService;
public void connectToPeer() {
if("".equals(P2P_SERVER_PORT)) {
return;
}
try {
final WebSocketClient socketClient = new WebSocketClient(new URI(P2P_SERVER_PORT)) {
@Override
public 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));
}
@Override
public void onMessage(String msg) {
System.out.println("收到服务端发送的消息:" + msg);
p2pService.handleMessage(this, msg);
}
@Override
public void onClose(int i, String msg, boolean b) {
System.out.println("connection failed");
p2pService.sockets.remove(this);
}
@Override
public 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;
@Autowired
private 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();
@Autowired
private 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转变成Message
Message 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
###节点2
port: 8002
servlet:
session:
timeout: 7200
max-http-header-size: 10000000
tomcat:
basedir: temp
spring:
servlet:
multipart:
max-file-size: 10MB
max-request-size: 100MB
application:
name: miniblog
###当前p2p服务端口号:节点1
#P2P_SERVER_PORT: 7001
###p2p服务
#P2P_CLIENT_PEER:
###当前p2p服务端口号:节点2
P2P_SERVER_PORT: 7002
###p2p服务
P2P_CLIENT_PEER: ws://127.0.0.1:7001
3、启动测试的方式为
修改配置文件,将节点1的端口以及websocket的端口放开,启动完后切换到节点2的端口,然后访问如下api即可。
#节点1
http://localhost:8001/getWallet 获得钱包
http://localhost:8001/mine 挖矿
http://localhost:8001/balance 查询余额
http://localhost:8001/getBlockChain 获得区块链
http://localhost:8001/transaction?value=10&address=751f520e1a3c3de161003a32b616d03d 交易
#节点2
http://localhost:8002/getWallet 获得钱包
http://localhost:8002/mine 挖矿
http://localhost:8002/balance 查询余额
http://localhost:8002/getBlockChain 获得区块链
转账后必须通过挖矿才能生效。
好了一个特别简陋,没有完善的区块链原型就实现了,当然应该还有无数的漏洞。
有兴趣可以参考下:https://www.suibibk.com/fileupload/files/minichain.zip