Part2 :使用 Java 創建你的第一個區塊鏈[譯]

?本篇是該系列的第二篇,你可以在這里找到第一篇。原文鏈接在此
?在第本篇,我們將

  • 創建一個簡單的錢包
  • 在我們的區塊鏈上簽發一個交易。

?上面的這些過程,其實就產生了我們自己的加密貨幣。
?在上一篇文章中,我們有了一個可驗證的、基本的區塊鏈。但是,我們的鏈中僅僅存儲了一些無用的信息。今天,我們將把這些無用的信息替換為交易數據。這允許我們可以創建一個簡單的加密貨幣,我們稱之為 “NoobCoin”。
?本文中,還將使用 Bouncy CastleGSON 庫。

1. 準備錢包

?在加密貨幣中,貨幣的所有權在區塊鏈上以交易的形式流轉,交易者擁有一個可以地址可以轉入轉出。因此,對于一個錢包而言,至少需要能夠存儲這些地址。更多的,錢包也可以作為一個軟件用以在區塊鏈上產生新的交易。

transaction.png

?現在,讓我們倆創建一個持有我們公鑰和私鑰的 Wallet 類:

package noobchain;

import java.security.PrivateKey;
import java.security.PublicKey;

public class Wallet {
    public PrivateKey privateKey;
    public PublicKey publicKey ;
}

?公鑰和私鑰有什么用?
?對于我們的加密貨幣 noobcoin 來說,公鑰扮演者我們的錢包地址的角色,我們可以隨意分享自己的公鑰。私鑰,是用來 簽署(sign) 交易的,沒有人能花費我們的 noobcoin,除非,他擁有我們的私鑰。所以,用戶的私鑰一定要被保管好。公鑰部分通常會和交易一起發送的,用以驗證交易前面是否合法,交易內容是否被篡改。

key.png

?公鑰和私鑰通過 KeyPair 生成,接下來,我們將使用 橢圓曲線加密算法 來生成密鑰對。

public class Wallet {
    public PrivateKey privateKey;
    public PublicKey publicKey ;
    
    public Wallet(){
        generateKeyPair();  
    }
        
    public void generateKeyPair() {
        try {
            KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ECDSA","BC");
            SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
            ECGenParameterSpec ecSpec = new ECGenParameterSpec("prime192v1");
            // Initialize the key generator and generate a KeyPair
            keyGen.initialize(ecSpec, random);   //256 bytes provides an acceptable security level
                KeyPair keyPair = keyGen.generateKeyPair();
                // Set the public and private keys from the keyPair
                privateKey = keyPair.getPrivate();
                publicKey = keyPair.getPublic();
        }catch(Exception e) {
            throw new RuntimeException(e);
        }
    }
}

?現在,我們的錢包類差不多了,接下來,讓我們來看看交易。


cash.gif

2. 交易和簽名

?每一個交易,都要攜帶一定的數據:

  • 發送者資金的公鑰(譯注:相當發送者的地址)
  • 接受者資金的公鑰(譯注:相當于接受者的地址)
  • 交易資金的數目
  • inputs,證明發送者有足夠的幣發送
  • outputs,接收地址收到的總金額
  • 加密簽名,保證發送者簽署的交易不會被惡意篡改

?現在,我們來創建一個 Transaction 類:

import java.security.*;
import java.util.ArrayList;

public class Transaction {
    
    public String transactionId; // this is also the hash of the transaction.
    public PublicKey sender; // senders address/public key.
    public PublicKey reciepient; // Recipients address/public key.
    public float value;
    public byte[] signature; // this is to prevent anybody else from spending funds in our wallet.
    
    public ArrayList<TransactionInput> inputs = new ArrayList<TransactionInput>();
    public ArrayList<TransactionOutput> outputs = new ArrayList<TransactionOutput>();
    
    private static int sequence = 0; // a rough count of how many transactions have been generated. 
    
    // Constructor: 
    public Transaction(PublicKey from, PublicKey to, float value,  ArrayList<TransactionInput> inputs) {
        this.sender = from;
        this.reciepient = to;
        this.value = value;
        this.inputs = inputs;
    }
    
    // This Calculates the transaction hash (which will be used as its Id)
    private String calulateHash() {
        sequence++; //increase the sequence to avoid 2 identical transactions having the same hash
        return StringUtil.applySha256(
                StringUtil.getStringFromKey(sender) +
                StringUtil.getStringFromKey(reciepient) +
                Float.toString(value) + sequence
                );
    }
}

