个人随笔
目录
三、Java实现一个简单的区块链源码
2021-06-29 23:35:26

下面开始根据上一篇文章说的区块链的属性来用Java实现一个简单版的区块链比特币逻辑,逻辑大概要实现如下功能。

1、生成账户
2、挖矿获得比特币
3、查询所有区块链
4、转账交易

我们知道,转账后交易还未记账的,需要先进行记账才能生效,而记账是通过挖矿来记账的。

下面简单介绍一下项目的组成。

1、SpringBoot

这里是简单的SpringBoot项目Contrller层暴露上面1、生成账户;2、挖矿获得比特币;3、查询所有区块链;4、转账交易这些接口。

2、WebSocket

这里是通过WebSocket来实现节点之间的通信,区块链的最大的优势就是去中心化。

3、关键源码如下

钱包
  1. public class Wallet {
  2. //公钥
  3. private String publicKey;
  4. //私钥
  5. private String privateKey;
  6. public String getPublicKey() {
  7. return publicKey;
  8. }
  9. public void setPublicKey(String publicKey) {
  10. this.publicKey = publicKey;
  11. }
  12. public String getPrivateKey() {
  13. return privateKey;
  14. }
  15. public void setPrivateKey(String privateKey) {
  16. this.privateKey = privateKey;
  17. }
  18. public Wallet() {
  19. //构造参数会生成一个公钥,一个私钥,这里用RSA算法
  20. //1、生成一队公钥私钥
  21. Map<String,String> keyMap = RSA.getKeyMap();
  22. //获得公钥
  23. this.publicKey=keyMap.get(RSA.PUBLIC_KEY);
  24. this.privateKey= keyMap.get(RSA.PRIVATE_KEY);
  25. }
  26. //交易的输入需要公钥的hash,所以这里需要获取公钥的hash
  27. public static String getPublicKeyHash(String publicKey) {
  28. //这里用SHA256
  29. return SHA256.encrypt(publicKey);
  30. }
  31. //当然上面那个方法是用来验证交易输出是否是这个用户的,这里还有一个自己的方法
  32. public String getPublicKeyHash() {
  33. return Wallet.getPublicKeyHash(publicKey);
  34. }
  35. //下面这里还需要获取地址,根据这个地址可以找到这个钱包,这里用MD5不用那么长
  36. public String getAdress() {
  37. return MD5.encrypt(getPublicKeyHash());
  38. }
  39. @Override
  40. public String toString() {
  41. return "Wallet [publicKey=" + publicKey + ", privateKey=" + privateKey + ", getPublicKeyHash()="
  42. + getPublicKeyHash() + ", getAdress()=" + getAdress() + "]";
  43. }
  44. public static void main(String[] args) {
  45. //生成一个钱包
  46. Wallet wallet = new Wallet();
  47. System.out.println(wallet);
  48. }
  49. }

钱包有私钥和公钥属性以及生成公钥Hash和钱包地址的方法。

区块
  1. public class Block {
  2. //区块索引号
  3. private String index;
  4. //当前区块的唯一标识
  5. private String hash;
  6. //前一区块的唯一标识
  7. private String preHash;
  8. //生成区块的时间戳
  9. private String timestamp;
  10. //交易集合
  11. private List<Transaction> transactions;
  12. //计算次数
  13. private String num;
  14. //辅助计算值:目的是增加随机性
  15. private String uuid;
  16. public String getNum() {
  17. return num;
  18. }
  19. public void setNum(String num) {
  20. this.num = num;
  21. }
  22. public String getUuid() {
  23. return uuid;
  24. }
  25. public void setUuid(String uuid) {
  26. this.uuid = uuid;
  27. }
  28. public String getIndex() {
  29. return index;
  30. }
  31. public void setIndex(String index) {
  32. this.index = index;
  33. }
  34. public String getHash() {
  35. return hash;
  36. }
  37. public void setHash(String hash) {
  38. this.hash = hash;
  39. }
  40. public String getPreHash() {
  41. return preHash;
  42. }
  43. public void setPreHash(String preHash) {
  44. this.preHash = preHash;
  45. }
  46. public String getTimestamp() {
  47. return timestamp;
  48. }
  49. public void setTimestamp(String timestamp) {
  50. this.timestamp = timestamp;
  51. }
  52. public List<Transaction> getTransactions() {
  53. return transactions;
  54. }
  55. public void setTransactions(List<Transaction> transactions) {
  56. this.transactions = transactions;
  57. }
  58. public static void main(String[] args) {
  59. // TODO Auto-generated method stub
  60. }
  61. }

