Part2 :使用 Java 創(chuàng)建你的第一個區(qū)塊鏈[譯]

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

  • 創(chuàng)建一個簡單的錢包
  • 在我們的區(qū)塊鏈上簽發(fā)一個交易。

?上面的這些過程声旺,其實就產(chǎn)生了我們自己的加密貨幣笔链。
?在上一篇文章中,我們有了一個可驗證的腮猖、基本的區(qū)塊鏈鉴扫。但是,我們的鏈中僅僅存儲了一些無用的信息澈缺。今天坪创,我們將把這些無用的信息替換為交易數(shù)據(jù)炕婶。這允許我們可以創(chuàng)建一個簡單的加密貨幣,我們稱之為 “NoobCoin”莱预。
?本文中柠掂,還將使用 Bouncy CastleGSON 庫。

1. 準備錢包

?在加密貨幣中依沮,貨幣的所有權(quán)在區(qū)塊鏈上以交易的形式流轉(zhuǎn)涯贞,交易者擁有一個可以地址可以轉(zhuǎn)入轉(zhuǎn)出。因此危喉,對于一個錢包而言肩狂,至少需要能夠存儲這些地址。更多的姥饰,錢包也可以作為一個軟件用以在區(qū)塊鏈上產(chǎn)生新的交易傻谁。

transaction.png

?現(xiàn)在,讓我們倆創(chuàng)建一個持有我們公鑰和私鑰的 Wallet 類:

package noobchain;

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

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

?公鑰和私鑰有什么用列粪?
?對于我們的加密貨幣 noobcoin 來說审磁,公鑰扮演者我們的錢包地址的角色,我們可以隨意分享自己的公鑰岂座。私鑰态蒂,是用來 簽署(sign) 交易的,沒有人能花費我們的 noobcoin费什,除非钾恢,他擁有我們的私鑰。所以鸳址,用戶的私鑰一定要被保管好瘩蚪。公鑰部分通常會和交易一起發(fā)送的,用以驗證交易前面是否合法稿黍,交易內(nèi)容是否被篡改疹瘦。

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);
        }
    }
}

?現(xiàn)在,我們的錢包類差不多了酣栈,接下來险胰,讓我們來看看交易。


cash.gif

2. 交易和簽名

?每一個交易矿筝,都要攜帶一定的數(shù)據(jù):

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

?現(xiàn)在缨睡,我們來創(chuàng)建一個 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 類還將包含生成、驗證簽名沛贪、驗證交易等相關(guān)方法陋守。但是,簽名的目的是是什么利赋?它們是如何工作的水评?

簽名的目的和工作原理

?簽名在區(qū)塊鏈中執(zhí)行了兩個非常重要的任務(wù):首先,允許擁有者們消費他們的幣媚送,它會放置交易信息被篡改中燥。
?私鑰被用來簽署數(shù)據(jù),公鑰被用來驗證數(shù)據(jù)的完整性塘偎。

舉個栗子:Bob 想向 Sally 轉(zhuǎn) 2 個 NoobCoin疗涉,因此,錢包軟件會生成這個交易吟秩,然后將這個交易提交給礦工咱扣,以將該數(shù)據(jù)包含連接到區(qū)塊鏈中。如果礦工企圖將這 2 個幣的接收人改為 John涵防。然而闹伪,幸運的是 Bob 使用私鑰簽署了這個交易數(shù)據(jù),并且允許任何人使用他的公鑰驗證該交易的完整性壮池、合法性偏瓤。

?從上面的代碼中,我們可以看到椰憋,所謂簽名實際上一個 byte 數(shù)組硼补,因此,下面讓我們來生成它熏矿。首先已骇,這里需要一個 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());
}

?現(xiàn)在,在 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);
}

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


check.gif

3.測試錢包和簽名

?現(xiàn)在慧域,我們基本上已經(jīng)完成了一半的工作鲤竹。在 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());
}

?現(xiàn)在碘橘,我們創(chuàng)建了兩個錢包,walletA 和 walletB吱肌,并且打印了 walletA 的私鑰和公鑰痘拆。你能看到的大概是這樣子的


image

?回過頭來看,現(xiàn)在我們需要創(chuàng)建/驗證 outputs 和 inputs氮墨,并且將他們存儲到區(qū)塊鏈上的交易上纺蛆。

4. Inputs 和 Outputs

1. 加密貨幣的歸屬

?如果你要擁有一個 bitcoin,首先你要收到一個 bitcoin规揪。這個過程桥氏,并非是在總賬單上將你的 bitcoin 加一 ,將發(fā)送者的 bitcoin 減一的過程猛铅。事實上字支,發(fā)送者肯定是前面也接收到的一個 bitcoin,然后才能將其發(fā)送到你的地址上奸忽。
?錢包的余額祥款,是所有跟你地址(公鑰)相關(guān)的未花費出去的交易輸出的總和。(譯注:后面將會看到月杉,實際上這個鏈會維護一個由 publicKey 做 key刃跛,TransactionOutput 做 value 的 HashMap,這個 map 是所有交易輸出的記錄苛萎,通過 publicKey 可以查找到關(guān)于其擁有者的 bitcoin 數(shù)量)
?接下來桨昙,我們遵從 bitcoin 慣例,將未花費出去的交易輸出命名為:UTXO.
?現(xiàn)在腌歉,我們來創(chuàng)建 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;
    }
}

