美文网首页
Part2 :使用 Java 创建你的第一个区块链[译]

Part2 :使用 Java 创建你的第一个区块链[译]

作者: 风雪围城 | 来源:发表于2019-07-24 23:17 被阅读0次

     本篇是该系列的第二篇,你可以在这里找到第一篇。原文链接在此
     在第本篇,我们将

    • 创建一个简单的钱包
    • 在我们的区块链上签发一个交易。

     上面的这些过程,其实就产生了我们自己的加密货币。
     在上一篇文章中,我们有了一个可验证的、基本的区块链。但是,我们的链中仅仅存储了一些无用的信息。今天,我们将把这些无用的信息替换为交易数据。这允许我们可以创建一个简单的加密货币,我们称之为 “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 上下载到这个项目。

    参考

    哈希树

    相关文章

      网友评论

          本文标题:Part2 :使用 Java 创建你的第一个区块链[译]

          本文链接:https://www.haomeiwen.com/subject/fzyqrctx.html