区块有区块索引号、当前区块的唯一标识hash、生产区块的时间戳、前一区块的唯一标识、交易集合。

交易输入
  1. public class TransactionInput {
  2. //前一个交易的ID
  3. private String preId;
  4. //比特币数量
  5. private String value;
  6. //发送方的公钥
  7. private String senderPublicKey;
  8. //交易签名
  9. //对发送者和接收者的公钥哈希以及整个交易签名
  10. private String sign;
  11. public String getPreId() {
  12. return preId;
  13. }
  14. public void setPreId(String preId) {
  15. this.preId = preId;
  16. }
  17. public String getValue() {
  18. return value;
  19. }
  20. public void setValue(String value) {
  21. this.value = value;
  22. }
  23. public String getSenderPublicKey() {
  24. return senderPublicKey;
  25. }
  26. public void setSenderPublicKey(String senderPublicKey) {
  27. this.senderPublicKey = senderPublicKey;
  28. }
  29. public String getSign() {
  30. return sign;
  31. }
  32. public void setSign(String sign) {
  33. this.sign = sign;
  34. }
  35. }

交易输入有前一个交易的ID、比特币数量、发送方的公钥、交易签名(对发送者和接收者的公钥哈希以及整个交易签名)

交易输出
  1. public class TransactionOutput {
  2. //比特币数量
  3. private String value;
  4. //接收者的公钥hash
  5. private String publicKeyHash;
  6. public String getValue() {
  7. return value;
  8. }
  9. public void setValue(String value) {
  10. this.value = value;
  11. }
  12. public String getPublicKeyHash() {
  13. return publicKeyHash;
  14. }
  15. public void setPublicKeyHash(String publicKeyHash) {
  16. this.publicKeyHash = publicKeyHash;
  17. }
  18. public TransactionOutput(String value, String publicKeyHash) {
  19. super();
  20. this.value = value;
  21. this.publicKeyHash = publicKeyHash;
  22. }
  23. }

只有比特币数量和接收者的公钥Hash

交易
  1. public class Transaction {
  2. //唯一ID
  3. private String id;
  4. private TransactionInput transactionInput;
  5. private TransactionOutput transactionOutput;
  6. public String getId() {
  7. return id;
  8. }
  9. public void setId(String id) {
  10. this.id = id;
  11. }
  12. public TransactionInput getTransactionInput() {
  13. return transactionInput;
  14. }
  15. public void setTransactionInput(TransactionInput transactionInput) {
  16. this.transactionInput = transactionInput;
  17. }
  18. public TransactionOutput getTransactionOutput() {
  19. return transactionOutput;
  20. }
  21. public void setTransactionOutput(TransactionOutput transactionOutput) {
  22. this.transactionOutput = transactionOutput;
  23. }
  24. //对交易进行签名:对发送者和接收者的公钥hash以及整个交易签名
  25. public void sign(String senderPrivateKey,String senderPublicKeyHash) {
  26. //这里签名用的发送者的私钥,在签名之前,
  27. //签名字段为空,所以生成签名之前,可以把签名设置成发送者公钥hash,这样子就全了
  28. this.getTransactionInput().setSign(senderPublicKeyHash);
  29. //获取签名,签名是对摘要进行签名的
  30. String hash = SHA256.encrypt(new Gson().toJson(this));
  31. System.out.println("hash:"+hash);
  32. String sign = RSA.rsaEncrypt(senderPrivateKey, hash);
  33. this.getTransactionInput().setSign(sign);
  34. System.out.println(sign);
  35. }
  36. //验证签名
  37. //对交易进行签名:对发送者和接收者的公钥hash以及整个交易签名
  38. public boolean verify(Transaction preTransaction) {
  39. if(!this.getTransactionInput().getPreId().equals("0")) {
  40. //这里表明该交易不是系统给的,有上一个交易
  41. if(!this.getTransactionInput().getPreId().equals(preTransaction.getId())) {
  42. //上一个交易的id和本交易的上一个交易的id不同
  43. return false;
  44. }
  45. }else {
  46. //该交易是系统给的
  47. return true;
  48. }
  49. //1、先获取发送者的公钥Hash
  50. String senderPublicKey = this.getTransactionInput().getSenderPublicKey();
  51. String senderPublicKeyHash = Wallet.getPublicKeyHash(senderPublicKey);
  52. //判断引用的输出是不是发送者的
  53. if(!preTransaction.getTransactionOutput().getPublicKeyHash().equals(senderPublicKeyHash)) {
  54. //上一个交易的输出不是发送者的
  55. return false;
  56. }
  57. //判断是否是发送者发送的
  58. //2、暂存签名
  59. String oldSign = this.getTransactionInput().getSign();
  60. System.out.println("oldSign:"+oldSign);
  61. //设置公钥Hash
  62. this.getTransactionInput().setSign(senderPublicKeyHash);
  63. //获取签名,签名是对摘要进行签名的
  64. String hash = SHA256.encrypt(new Gson().toJson(this));
  65. System.out.println("hash:"+hash);
  66. //用发送者的公钥解密
  67. String newHash = RSA.rsaDecode(senderPublicKey, oldSign);
  68. System.out.println("newHash:"+newHash);
  69. //将签名设置回去
  70. this.getTransactionInput().setSign(oldSign);
  71. if(hash.equals(newHash)) {
  72. System.out.println("是发送者发送的");
  73. return true;
  74. }else {
  75. System.out.println("不是发送者发送的");
  76. return false;
  77. }
  78. }
  79. @Override
  80. public String toString() {
  81. return "Transaction [id=" + id + ", transactionInput=" + transactionInput + ", transactionOutput="
  82. + transactionOutput + "]";
  83. }
  84. }