?然后蛙酪,創(chuàng)建 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 的總數(shù)。因此翘盖,它也可以作為新交易的 inputs桂塞,以證明你有足夠多的幣用以交易。


done.gif

2. 處理交易

?鏈上的區(qū)塊馍驯,可能包含了很多交易阁危,區(qū)塊鏈可能會很長很長很長很長,也因此汰瘫,在處理新塊的時候狂打,可能會花費很長很長的時間,因為我們需要查找并檢查它的輸入混弥。為了繞過這一點趴乡,我們將使用一個額外的集合,以保存未花費的交易。在 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蒿涎,現(xiàn)在是時候來揭曉事情的真相了。
?讓我們把所有事情集中起來惦辛,在 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;
}

?使用這個方法劳秋,我們執(zhí)行一些檢查,確保交易的合法性裙品,接著,收集輸入俗或,并產(chǎn)生輸出市怎。
?很重要的一點,在結(jié)尾處辛慰,我們從 UTXO 中刪除了 Inputs区匠。這意味著 transaction output 僅有一次機會作為輸入...所有輸入值,都將在本次交易中被使用帅腌,如果沒有使用完驰弄,剩余的部分會返回到自身中。

image

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

  • 計算余額(通過對 UTXO 循環(huán)戚篙,計算屬于“我”的余額,判斷是否有足夠的余額進行交易 )溺职。
  • 產(chǎn)生新的 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. 將交易添加到區(qū)塊中

?現(xiàn)在,我們有了一個可以工作的交易系統(tǒng)了浪耘,接下來乱灵,需要將其實現(xiàn)到區(qū)塊鏈上。現(xiàn)在七冲,我們可以將以前那些無用的數(shù)據(jù)替換為 ArrayList of transactions(交易列表)痛倚,同時,使用根哈希的方式澜躺,計算區(qū)塊的哈希值蝉稳。
?下面,在 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 的構(gòu)造器,不在傳入一個字符串通铲,并且添加了用于計算哈希值的 merkle root(根哈希)屬性毕莱。
?addTransaction 方法將返回一個 boolean,以表示交易是否成功。

7. 華麗的落幕

?最后朋截,我們還需要測試從錢包中消費 noobcoin 蛹稍,更新區(qū)塊鏈合法性檢測。但是部服,首先唆姐,我們還是需要引入這些新的 coins。其實廓八,還是有很多方式引入新的 coins奉芦,比如在比特幣區(qū)塊鏈上,新幣可以作為對礦工挖到礦的獎勵剧蹂。在本文中声功,我們將采用直接在創(chuàng)世區(qū)塊中釋放所有幣的方式。
?我們來更新下 NoobChain 類:

  • 創(chuàng)世區(qū)塊將向 walletA 發(fā)放 100 個 Noobcoins.
  • 當交易發(fā)生后宠叼,檢測區(qū)塊鏈的合法性
  • 通過一些交易先巴,測試是否一切工作 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 上下載到這個項目。

參考

哈希樹

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末冒冬,一起剝皮案震驚了整個濱河市伸蚯,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌简烤,老刑警劉巖剂邮,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異横侦,居然都是意外死亡抗斤,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門丈咐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來瑞眼,“玉大人,你說我怎么就攤上這事棵逊∩烁恚” “怎么了?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵辆影,是天一觀的道長徒像。 經(jīng)常有香客問我,道長蛙讥,這世上最難降的妖魔是什么锯蛀? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮次慢,結(jié)果婚禮上旁涤,老公的妹妹穿的比我還像新娘翔曲。我一直安慰自己,他們只是感情好劈愚,可當我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布瞳遍。 她就那樣靜靜地躺著,像睡著了一般菌羽。 火紅的嫁衣襯著肌膚如雪掠械。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天注祖,我揣著相機與錄音猾蒂,去河邊找鬼。 笑死是晨,一個胖子當著我的面吹牛肚菠,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播署鸡,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼案糙,長吁一口氣:“原來是場噩夢啊……” “哼限嫌!你這毒婦竟也來了靴庆?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤怒医,失蹤者是張志新(化名)和其女友劉穎炉抒,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體稚叹,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡焰薄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了扒袖。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片塞茅。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖季率,靈堂內(nèi)的尸體忽然破棺而出野瘦,到底是詐尸還是另有隱情,我是刑警寧澤飒泻,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布鞭光,位于F島的核電站,受9級特大地震影響泞遗,放射性物質(zhì)發(fā)生泄漏惰许。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一史辙、第九天 我趴在偏房一處隱蔽的房頂上張望汹买。 院中可真熱鬧佩伤,春花似錦、人聲如沸卦睹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽结序。三九已至障斋,卻和暖如春埋心,著一層夾襖步出監(jiān)牢的瞬間伴挚,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工异赫, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留返敬,地道東北人遂庄。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像劲赠,于是被迫代替她去往敵國和親涛目。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,435評論 2 359