基于Java 实现把区块链变成数字货币

250 阅读4分钟

基于Java 实现把区块链变成数字货币

1. 交易数据

/**
 * 转账数据信息
 * 转账的要素:
 * 付款人
 * 收款人
 * 转账金额
 * 转账时间
 */
public class Transaction {

    private String from;

    private String to;

    private Long  amount;

//    private Date timeStamp;  转账时间 最终以上链时间为准,顾需要再区块加一个时间

    public Transaction() {

    }

    public Transaction(String from, String to, Long amount) {
        this.from = from;
        this.to = to;
        this.amount = amount;
//        this.timeStamp = timeStamp;
    }

    public String getFrom() {
        return from;
    }

    public void setFrom(String from) {
        this.from = from;
    }

    public String getTo() {
        return to;
    }

    public void setTo(String to) {
        this.to = to;
    }

    public Long getAmount() {
        return amount;
    }

    public void setAmount(Long amount) {
        this.amount = amount;
    }

//    public Date getTimeStamp() {
//        return timeStamp;
//    }
//
//    public void setTimeStamp(Date timeStamp) {
//        this.timeStamp = timeStamp;
//    }



    @Override
    public String toString() {
        return "Transaction{" +
                "from='" + from + '\'' +
                ", to='" + to + '\'' +
                ", amount=" + amount +
                '}';
    }
}

2. 创建区块

import java.time.LocalDateTime;
import java.util.List;

/**
 * 区块链 里边存放的数据是:
 * data 数据
 * preHash 上一个区块的hash值
 * hash 自己的 hash值 它是由存储在区块链的信息 计算出来的(data + 之前区块的哈希值) ,采用 sha256 算法
 */
public class Block {

    //当区块链上边存储的数据是转账数据的时候 我们就可以把它变成数字货币
//    private Object data;
    // 现在要把Object 的 data 变为 List Transaction 对象
    private List<Transaction> transactions;

    private String  preHash;

    private String  hash;

    // 初始随机值
    private Long nonce;

    // 区块的上链时间
    private LocalDateTime timestamp;

//    public Block(Object data, String preHash) {
//        this.data = data;
//        this.preHash = preHash;
//        this.hash = this.computeHash();
//        this.nonce = 1L;
//    }

    public Block () {

    }

    public Block(List<Transaction> transactions, String preHash) {
        this.transactions = transactions;
        this.preHash = preHash;
        this.hash = this.computeHash();
        this.nonce = 1L;
        this.timestamp = LocalDateTime.now();
    }

//    public Object getData() {
//        return data;
//    }
//
//    public void setData(Object data) {
//        this.data = data;
//    }


    public List<Transaction> getTransactions() {
        return transactions;
    }

    public void setTransactions(List<Transaction> transactions) {
        this.transactions = transactions;
    }

    public String getPreHash() {
        return preHash;
    }

    public void setPreHash(String preHash) {
        this.preHash = preHash;
    }

    public String getHash() {
        return hash;
    }

    public void setHash(String hash) {
        this.hash = hash;
    }

    public Long getNonce() {
        return nonce;
    }

    public void setNonce(Long nonce) {
        this.nonce = nonce;
    }

    public LocalDateTime getTimestamp() {
        return timestamp;
    }

    public void setTimestamp(LocalDateTime timestamp) {
        this.timestamp = timestamp;
    }

    public String computeHash() {
        // 要加入随机值 nonce
//        return new Encrypt().SHA256(this.data + this.preHash + this.nonce);
        // timestamp 参与改动hash 的计算
        return new Encrypt().SHA256(this.transactions.toString() + this.preHash + this.nonce + this.timestamp);
    }

    // 开头前n 位为0的 hash值
    public String  getAnswer(Integer difficulty) {
        String answer = "";
        for (int i = 0; i < difficulty; i++) {
            answer +="0";
        }
        return answer;

    }
    // 计算符合区块链难度要求的hash 这个过程 俗称 挖矿
    public void mine(Integer difficulty) {
        while (true) {
            this.hash = this.computeHash();
            if (! this.hash.substring(0,difficulty).equals(this.getAnswer(difficulty))) {
                // nonce ++ 才会得到不同的hash值
                this.nonce ++ ;
                this.hash =this.computeHash();
            } else {
                break;
            }
        }
        System.out.println("挖矿结束:"+ this.hash);
    }

//    @Override
//    public String toString() {
//        return "Block{" +
//                "data=" + data +
//                ", preHash='" + preHash + '\'' +
//                ", hash='" + hash + '\'' +
//                ", nonce=" + nonce +
//                '}';
//    }