交易包括交易ID和交易输入以及输出,因为交易输入的签名包括整个交易,所以交易的验证就放到交易对象本身

挖矿
  1. public Block mine() {
  2. //生成一个区块
  3. Block block = new Block();
  4. //1、获取当前区块链的最后一个区块
  5. if(blockChain.size()==0) {
  6. block.setIndex("0");//创世区块
  7. block.setPreHash("0");
  8. }else {
  9. Block lastBlock = blockChain.get(blockChain.size()-1);
  10. block.setIndex((Integer.parseInt(lastBlock.getIndex())+1)+"");
  11. block.setPreHash(lastBlock.getHash());
  12. }
  13. List<Transaction> transactions = new ArrayList<Transaction>();
  14. //生成一个交易,这个交易是区块链这个系统给的
  15. //交易输入
  16. TransactionInput transactionInput = new TransactionInput();
  17. //上一个交易的ID。系统发的,所以为0
  18. transactionInput.setPreId("0");
  19. //这个系统发的。所以发送方的公钥为空
  20. transactionInput.setSenderPublicKey("");
  21. transactionInput.setValue("10");
  22. //交易输出:发给自己
  23. TransactionOutput transactionOutput = new TransactionOutput("10", myWallet.getPublicKeyHash());
  24. //构建交易
  25. Transaction transaction = new Transaction();
  26. //唯一ID
  27. transaction.setId(MD5.encrypt(UUID.randomUUID().toString()));
  28. //交易输入
  29. transaction.setTransactionInput(transactionInput);
  30. //交易输出
  31. transaction.setTransactionOutput(transactionOutput);
  32. //签名
  33. transaction.sign(myWallet.getPrivateKey(), myWallet.getPublicKeyHash());
  34. transactions.add(transaction);
  35. //获得所有未花费的交易
  36. for (Transaction notPackedTransaction : notPackedTransactions) {
  37. Transaction preTransaction = preTransaction(notPackedTransaction);
  38. if(preTransaction!=null) {
  39. if(notPackedTransaction.verify(preTransaction)) {
  40. //加入挖矿中
  41. transactions.add(notPackedTransaction);
  42. }
  43. }
  44. }
  45. block.setTransactions(transactions);
  46. int num = 0;
  47. //挖矿
  48. while(true) {
  49. //时间搓
  50. String timestamp = System.currentTimeMillis()+"";
  51. //num
  52. num++;
  53. //UUID
  54. String uuid = UUID.randomUUID().toString();
  55. block.setNum(num+"");
  56. block.setTimestamp(timestamp);
  57. block.setUuid(uuid);
  58. block.setHash("");
  59. //用SHA256计算目标值
  60. String value = SHA256.encrypt(gson.toJson(block));
  61. log.info("挖矿中:"+value);
  62. if("0000".equals(value.substring(0, 4))) {
  63. log.info("挖矿成功:"+value+"block="+gson.toJson(block));
  64. block.setHash(value);
  65. break;
  66. }
  67. }
  68. //挖矿成功
  69. blockChain.add(block);
  70. //从已打包交易中移除
  71. for(Transaction t : block.getTransactions()) {
  72. notPackedTransactions.remove(t);
  73. }
  74. //这里需要将该区块广播出去:
  75. Message message = new Message(Const.BROATCAST_BLOCK);
  76. message.setBody(block);
  77. //不管是客户端还是服务器,全部广播
  78. p2pService.broatcast(gson.toJson(message));
  79. //把剩下的交易也广播出去
  80. Message message2 = new Message(Const.RESPONSE_NOT_PACKET_TRANSACTIONS);
  81. message2.setBody(notPackedTransactions);
  82. //不管是客户端还是服务器,全部广播
  83. p2pService.broatcast(gson.toJson(message2));
  84. return block;
  85. }