?此時,inputs 和 outputs 都是空的,后面我們會使用到它們。這個 Transaction 類還將包含生成、驗證簽名、驗證交易等相關方法。但是,簽名的目的是是什么?它們是如何工作的?

簽名的目的和工作原理

?簽名在區塊鏈中執行了兩個非常重要的任務:首先,允許擁有者們消費他們的幣,它會放置交易信息被篡改。
?私鑰被用來簽署數據,公鑰被用來驗證數據的完整性。

舉個栗子:Bob 想向 Sally 轉 2 個 NoobCoin,因此,錢包軟件會生成這個交易,然后將這個交易提交給礦工,以將該數據包含連接到區塊鏈中。如果礦工企圖將這 2 個幣的接收人改為 John。然而,幸運的是 Bob 使用私鑰簽署了這個交易數據,并且允許任何人使用他的公鑰驗證該交易的完整性、合法性。

?從上面的代碼中,我們可以看到,所謂簽名實際上一個 byte 數組,因此,下面讓我們來生成它。首先,這里需要一個 StringUtil 類:

//Applies ECDSA Signature and returns the result ( as bytes ).
public static byte[] applyECDSASig(PrivateKey privateKey, String input) {
Signature dsa;
byte[] output = new byte[0];
try {
    dsa = Signature.getInstance("ECDSA", "BC");
    dsa.initSign(privateKey);
    byte[] strByte = input.getBytes();
    dsa.update(strByte);
    byte[] realSig = dsa.sign();
    output = realSig;
} catch (Exception e) {
    throw new RuntimeException(e);
}
return output;
}

//Verifies a String signature 
public static boolean verifyECDSASig(PublicKey publicKey, String data, byte[] signature) {
try {
    Signature ecdsaVerify = Signature.getInstance("ECDSA", "BC");
    ecdsaVerify.initVerify(publicKey);
    ecdsaVerify.update(data.getBytes());
    return ecdsaVerify.verify(signature);
}catch(Exception e) {
    throw new RuntimeException(e);
}
}

public static String getStringFromKey(Key key) {
return Base64.getEncoder().encodeToString(key.getEncoded());
}

?現在,在 Transaction 類的 generateSignature()verifySignature() 方法中運用該簽名方法。


//Signs all the data we dont wish to be tampered with.
public void generateSignature(PrivateKey privateKey) {
    String data = StringUtil.getStringFromKey(sender) + StringUtil.getStringFromKey(reciepient) + Float.toString(value) ;
    signature = StringUtil.applyECDSASig(privateKey,data);      
}
//Verifies the data we signed hasnt been tampered with
public boolean verifiySignature() {
    String data = StringUtil.getStringFromKey(sender) + StringUtil.getStringFromKey(reciepient) + Float.toString(value) ;
    return StringUtil.verifyECDSASig(sender, data, signature);
}

?當該交易被礦工添加到區塊鏈上的一個新塊的時候,這個簽名將會被驗證。


check.gif

3.測試錢包和簽名

?現在,我們基本上已經完成了一半的工作。在 NoobChain 這個類中,添加一些新的變量,并替換到 main 方法中的一些方法。

import java.security.Security;
import java.util.ArrayList;
import java.util.Base64;
import com.google.gson.GsonBuilder;

public class NoobChain {
    
    public static ArrayList<Block> blockchain = new ArrayList<Block>();
    public static int difficulty = 5;
    public static Wallet walletA;
    public static Wallet walletB;

    public static void main(String[] args) {    
        //Setup Bouncey castle as a Security Provider
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); 
        //Create the new wallets
        walletA = new Wallet();
        walletB = new Wallet();
        //Test public and private keys
        System.out.println("Private and public keys:");
        System.out.println(StringUtil.getStringFromKey(walletA.privateKey));
        System.out.println(StringUtil.getStringFromKey(walletA.publicKey));
        //Create a test transaction from WalletA to walletB 
        Transaction transaction = new Transaction(walletA.publicKey, walletB.publicKey, 5, null);
        transaction.generateSignature(walletA.privateKey);
        //Verify the signature works and verify it from the public key
        System.out.println("Is signature verified");
        System.out.println(transaction.verifiySignature());
}