    @Override
    public String toString() {
        return "Block{" +
                "transactions=" + transactions +
                ", preHash='" + preHash + '\'' +
                ", hash='" + hash + '\'' +
                ", nonce=" + nonce +
                ", timestamp=" + timestamp +
                '}';
    }

}

3. 创建链 并测试数据

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * 参考:https://github.com/ycraaron/LuotuoCoin
 * 区块的链
 */
public class Chain {

    private List<Block> chain = new ArrayList<>();

    // 交易池
    private List<Transaction> transactionPool;
    // 每挖出来一个区块会有50个比特币的产生会发放到矿工这里 这是也是从50开始为初始值
    private Long minerReward;

    // 工作量难度
    private Integer difficulty;

    public Chain() {
        chain.add(bigBang());
        this.transactionPool = new ArrayList<>();
        this.minerReward = 50L;
        this.difficulty = 5;
    }

    public Chain(List<Block> chain) {
        chain.add(bigBang());
        this.chain.addAll(chain);
    }

    /**
     * 每个区块都会生成一个祖先区块
     * @return
     */
    public Block bigBang() {
        // 创建祖先节点 hash为空
        Transaction transaction = new Transaction("我是祖先","",0L);
        return new Block(Arrays.asList(transaction),"");
//        Block block = new Block();
//        return new Block("祖先","");
//         return  block;
    }

    /**
     * 获取最后一个区块的信息
     * @return
     */
    public Block getLatestBlock() {
        return getChain().get(this.chain.size()-1);
    }
    /**
     * 添加区块到区块链上
     * 找到最近一个block的hash
     * 这个hash就是新区块的preHash
     */
    public void addBlockToChain(Block newBlock) {
        newBlock.setPreHash(this.getLatestBlock().computeHash());
        newBlock.setHash(newBlock.computeHash());
        // 比特币 控制 每十分钟出一个区块了 就是通过 ProofOfWork 工作机制
        // 这个hash 需要满足一个区块链设置的条件
        newBlock.mine(this.difficulty);
        this.chain.add(newBlock);
    }

    /**
     * 添加 transaction 到 transactionPool 里边
     * @param transaction 交易对象
     */
    public void addTransaction(Transaction transaction) {
        this.transactionPool.add(transaction);
    }
    /**
     * 现在所有的区块都不是从外部传进来的,每当  被发出来的时候,矿工会从池子中 去拿 transaction, 以transaction数据为基础去
     * 新建这个block,意味着 block 新建发生在链上, 发生在挖transaction这步操作里边。
     * @param minerRewardAddress 挖矿人的address, 矿工调用的时候传过来的
     * @return
     */
    public void  mineTransactionPool (String minerRewardAddress) {
        // 1. 发放矿工奖励 矿工的奖励是以transaction的形式发放
        // 发放地址为空 因为这是链来发放的
        // minerReward 由链产生并发放
        Transaction minerRewardTransaction = new Transaction("", minerRewardAddress, this.minerReward);
        this.transactionPool.add(minerRewardTransaction);
        // 2. 挖矿  每个区块从链上新建 而不是外部传过来
        // 现实生活当中 每个 Block 可以容纳 一定量的 Transaction,在挖矿的时候 选择收益最高的矿去挖,所以它会从transactionPool
        // 调取最多收益的去挖这个 block ,这种情况就不考虑了,简单起见 每次就报所有的block 去挖
        Block newBlock = new Block(this.transactionPool, this.getLatestBlock().getHash());
        newBlock.mine(this.difficulty);
        // 3. 添加区块到区块链
        this.chain.add(newBlock);
        // 清空 这次挖出来的 transaction Pool
//        this.transactionPool.clear();
    }

    public List<Block> getChain() {
        return chain;
    }

    public void setChain(List<Block> chain) {
        this.chain = chain;
    }