挖矿需要打包所有未打包的交易,然后做工作了证明,这里是计算一个固定的hash值规律,真正的比特币实现这个难度是可以动态变化的。

获得账户比特币数目

比特币是所有未花费的交易输出,计算方式如下

  1. public String balance() {
  2. int value = 0;
  3. //获取该账户所有未花费的交易输出
  4. List<Transaction> unspentTransactions = this.getUnspentTransactions();
  5. for (Transaction transaction : unspentTransactions) {
  6. //获得交易输出
  7. TransactionOutput transactionOutput = transaction.getTransactionOutput();
  8. value=value+Integer.parseInt(transactionOutput.getValue());
  9. }
  10. return value+"";
  11. }
  12. //这里的实现效率是比较慢的,真正的比特币是借助一种merkle树的结构来提高效率
  13. private List<Transaction> getUnspentTransactions(){
  14. //获取未打包的交易中属于自己的交易输入的ID
  15. List<String> ids =new ArrayList<String>();
  16. for (Transaction transaction : notPackedTransactions) {
  17. //该交易的输入是否是自己
  18. TransactionInput transactionInput = transaction.getTransactionInput();
  19. String senderPublicKey = transactionInput.getSenderPublicKey();
  20. if(senderPublicKey.equals(myWallet.getPublicKey())) {
  21. //该交易的是自己产生的,用的是自己的交易输出所在的交易
  22. ids.add(transactionInput.getPreId());
  23. }
  24. }
  25. //获取所有已打包的交易中数以自己的交易输入的ID
  26. for (Block block : blockChain) {
  27. //获取交易
  28. List<Transaction> transactions = block.getTransactions();
  29. //获取属于自己的交易输入
  30. for (Transaction transaction : transactions) {
  31. //该交易的输入是否是自己
  32. TransactionInput transactionInput = transaction.getTransactionInput();
  33. String senderPublicKey = transactionInput.getSenderPublicKey();
  34. if(senderPublicKey.equals(myWallet.getPublicKey())) {
  35. //该交易的是自己产生的,用的是自己的交易输出所在的交易
  36. ids.add(transactionInput.getPreId());
  37. }
  38. }
  39. }
  40. //获取为话费的交易输出,也就是这个交易的输出是自己,但是交易未花费
  41. List<Transaction> unspentTransactions = new ArrayList<Transaction>();
  42. for (Block block : blockChain) {
  43. //获取交易
  44. List<Transaction> transactions = block.getTransactions();
  45. //获取属于自己的交易输入
  46. for (Transaction transaction : transactions) {
  47. //获得交易输出
  48. TransactionOutput transactionOutput = transaction.getTransactionOutput();
  49. //判断是否是自己的
  50. if(transactionOutput.getPublicKeyHash().equals(myWallet.getPublicKeyHash())) {
  51. //检查这个交易有没有花费
  52. if(!ids.contains(transaction.getId())) {
  53. unspentTransactions.add(transaction);
  54. }
  55. }
  56. }
  57. }
  58. return unspentTransactions;
  59. }