?現在,我們創建了兩個錢包,walletA 和 walletB,并且打印了 walletA 的私鑰和公鑰。你能看到的大概是這樣子的


image

?回過頭來看,現在我們需要創建/驗證 outputs 和 inputs,并且將他們存儲到區塊鏈上的交易上。

4. Inputs 和 Outputs

1. 加密貨幣的歸屬

?如果你要擁有一個 bitcoin,首先你要收到一個 bitcoin。這個過程,并非是在總賬單上將你的 bitcoin 加一 ,將發送者的 bitcoin 減一的過程。事實上,發送者肯定是前面也接收到的一個 bitcoin,然后才能將其發送到你的地址上。
?錢包的余額,是所有跟你地址(公鑰)相關的未花費出去的交易輸出的總和。(譯注:后面將會看到,實際上這個鏈會維護一個由 publicKey 做 key,TransactionOutput 做 value 的 HashMap,這個 map 是所有交易輸出的記錄,通過 publicKey 可以查找到關于其擁有者的 bitcoin 數量)
?接下來,我們遵從 bitcoin 慣例,將未花費出去的交易輸出命名為:UTXO.
?現在,我們來創建 TransactionInput 類:

public class TransactionInput {
    public String transactionOutputId; //Reference to TransactionOutputs -> transactionId
    public TransactionOutput UTXO; //Contains the Unspent transaction output

    public TransactionInput(String transactionOutputId) {
        this.transactionOutputId = transactionOutputId;
    }
}

?然后,創建 TransactionOutputs 類:

import java.security.PublicKey;

public class TransactionOutput {
    public String id;
    public PublicKey reciepient; //also known as the new owner of these coins.
    public float value; //the amount of coins they own
    public String parentTransactionId; //the id of the transaction this output was created in
    
    //Constructor
    public TransactionOutput(PublicKey reciepient, float value, String parentTransactionId) {
        this.reciepient = reciepient;
        this.value = value;
        this.parentTransactionId = parentTransactionId;
        this.id = StringUtil.applySha256(StringUtil.getStringFromKey(reciepient)+Float.toString(value)+parentTransactionId);
    }

    //Check if coin belongs to you
    public boolean isMine(PublicKey publicKey) {
        return (publicKey == reciepient);
    }
}

?通過 Transaction outputs 可以獲取到交易雙方通過交易獲取到的各自 bitcoin 的總數。因此,它也可以作為新交易的 inputs,以證明你有足夠多的幣用以交易。


done.gif

2. 處理交易

?鏈上的區塊,可能包含了很多交易,區塊鏈可能會很長很長很長很長,也因此,在處理新塊的時候,可能會花費很長很長的時間,因為我們需要查找并檢查它的輸入。為了繞過這一點,我們將使用一個額外的集合,以保存未花費的交易。在 NoobChain 中,我們通過 UTXO 表示:

public class NoobChain {
    
    public static ArrayList<Block> blockchain = new ArrayList<Block>();
    public static HashMap<String,TransactionOutput> UTXOs = new HashMap<String,TransactionOutput>(); //list of all unspent transactions. 
    public static int difficulty = 5;
    public static Wallet walletA;
    public static Wallet walletB;