    /**
     * 验证这个当前的区块链是否合法
     * 当前的区块是否被篡改
     *  我们要验证区块的preHash是否等于previous区块hash
     */
    public Boolean validateBlockChain() {
        // 创始区块的校验
        if (this.chain.size() == 1) {
            // 比较创始区块的hash值
            if (! this.chain.get(0).getHash().equalsIgnoreCase(this.chain.get(0).computeHash())) {
                return false;
            }
           return true;
        } else {
            // this.chain[1] 是第二个区块
            // 我们从第二个区块开始验证
            // 验证到最后一个区块  this.chain.size() -1
            for (int i = 1; i <= this.chain.size() -1; i++) {
                Block blockValidate = this.chain.get(i);
                // 验证当前数据是否被篡改
                if (!blockValidate.getHash().equalsIgnoreCase(blockValidate.computeHash())) {
                    System.out.println("数据被篡改!");
                    return false;
                }
                // 验证区块的previousHash 是否等于 previous区块的hash
                Block previousBlock = this.chain.get(i-1);
                //
                if (!blockValidate.getPreHash().equalsIgnoreCase(previousBlock.getHash())) {
                    System.out.println("前后区块链接断裂!");
                    return  false;
                }
            }
            return  true;
        }
    }

    @Override
    public String toString() {
        return "Chain{" +
                "chain=" + chain +
                ", transactionPool=" + transactionPool +
                ", minerReward=" + minerReward +
                ", difficulty=" + difficulty +
                '}';
    }

    public static void main(String[] args) {
//        Chain chain = new Chain();
////        System.out.println(chain.validateBlockChain());
//        Block block1 = new Block("转账10元","1234");
//        chain.addBlockToChain(block1);
//        Block block2 = new Block("转账20元","1234");
//        chain.addBlockToChain(block2);
//        System.out.println(chain);
//
////        System.out.println(chain.validateBlockChain());
////        // 篡改区块链上的数据
//        chain.getChain().get(1).setData("转账30元");
////        System.out.println(chain.validateBlockChain());
//        // 篡改区块链的hash值
////        chain.getChain().get(1).setHash(chain.getChain().get(1).computeHash());
//        chain.getChain().get(1).mine(5);
//        System.out.println(chain);
//        System.out.println(chain.validateBlockChain());
        Chain chainCoin = new Chain();
        Transaction t1 = new Transaction("add1", "add2", 10L);
        Transaction t2 = new Transaction("add2", "add1", 5L);
        chainCoin.addTransaction(t1);
        chainCoin.addTransaction(t2);
//        System.out.println(chainCoin);
        chainCoin.mineTransactionPool("add3");
//        System.out.println(chainCoin);

        System.out.println(chainCoin.getChain().get(1));
        System.out.println(chainCoin.getChain().get(1).getTransactions());

    }
}

4. SHA-256 算法

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 *JAVA 已经实现了 SHA-256 和 SHA-512 两种 Hash 算法
 *利用 java.security.MessageDigest 调用已经集成的 Hash 算法
 *创建 Encrypt 对象,并调用 SHA256 或者 SHA512 并传入要加密的文本信息,分别得到 SHA-256 或 SHA-512 两种被加密的 hash 串。
 *若要改为 MD5 算法,修改传入参数 strType 为 "MD5" 即可得到 MD5 加密功能。
 */
public class Encrypt {
 
  /**
   * 传入文本内容,返回 SHA-256 串
   * 
   * @param strText
   * @return
   */
  public String SHA256(final String strText) {
    return SHA(strText, "SHA-256");
  }
 
  /**
   * 传入文本内容,返回 SHA-512 串
   * 
   * @param strText
   * @return
   */
  public String SHA512(final String strText) {
    return SHA(strText, "SHA-512");
  }
 
  /**
   * 字符串 SHA 加密
   * 
   * @param strText
   * @param strType
   * @return
   */
  private String SHA(final String strText, final String strType) {
    // 返回值
    String strResult = null;
 
    // 是否是有效字符串
    if (strText != null && strText.length() > 0) {
      try {
        // SHA 加密开始
        // 创建加密对象 并傳入加密類型
        MessageDigest messageDigest = MessageDigest.getInstance(strType);
        // 传入要加密的字符串
        messageDigest.update(strText.getBytes());
        // 得到 byte 類型结果
        byte byteBuffer[] = messageDigest.digest();
 
        // 將 byte 轉換爲 string
        StringBuffer strHexString = new StringBuffer();
        // 遍歷 byte buffer
        for (int i = 0; i < byteBuffer.length; i++) {
          String hex = Integer.toHexString(0xff & byteBuffer[i]);
          if (hex.length() == 1) {
            strHexString.append('0');
          }
          strHexString.append(hex);
        }
        // 得到返回結果
        strResult = strHexString.toString();
      }
      catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
      }
    }
    return strResult;
  }
}