转账
  1. public Map<String, Object> transaction(String value, String address) {
  2. Map<String, Object> result= new HashMap<String, Object>();
  3. if(address.equals(myWallet.getAdress())) {
  4. result.put("status", "不能给自己转账");
  5. return result;
  6. }
  7. //获取当前用户的价值数目
  8. List<Transaction> unspentTransactions = this.getUnspentTransactions();
  9. for (Transaction transaction : unspentTransactions) {
  10. //获得交易输出
  11. TransactionOutput transactionOutput = transaction.getTransactionOutput();
  12. //先实现整个转
  13. if(transactionOutput.getValue().equals(value)) {
  14. //生成一个交易
  15. //交易输入
  16. TransactionInput transactionInput = new TransactionInput();
  17. //上一个交易的ID
  18. transactionInput.setPreId(transaction.getId());
  19. //这个系统发的。所以发送方的公钥为空
  20. transactionInput.setSenderPublicKey(myWallet.getPublicKey());
  21. transactionInput.setValue(value);
  22. String receivePublicHashKey = "";//接受者公钥Hash
  23. //获取接受者的钱包
  24. for (Wallet wallet : wallets) {
  25. if(address.equals(wallet.getAdress())) {
  26. receivePublicHashKey = wallet.getPublicKeyHash();
  27. break;
  28. }
  29. }
  30. if(StringUtils.isBlank(receivePublicHashKey)) {
  31. result.put("status", "接受者不存在");
  32. return result;
  33. }
  34. //交易输出:发给自己
  35. TransactionOutput transactionOutput2 = new TransactionOutput(value, receivePublicHashKey);
  36. //构建交易
  37. Transaction transaction2 = new Transaction();
  38. //唯一ID
  39. transaction2.setId(MD5.encrypt(UUID.randomUUID().toString()));
  40. //交易输入
  41. transaction2.setTransactionInput(transactionInput);
  42. //交易输出
  43. transaction2.setTransactionOutput(transactionOutput2);
  44. //签名
  45. transaction2.sign(myWallet.getPrivateKey(), myWallet.getPublicKeyHash());
  46. log.info("交易生成成功:"+gson.toJson(transaction2));
  47. //加入到未打包交易中
  48. notPackedTransactions.add(transaction2);
  49. result.put("status", "success");
  50. //把交易广播出去
  51. //这里需要将该区块广播出去:
  52. Message message = new Message(Const.BROATCAST_TRANSACTION);
  53. message.setBody(transaction2);
  54. //不管是客户端还是服务器,全部广播
  55. p2pService.broatcast(gson.toJson(message));
  56. return result;
  57. }
  58. }
  59. result.put("status", "不够钱");
  60. return result;
  61. }

转账得先判断有没有足够的比特币,有才能够转账,这里实现的是刚好10个比特币,没有说扣不完的情况,如果要实现用户有10个比特币,但是只转账5个,那么要实现
找零的逻辑,其实也很简单,比特币是没有说只用5个的,每个交易输出一定是全部用完,比如我有一个未花费的交易输出为10,也就是10个未花费的比特币,如果我要转账给张三5个,那么这个交易输出将会全部用完,会生成两个交易输出,一个是给张三的5,另一个是给自己的5.

P2P的实现

这个其实比较简单,这里借助WebSocket来实现即可
客户端

  1. @Component("p2pClient")
  2. public class P2PClient {
  3. private static Gson gson = new Gson();
  4. //ws服务 ws://127.0.0.1:port
  5. @Value("${P2P_CLIENT_PEER}")
  6. private String P2P_SERVER_PORT;
  7. @Autowired
  8. private P2PService p2pService;
  9. public void connectToPeer() {
  10. if("".equals(P2P_SERVER_PORT)) {
  11. return;
  12. }
  13. try {
  14. final WebSocketClient socketClient = new WebSocketClient(new URI(P2P_SERVER_PORT)) {
  15. @Override
  16. public void onOpen(ServerHandshake serverHandshake) {
  17. //向服务器发送获取区块链,未打包交易,钱包集合
  18. Message QUERY_BLOCKCHAIN = new Message(Const.QUERY_BLOCKCHAIN);
  19. Message QUERY_NOT_PACKET_TRANSACTIONS = new Message(Const.QUERY_NOT_PACKET_TRANSACTIONS);
  20. Message QUERY_WALLETS = new Message(Const.QUERY_WALLETS);
  21. p2pService.sockets.add(this);
  22. p2pService.write(this, gson.toJson(QUERY_BLOCKCHAIN));
  23. p2pService.write(this, gson.toJson(QUERY_NOT_PACKET_TRANSACTIONS));
  24. p2pService.write(this, gson.toJson(QUERY_WALLETS));
  25. }
  26. @Override
  27. public void onMessage(String msg) {
  28. System.out.println("收到服务端发送的消息:" + msg);
  29. p2pService.handleMessage(this, msg);
  30. }
  31. @Override
  32. public void onClose(int i, String msg, boolean b) {
  33. System.out.println("connection failed");
  34. p2pService.sockets.remove(this);
  35. }
  36. @Override
  37. public void onError(Exception e) {
  38. System.out.println("connection failed");
  39. p2pService.sockets.remove(this);
  40. }
  41. };
  42. socketClient.connect();
  43. } catch (URISyntaxException e) {
  44. System.out.println("p2p connect is error:" + e.getMessage());
  45. }
  46. }
  47. }