    public static void main(String[] args) {
    ......

?ok,現在是時候來揭曉事情的真相了。
?讓我們把所有事情集中起來,在 Transactoin 處理:

//Returns true if new transaction could be created. 
public boolean processTransaction() {

    if(verifiySignature() == false) {
        System.out.println("#Transaction Signature failed to verify");
        return false;
    }

    //gather transaction inputs (Make sure they are unspent):
    for(TransactionInput i : inputs) {
        i.UTXO = NoobChain.UTXOs.get(i.transactionOutputId);
    }

    //check if transaction is valid:
    if(getInputsValue() < NoobChain.minimumTransaction) {
        System.out.println("#Transaction Inputs to small: " + getInputsValue());
        return false;
    }

    //generate transaction outputs:
    float leftOver = getInputsValue() - value; //get value of inputs then the left over change:
    transactionId = calulateHash();
    outputs.add(new TransactionOutput( this.reciepient, value,transactionId)); //send value to recipient
    outputs.add(new TransactionOutput( this.sender, leftOver,transactionId)); //send the left over 'change' back to sender      

    //add outputs to Unspent list
    for(TransactionOutput o : outputs) {
        NoobChain.UTXOs.put(o.id , o);
    }

    //remove transaction inputs from UTXO lists as spent:
    for(TransactionInput i : inputs) {
        if(i.UTXO == null) continue; //if Transaction can't be found skip it 
        NoobChain.UTXOs.remove(i.UTXO.id);
    }

    return true;
}

//returns sum of inputs(UTXOs) values
public float getInputsValue() {
    float total = 0;
    for(TransactionInput i : inputs) {
        if(i.UTXO == null) continue; //if Transaction can't be found skip it 
        total += i.UTXO.value;
    }
    return total;
}

//returns sum of outputs:
public float getOutputsValue() {
    float total = 0;
    for(TransactionOutput o : outputs) {
        total += o.value;
    }
    return total;
}

?使用這個方法,我們執行一些檢查,確保交易的合法性,接著,收集輸入,并產生輸出。
?很重要的一點,在結尾處,我們從 UTXO 中刪除了 Inputs。這意味著 transaction output 僅有一次機會作為輸入...所有輸入值,都將在本次交易中被使用,如果沒有使用完,剩余的部分會返回到自身中。

image

?最后,讓我們來更新下錢包:

  • 計算余額(通過對 UTXO 循環,計算屬于“我”的余額,判斷是否有足夠的余額進行交易 )。
  • 產生新的 transaction(交易)。

import java.security.*;
import java.security.spec.ECGenParameterSpec;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

public class Wallet {
    
    public PrivateKey privateKey;
    public PublicKey publicKey;
    
    public HashMap<String,TransactionOutput> UTXOs = new HashMap<String,TransactionOutput>(); //only UTXOs owned by this wallet.
    
    public Wallet() {...
        
    public void generateKeyPair() {...
    
  //returns balance and stores the UTXO's owned by this wallet in this.UTXOs
    public float getBalance() {
        float total = 0;    
        for (Map.Entry<String, TransactionOutput> item: NoobChain.UTXOs.entrySet()){
            TransactionOutput UTXO = item.getValue();
            if(UTXO.isMine(publicKey)) { //if output belongs to me ( if coins belong to me )
                UTXOs.put(UTXO.id,UTXO); //add it to our list of unspent transactions.
                total += UTXO.value ; 
            }
        }  
        return total;
    }
    //Generates and returns a new transaction from this wallet.
    public Transaction sendFunds(PublicKey _recipient,float value ) {
        if(getBalance() < value) { //gather balance and check funds.
            System.out.println("#Not Enough funds to send transaction. Transaction Discarded.");
            return null;
        }
    //create array list of inputs
        ArrayList<TransactionInput> inputs = new ArrayList<TransactionInput>();
    
        float total = 0;
        for (Map.Entry<String, TransactionOutput> item: UTXOs.entrySet()){
            TransactionOutput UTXO = item.getValue();
            total += UTXO.value;
            inputs.add(new TransactionInput(UTXO.id));
            if(total > value) break;
        }
        Transaction newTransaction = new Transaction(publicKey, _recipient , value, inputs);
        newTransaction.generateSignature(privateKey);
        for(TransactionInput input: inputs){
            UTXOs.remove(input.transactionOutputId);
        }
        return newTransaction;
    }
}

6. 將交易添加到區塊中

?現在,我們有了一個可以工作的交易系統了,接下來,需要將其實現到區塊鏈上。現在,我們可以將以前那些無用的數據替換為 ArrayList of transactions(交易列表),同時,使用根哈希的方式,計算區塊的哈希值。
?下面,在 StringUtil 中增加一個獲取根哈希(譯注:可以參考 維基百科)的方法:

//Tacks in array of transactions and returns a merkle root.
public static String getMerkleRoot(ArrayList<Transaction> transactions) {
    int count = transactions.size();
    ArrayList<String> previousTreeLayer = new ArrayList<String>();
    for(Transaction transaction : transactions) {
        previousTreeLayer.add(transaction.transactionId);
    }
    ArrayList<String> treeLayer = previousTreeLayer;
    while(count > 1) {
        treeLayer = new ArrayList<String>();
        for(int i=1; i < previousTreeLayer.size(); i++) {
            treeLayer.add(applySha256(previousTreeLayer.get(i-1) + previousTreeLayer.get(i)));
        }
        count = treeLayer.size();
        previousTreeLayer = treeLayer;
    }
    String merkleRoot = (treeLayer.size() == 1) ? treeLayer.get(0) : "";
    return merkleRoot;
}

?再接下來,進行 Block 中的修改:

import java.util.ArrayList;
import java.util.Date;

public class Block {
    
    public String hash;
    public String previousHash; 
    public String merkleRoot;
    public ArrayList<Transaction> transactions = new ArrayList<Transaction>(); //our data will be a simple message.
    public long timeStamp; //as number of milliseconds since 1/1/1970.
    public int nonce;
    
    //Block Constructor.  
    public Block(String previousHash ) {
        this.previousHash = previousHash;
        this.timeStamp = new Date().getTime();
        
        this.hash = calculateHash(); //Making sure we do this after we set the other values.
    }
    
    //Calculate new hash based on blocks contents
    public String calculateHash() {
        String calculatedhash = StringUtil.applySha256( 
                previousHash +
                Long.toString(timeStamp) +
                Integer.toString(nonce) + 
                merkleRoot
                );
        return calculatedhash;
    }
    
    //Increases nonce value until hash target is reached.
    public void mineBlock(int difficulty) {
        merkleRoot = StringUtil.getMerkleRoot(transactions);
        String target = StringUtil.getDificultyString(difficulty); //Create a string with difficulty * "0" 
        while(!hash.substring( 0, difficulty).equals(target)) {
            nonce ++;
            hash = calculateHash();
        }
        System.out.println("Block Mined!!! : " + hash);
    }
    
    //Add transactions to this block
    public boolean addTransaction(Transaction transaction) {
        //process transaction and check if valid, unless block is genesis block then ignore.
        if(transaction == null) return false;       
        if((previousHash != "0")) {
            if((transaction.processTransaction() != true)) {
                System.out.println("Transaction failed to process. Discarded.");
                return false;
            }
        }
        transactions.add(transaction);
        System.out.println("Transaction Successfully added to Block");
        return true;
    }
    
}

?注意,我們更新了 Block 的構造器,不在傳入一個字符串,并且添加了用于計算哈希值的 merkle root(根哈希)屬性。
?addTransaction 方法將返回一個 boolean,以表示交易是否成功。

7. 華麗的落幕

?最后,我們還需要測試從錢包中消費 noobcoin ,更新區塊鏈合法性檢測。但是,首先,我們還是需要引入這些新的 coins。其實,還是有很多方式引入新的 coins,比如在比特幣區塊鏈上,新幣可以作為對礦工挖到礦的獎勵。在本文中,我們將采用直接在創世區塊中釋放所有幣的方式。
?我們來更新下 NoobChain 類:

  • 創世區塊將向 walletA 發放 100 個 Noobcoins.
  • 當交易發生后,檢測區塊鏈的合法性
  • 通過一些交易,測試是否一切工作 ok
public class NoobChain {
    
    public static ArrayList<Block> blockchain = new ArrayList<Block>();
    public static HashMap<String,TransactionOutput> UTXOs = new HashMap<String,TransactionOutput>();
    
    public static int difficulty = 3;
    public static float minimumTransaction = 0.1f;
    public static Wallet walletA;
    public static Wallet walletB;
    public static Transaction genesisTransaction;

    public static void main(String[] args) {    
        //add our blocks to the blockchain ArrayList:
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); //Setup Bouncey castle as a Security Provider
        
        //Create wallets:
        walletA = new Wallet();
        walletB = new Wallet();     
        Wallet coinbase = new Wallet();
        
        //create genesis transaction, which sends 100 NoobCoin to walletA: 
        genesisTransaction = new Transaction(coinbase.publicKey, walletA.publicKey, 100f, null);
        genesisTransaction.generateSignature(coinbase.privateKey);   //manually sign the genesis transaction    
        genesisTransaction.transactionId = "0"; //manually set the transaction id
        genesisTransaction.outputs.add(new TransactionOutput(genesisTransaction.reciepient, genesisTransaction.value, genesisTransaction.transactionId)); //manually add the Transactions Output
        UTXOs.put(genesisTransaction.outputs.get(0).id, genesisTransaction.outputs.get(0)); //its important to store our first transaction in the UTXOs list.
        
        System.out.println("Creating and Mining Genesis block... ");
        Block genesis = new Block("0");
        genesis.addTransaction(genesisTransaction);
        addBlock(genesis);
        
        //testing
        Block block1 = new Block(genesis.hash);
        System.out.println("\nWalletA's balance is: " + walletA.getBalance());
        System.out.println("\nWalletA is Attempting to send funds (40) to WalletB...");
        block1.addTransaction(walletA.sendFunds(walletB.publicKey, 40f));
        addBlock(block1);
        System.out.println("\nWalletA's balance is: " + walletA.getBalance());
        System.out.println("WalletB's balance is: " + walletB.getBalance());
        
        Block block2 = new Block(block1.hash);
        System.out.println("\nWalletA Attempting to send more funds (1000) than it has...");
        block2.addTransaction(walletA.sendFunds(walletB.publicKey, 1000f));
        addBlock(block2);
        System.out.println("\nWalletA's balance is: " + walletA.getBalance());
        System.out.println("WalletB's balance is: " + walletB.getBalance());
        
        Block block3 = new Block(block2.hash);
        System.out.println("\nWalletB is Attempting to send funds (20) to WalletA...");
        block3.addTransaction(walletB.sendFunds( walletA.publicKey, 20));
        System.out.println("\nWalletA's balance is: " + walletA.getBalance());
        System.out.println("WalletB's balance is: " + walletB.getBalance());
        
        isChainValid();
        
    }
    
    public static Boolean isChainValid() {
        Block currentBlock; 
        Block previousBlock;
        String hashTarget = new String(new char[difficulty]).replace('\0', '0');
        HashMap<String,TransactionOutput> tempUTXOs = new HashMap<String,TransactionOutput>(); //a temporary working list of unspent transactions at a given block state.
        tempUTXOs.put(genesisTransaction.outputs.get(0).id, genesisTransaction.outputs.get(0));
        
        //loop through blockchain to check hashes:
        for(int i=1; i < blockchain.size(); i++) {
            
            currentBlock = blockchain.get(i);
            previousBlock = blockchain.get(i-1);
            //compare registered hash and calculated hash:
            if(!currentBlock.hash.equals(currentBlock.calculateHash()) ){
                System.out.println("#Current Hashes not equal");
                return false;
            }
            //compare previous hash and registered previous hash
            if(!previousBlock.hash.equals(currentBlock.previousHash) ) {
                System.out.println("#Previous Hashes not equal");
                return false;
            }
            //check if hash is solved
            if(!currentBlock.hash.substring( 0, difficulty).equals(hashTarget)) {
                System.out.println("#This block hasn't been mined");
                return false;
            }
            
            //loop thru blockchains transactions:
            TransactionOutput tempOutput;
            for(int t=0; t <currentBlock.transactions.size(); t++) {
                Transaction currentTransaction = currentBlock.transactions.get(t);
                
                if(!currentTransaction.verifiySignature()) {
                    System.out.println("#Signature on Transaction(" + t + ") is Invalid");
                    return false; 
                }
                if(currentTransaction.getInputsValue() != currentTransaction.getOutputsValue()) {
                    System.out.println("#Inputs are note equal to outputs on Transaction(" + t + ")");
                    return false; 
                }
                
                for(TransactionInput input: currentTransaction.inputs) {    
                    tempOutput = tempUTXOs.get(input.transactionOutputId);
                    
                    if(tempOutput == null) {
                        System.out.println("#Referenced input on Transaction(" + t + ") is Missing");
                        return false;
                    }
                    
                    if(input.UTXO.value != tempOutput.value) {
                        System.out.println("#Referenced input Transaction(" + t + ") value is Invalid");
                        return false;
                    }
                    
                    tempUTXOs.remove(input.transactionOutputId);
                }
                
                for(TransactionOutput output: currentTransaction.outputs) {
                    tempUTXOs.put(output.id, output);
                }
                
                if( currentTransaction.outputs.get(0).reciepient != currentTransaction.reciepient) {
                    System.out.println("#Transaction(" + t + ") output reciepient is not who it should be");
                    return false;
                }
                if( currentTransaction.outputs.get(1).reciepient != currentTransaction.sender) {
                    System.out.println("#Transaction(" + t + ") output 'change' is not sender.");
                    return false;
                }
                
            }
            
        }
        System.out.println("Blockchain is valid");
        return true;
    }
    
    public static void addBlock(Block newBlock) {
        newBlock.mineBlock(difficulty);
        blockchain.add(newBlock);
    }
}

?你可以在 Github 上下載到這個項目。

參考

哈希樹

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。