服务端

  1. @Component("p2pServer")
  2. public class P2PServer {
  3. //端口
  4. @Value("${P2P_SERVER_PORT}")
  5. private String P2P_SERVER_PORT;
  6. @Autowired
  7. private P2PService p2pService;
  8. public void initP2PServer() {
  9. int port = Integer.parseInt(P2P_SERVER_PORT);
  10. System.out.println("PORT:"+port);
  11. final WebSocketServer socketServer = new WebSocketServer(new InetSocketAddress(port)) {
  12. public void onOpen(WebSocket webSocket, ClientHandshake clientHandshake) {
  13. //write(webSocket, "服务端连接成功");
  14. p2pService.sockets.add(webSocket);
  15. }
  16. public void onClose(WebSocket webSocket, int i, String s, boolean b) {
  17. System.out.println("connection failed to peer:" + webSocket.getRemoteSocketAddress());
  18. p2pService.sockets.remove(webSocket);
  19. }
  20. public void onMessage(WebSocket webSocket, String msg) {
  21. System.out.println("接收到客户端消息:" + msg);
  22. p2pService.handleMessage(webSocket,msg);
  23. }
  24. public void onError(WebSocket webSocket, Exception e) {
  25. System.out.println("connection failed to peer:" + webSocket.getRemoteSocketAddress());
  26. p2pService.sockets.remove(webSocket);
  27. }
  28. public void onStart() {
  29. }
  30. };
  31. socketServer.start();
  32. System.out.println("listening websocket p2p port on: " + port);
  33. }
  34. }

因为是点对点通信,所以自己又是客户端,又是服务端,当然对应的比特币的就是部署在服务器上的能够通过公网IP向外提供服务的全节点啦。
主要的实现逻辑是通过P2PService

  1. @Component("p2pService")
  2. public class P2PService {
  3. public List<WebSocket> sockets = new ArrayList<WebSocket>();
  4. private static Gson gson = new Gson();
  5. @Autowired
  6. private CommonService commonService;
  7. /**
  8. * 获取区块链
  9. * @return
  10. */
  11. private String getBlockChain() {
  12. Message message = new Message(Const.RESPONSE_BLOCKCHAIN);
  13. message.setBody(commonService.blockChain);
  14. return gson.toJson(message);
  15. }
  16. /**
  17. * 获取未打包交易
  18. * @return
  19. */
  20. private String getNotPackedTransactions() {
  21. Message message = new Message(Const.RESPONSE_NOT_PACKET_TRANSACTIONS);
  22. message.setBody(commonService.notPackedTransactions);
  23. return gson.toJson(message);
  24. }
  25. /**
  26. * 查询钱包的信息
  27. * @return
  28. */
  29. private String getWallets() {
  30. Message message = new Message(Const.RESPONSE_WALLETS);
  31. message.setBody(commonService.wallets);
  32. return gson.toJson(message);
  33. }
  34. /**
  35. * 消息处理
  36. * @param webSocket
  37. * @param msg
  38. */
  39. public void handleMessage(WebSocket webSocket, String msg) {
  40. //将msg转变成Message
  41. Message message =gson.fromJson(msg, Message.class);
  42. System.out.println("服务器或者客户端收到的消息:"+message);
  43. Object body = message.getBody();
  44. String str = gson.toJson(body);
  45. switch (message.getType()) {
  46. case Const.QUERY_BLOCKCHAIN:
  47. //客户端发来查询区块链的信息
  48. this.write(webSocket, getBlockChain());
  49. break;
  50. case Const.QUERY_NOT_PACKET_TRANSACTIONS:
  51. //客户端发来查询未打包交易的信息
  52. this.write(webSocket, getNotPackedTransactions());
  53. break;
  54. case Const.QUERY_WALLETS:
  55. //客户端发来查询钱包的信息
  56. this.write(webSocket, getWallets());
  57. break;
  58. case Const.RESPONSE_BLOCKCHAIN:
  59. Type type = new TypeToken<List<Block>>(){}.getType() ;
  60. List<Block> bc = gson.fromJson(str, type);
  61. if(bc.size()>commonService.blockChain.size()) {
  62. commonService.blockChain = bc;
  63. System.out.println(commonService.blockChain );
  64. }
  65. break;
  66. case Const.RESPONSE_NOT_PACKET_TRANSACTIONS:
  67. Type type2 = new TypeToken<List<Transaction>>(){}.getType() ;
  68. commonService.notPackedTransactions = gson.fromJson(str,type2);
  69. System.out.println(commonService.notPackedTransactions );
  70. break;
  71. case Const.RESPONSE_WALLETS:
  72. Type type3 = new TypeToken<List<Wallet>>(){}.getType() ;
  73. commonService.wallets = gson.fromJson(str,type3);
  74. System.out.println(commonService.wallets );
  75. break;
  76. case Const.BROATCAST_BLOCK:
  77. //区块也一样
  78. Block block = gson.fromJson(str, Block.class);
  79. //先校验区块,获取区块的hash值
  80. String hash = block.getHash();
  81. block.setHash("");
  82. //用SHA256计算目标值
  83. String value = SHA256.encrypt(gson.toJson(block));
  84. if("0000".equals(value.substring(0, 4))&&hash.equals(value)) {
  85. //挖矿成功,获取当前最长的链
  86. if(commonService.blockChain.size()==0) {
  87. commonService.blockChain.add(block);
  88. }else {
  89. //检查是否在区块中
  90. boolean flag1 = false;
  91. for(Block b:commonService.blockChain) {
  92. if(b.getHash().equals(block.getHash())) {
  93. //在区块中
  94. flag1=true;
  95. }
  96. }
  97. if(!flag1) {
  98. //获取最后一个区块
  99. Block lastBlock = commonService.blockChain.get(commonService.blockChain.size()-1);
  100. if(Integer.parseInt(lastBlock.getIndex())+1==Integer.parseInt(block.getIndex())) {
  101. //加入新的区块中
  102. commonService.blockChain.add(block);
  103. //广播出去
  104. //把交易广播出去
  105. //这里需要将该区块广播出去:
  106. Message m = new Message(Const.BROATCAST_TRANSACTION);
  107. m.setBody(block);
  108. //不管是客户端还是服务器,全部广播
  109. broatcast(gson.toJson(m));
  110. }else {
  111. //获取最新的链
  112. Message QUERY_BLOCKCHAIN = new Message(Const.QUERY_BLOCKCHAIN);
  113. //向所有节点
  114. broatcast(gson.toJson(QUERY_BLOCKCHAIN));
  115. }
  116. }
  117. }
  118. }
  119. break;
  120. case Const.BROATCAST_TRANSACTION:
  121. Transaction transaction = gson.fromJson(str, Transaction.class);
  122. //先验证交易,先获取上一个交易
  123. System.out.println("transaction:"+transaction);
  124. Transaction preTransaction = commonService.preTransaction(transaction);
  125. System.out.println("preTransaction:"+preTransaction);
  126. Boolean check = transaction.verify(preTransaction);
  127. //交易正确
  128. if(check) {
  129. boolean flag1 = false;
  130. for(Transaction t:commonService.notPackedTransactions) {
  131. if(transaction.getId().equals(t.getId())) {
  132. //在未打包的交易中
  133. flag1=true;
  134. }
  135. }
  136. if(!flag1) {
  137. commonService.notPackedTransactions.add(transaction);
  138. //把交易广播出去
  139. //这里需要将该区块广播出去:
  140. Message m = new Message(Const.BROATCAST_TRANSACTION);
  141. m.setBody(transaction);
  142. //不管是客户端还是服务器,全部广播
  143. broatcast(gson.toJson(m));
  144. }
  145. }
  146. break;
  147. case Const.BROATCAST_WALLET:
  148. Wallet wallet = gson.fromJson(str, Wallet.class);
  149. //检查该钱包有没有加入,若已经加入则不处理,否则加入钱包集合中,广播出去
  150. boolean flag = false;
  151. for(Wallet w:commonService.wallets) {
  152. if(w.getAdress().equals(wallet.getAdress())) {
  153. //在钱包中
  154. flag=true;
  155. }
  156. }
  157. if(!flag) {
  158. //加入钱包
  159. commonService.wallets.add(wallet);
  160. //广播出去,这里要给把自己当作服务其的用户广播
  161. //这里需要将该钱包广播出去:
  162. Message m = new Message(Const.BROATCAST_WALLET);
  163. m.setBody(wallet);
  164. //这里不需要过滤掉客户端,因为都会做上面的逻辑判断,如果钱包已经在集合中了就
  165. //不会再加入,也不会再广播,当然为了提高效率也可以做个过滤
  166. broatcast(gson.toJson(message));
  167. }
  168. break;
  169. }
  170. }
  171. public void write(WebSocket ws, String message) {
  172. System.out.println("发送给" + ws.getRemoteSocketAddress().getPort() + "的p2p消息:" + message);
  173. ws.send(message);
  174. }
  175. public void broatcast(String message) {
  176. if (sockets.size() == 0) {
  177. return;
  178. }
  179. System.out.println("======广播消息开始:");
  180. for (WebSocket socket : sockets) {
  181. this.write(socket, message);
  182. }
  183. System.out.println("======广播消息结束");
  184. }
  185. }

然后我们看下springboot引用的相关包

  1. <parent>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-parent</artifactId>
  4. <version>2.0.5.RELEASE</version>
  5. </parent>
  6. <dependencies>
  7. <dependency>
  8. <groupId>org.springframework.boot</groupId>
  9. <artifactId>spring-boot-starter-web</artifactId>
  10. </dependency>
  11. <dependency>
  12. <groupId>org.springframework.boot</groupId>
  13. <artifactId>spring-boot-starter-freemarker</artifactId>
  14. </dependency>
  15. <!-- AOP依赖 -->
  16. <dependency>
  17. <groupId>org.springframework.boot</groupId>
  18. <artifactId>spring-boot-starter-aop</artifactId>
  19. </dependency>
  20. <dependency>
  21. <groupId>com.google.code.gson</groupId>
  22. <artifactId>gson</artifactId>
  23. </dependency>
  24. <dependency>
  25. <groupId>commons-lang</groupId>
  26. <artifactId>commons-lang</artifactId>
  27. <version>2.6</version>
  28. </dependency>
  29. <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
  30. <dependency>
  31. <groupId>org.apache.httpcomponents</groupId>
  32. <artifactId>httpclient</artifactId>
  33. <version>4.5.5</version>
  34. </dependency>
  35. <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpcore -->
  36. <dependency>
  37. <groupId>org.apache.httpcomponents</groupId>
  38. <artifactId>httpcore</artifactId>
  39. <version>4.4.9</version>
  40. </dependency>
  41. <!-- email -->
  42. <dependency>
  43. <groupId>org.apache.commons</groupId>
  44. <artifactId>commons-email</artifactId>
  45. <version>1.4</version>
  46. </dependency>
  47. <!-- https://mvnrepository.com/artifact/dom4j/dom4j -->
  48. <dependency>
  49. <groupId>dom4j</groupId>
  50. <artifactId>dom4j</artifactId>
  51. <version>1.6.1</version>
  52. </dependency>
  53. <!-- https://mvnrepository.com/artifact/org.java-websocket/Java-WebSocket -->
  54. <dependency>
  55. <groupId>org.java-websocket</groupId>
  56. <artifactId>Java-WebSocket</artifactId>
  57. <version>1.5.2</version>
  58. </dependency>

以及配置文件

  1. ###服务启动端口号
  2. server:
  3. ###节点1
  4. #port: 8001
  5. ###节点2
  6. port: 8002
  7. servlet:
  8. session:
  9. timeout: 7200
  10. max-http-header-size: 10000000
  11. tomcat:
  12. basedir: temp
  13. spring:
  14. servlet:
  15. multipart:
  16. max-file-size: 10MB
  17. max-request-size: 100MB
  18. application:
  19. name: miniblog
  20. ###当前p2p服务端口号:节点1
  21. #P2P_SERVER_PORT: 7001
  22. ###p2p服务
  23. #P2P_CLIENT_PEER:
  24. ###当前p2p服务端口号:节点2
  25. P2P_SERVER_PORT: 7002
  26. ###p2p服务
  27. P2P_CLIENT_PEER: ws://127.0.0.1:7001

3、启动测试的方式为

修改配置文件,将节点1的端口以及websocket的端口放开,启动完后切换到节点2的端口,然后访问如下api即可。

  1. #节点1
  2. http://localhost:8001/getWallet 获得钱包
  3. http://localhost:8001/mine 挖矿
  4. http://localhost:8001/balance 查询余额
  5. http://localhost:8001/getBlockChain 获得区块链
  6. http://localhost:8001/transaction?value=10&address=751f520e1a3c3de161003a32b616d03d 交易
  7. #节点2
  8. http://localhost:8002/getWallet 获得钱包
  9. http://localhost:8002/mine 挖矿
  10. http://localhost:8002/balance 查询余额
  11. http://localhost:8002/getBlockChain 获得区块链

转账后必须通过挖矿才能生效。

好了一个特别简陋,没有完善的区块链原型就实现了,当然应该还有无数的漏洞。
有兴趣可以参考下:https://www.suibibk.com/fileupload/files/minichain.zip

 665

啊!这个可能是世界上最丑的留言输入框功能~


当然,也是最丑的留言列表

有疑问发邮件到 : suibibk@qq.com 侵权立删
Copyright : 个人随笔   备案号 : 粤ICP备18099399号-2