前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >自己动手写区块链-发起一笔交易(Java版)

自己动手写区块链-发起一笔交易(Java版)

作者头像
ImportSource
发布2018-04-03 11:49:53
4.3K1
发布2018-04-03 11:49:53
举报
文章被收录于专栏:ImportSourceImportSource

本文我们将会做以下事情:

1、创建一个钱包(wallet)。

2、使用我们的前面创建的区块链发送一笔签名的交易出去。

3、还有其他更叼的事情等等。

听起来是不是就让人心动。

最后的结果就是我们有了自己的加密货币,是的,crypto coin。

前面我们已经构建了一个基本的区块链。但目前这个区块链的区块中的message是一些没有什么实际用途和意义的数据。本文我们就尝试让区块中能够存储一些交易数据(一个区块中可以存储多笔交易数据),这样我们就可以创建自己的加密货币(当然还是一个简单的),这里给我们的货币起个名字叫:“NoobCoin”。

1、创建钱包

在加密货币(crypto-currencies)中,货币所有权被作为交易(transaction)在区块链上进行转移,参与者有一个收发资金的地址。

好,现在让我们创建一个钱包(Wallet)来持有pubkey和private key:

代码语言:javascript
复制
import java.security.*;
代码语言:javascript
复制
public class Wallet {
   
   public PrivateKey privateKey;
   public PublicKey publicKey;
   
}

公钥和私钥的用途是什么?

对于我们的“noobcoin”,公钥(public key)就是我们的一个地址,address。

可以与其他人共享这个公钥,来接受支付。我们的私钥是用来签署(sign)我们的交易(transaction),所以除了私钥(private key)的所有者,没有人可以花我们的钱。用户将不得不对自己的私钥保密!我们还将公钥与交易(transaction)一起发送,它可以用来验证我们的签名是否有效,并且数据没有被篡改。

私钥用于对我们不希望被篡改的数据进行签名。公钥用于验证签名。

我们在一个KeyPair中生成我们的私钥和公钥。这里使用

Elliptic-curve加密来生成KeyPair。现在我们就去Wallet类中添加一个方法generateKeyPair(),然后在构造函数中调用它:

代码语言:javascript
复制
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 
           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);
      }
   }
}

这个方法就是负责生成公钥和私钥。具体就是通过Java.security.KeyPairGenerator来生成Elliptic Curve key对。然后把这个方法加入到Wallet的构造函数中。

现在我们已经有了一个大体的钱包类。接下来我们看看交易(transaction)类。

2. 交易和签名(Transactions & Signatures)

每笔交易将会携带如下数据:

1、资金发送方的公钥(地址)。

2、资金接收方的公钥(地址)。

3、要转移的资金金额。

4、输入(Inputs)。这个输入是对以前交易的引用,这些交易证明发件人拥有要发送的资金。

5、输出(Outputs),显示交易中收到的相关地址量。(这些输出作为新交易中的输入引用)

6、一个加密签名。证明地址的所有者是发起该交易的人,并且数据没有被更改。(例如:防止第三方更改发送的金额)

让我们创建交易类吧:

代码语言:javascript
复制
import java.security.*;
import java.util.ArrayList;

public class Transaction {
   
   public String transactionId; //Contains a hash of transaction*
   public PublicKey sender; //Senders address/public key.
   public PublicKey reciepient; //Recipients address/public key.
   public float value; //Contains the amount we wish to send to the recipient.
   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;
   }
   
   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
            );
   }
}

上面的TransactionInput和TransactionOutput类一会再新建。

我们的交易(Transaction)类还应该包含生成/验证签名和验证交易的相关方法。

注意这里,既有验证签名的方法,也有验证交易的方法。

但是,稍等...

先来说说签名的目的是什么?它们是如何工作的?

签名在我们的区块链上执行两个非常重要的任务:首先,它能只允许所有者使用其货币;其次,在新区块被挖掘之前,它能防止其他人篡改其提交的交易(在入口点)。

私钥用于对数据进行签名,公钥可用于验证其完整性。

例如:Bob想给Sally发送2个NoobCoin,然后他们的钱包软件生成了这个交易并将其提交给矿工,以便将其包含在下一个块中。一名矿工试图将2枚货币的接收人改为Josh。不过,幸运的是,Bob已经用他的私钥签署了交易数据,允许任何人使用Bob的公钥去验证交易数据是否被更改(因为没有其他任何人的公钥能够验证交易)。

可以(从前面的代码块中)看到我们的签名就是一堆字节,所以现在创建一个方法来生成签名。我们首先需要的是StringUtil类中的几个helper方法:

代码语言:javascript
复制
//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());
}

不要过分担心这些方法具体的逻辑。你只需要知道的是:applyECDSASig方法接收发送方的私钥和字符串输入,对其进行签名并返回字节数组。verifyECDSASig接受签名、公钥和字符串数据,如果签名是有效的,则返回true,否则false。getStringFromKey从任意key返回编码的字符串。

现在让我们在Transaction类中使用这些签名方法,分别创建generateSignature()和verifiySignature()方法:

代码语言:javascript
复制
public void generateSignature(PrivateKey privateKey) {
   String data = StringUtil.getStringFromKey(sender) + StringUtil.getStringFromKey(reciepient) + Float.toString(value)    ;
   signature = StringUtil.applyECDSASig(privateKey,data);
}

public boolean verifySignature() {
   String data = StringUtil.getStringFromKey(sender) + StringUtil.getStringFromKey(reciepient) + Float.toString(value)    ;
   return StringUtil.verifyECDSASig(sender, data, signature);
}

在现实中,你可能希望签署更多的信息,比如使用的输出(outputs)/输入(inputs)和/或时间戳(time-stamp)(现在我们只签署了最基本的)。

在将新的交易添加到块中时,矿工将对签名进行验证。

当我们检查区块链的合法性的时候,其实也可以检查签名。

3.测试钱包(Wallets)和签名(Signatures)

现在我们差不多完成了一半了,先来测试下已经完成的是不是可以正常工作。在NoobChain类中,让我们添加一些新变量并替换main方法的内容如下:

代码语言:javascript
复制
import java.security.Security;
import java.util.ArrayList;

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.verifySignature());

    }
}

可以发现我们使用了boncey castle来作为安全实现的提供者。

还创建了两个钱包,钱包A和钱包B,然后打印了钱包A的私钥和公钥。还新建一笔交易。然后使用钱包A的公钥对这笔交易进行了签名。

输出:

嗯,签名验证是true,符合期望。

现在是时候小开心一下了。现在我们只需要创建和校验输出(outputs)和输入(inputs)然后把交易存储到区块链中。

4. 输入(Inputs)与输出(Outputs)1:加密货币是如何拥有的…

如果你想拥有1个比特币,你必须收到1个比特币。总账不会真的给你添加一个比特币,从发送者那里减去一个比特币,发送者提到他/她以前收到一个比特币,然后创建一个交易输出,显示1比特币被发送到你的地址。(交易输入是对以前交易输出的引用。)

你的钱包余额是所有发送给你的未使用的交易输出的总和。

ps:这里略微有点绕,总之你就记住进账和出账这回事情。

从现在开始,我们将遵循比特币惯例并调用未使用的交易输出:UTXO。

好,让我们创建一个TransactionInput类:

代码语言:javascript
复制
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的值。transactionOutputId将用于查找相关的TransactionOutput,从而允许矿工检查你的所有权。

下面是TransactionOutput类:

代码语言:javascript
复制
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);
   }
   
}

交易输出将显示从交易发送到每一方的最终金额。当在新的交易中作为输入引用时,它们将作为你要发送的货币的证明,能够证明你有钱可发送。

5. 输入(Inputs)与输出(Outputs)2:处理交易……

链中的块可能接收到许多交易,而区块链可能非常非常长,处理新交易可能需要数亿年的时间,因为我们必须查找并检查它的输入。要解决这个问题,我们就需要存在一个额外的集合(collection)来保存所有未使用的可被作为输入(inputs)的交易。在下面的ImportChain类中,添加一个所有UTXO的集合:

代码语言:javascript
复制
public class ImportChain {

   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) {

现在我们把之前的那些实现放在一起来处理一笔交易吧。先在Transaction类中的添加一个方法processTransaction:

代码语言:javascript
复制
public boolean processTransaction() {

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

   //Gathers transaction inputs (Making sure they are unspent):
   for(TransactionInput i : inputs) {
      i.UTXO = ImportChain.UTXOs.get(i.transactionOutputId);
   }

   //Checks if transaction is valid:
   if(getInputsValue() < ImportChain.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) {
      ImportChain.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
      ImportChain.UTXOs.remove(i.UTXO.id);
   }

   return true;
}

还添加了getInputsValue方法。使用此方法,我们执行一些检查以确保交易是有效的,然后收集输入并生成输出。(要了解更多信息,请参阅代码中的注释行)。

重要的是,在最后,我们从UTXO的列表中删除input,这意味着交易输出只能作为一个输入使用一次…而且必须使用完整的输入值,因为发送方要将“更改”返回给自己。

红色箭头是输出。请注意,绿色输入是对以前输出的引用。

最后,让我们将钱包类更新为:

可以汇总得到的余额(通过循环遍历UTXO列表并检查事务输出是否为Mine())

并可以生成交易。

代码语言:javascript
复制
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>();
   
   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 
           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);
      }
   }
   
   public float getBalance() {
      float total = 0;   
        for (Map.Entry<String, TransactionOutput> item: ImportChain.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;
   }
   
   public Transaction sendFunds(PublicKey _recipient,float value ) {
      if(getBalance() < value) {
         System.out.println("#Not Enough funds to send transaction. Transaction Discarded.");
         return null;
      }
      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。

然而,在一个块中可能有1000个交易,太多的交易不能包括在散列计算中……

没事,别担心,我们可以使用交易的merkle根,就是下面的那个getMerkleRoot()方法。

现在在StringUtils中添加一个helper方法getMerkleRoot():

代码语言:javascript
复制
public static String getMerkleRoot(ArrayList<Transaction> transactions) {
   int count = transactions.size();

   List<String> previousTreeLayer = new ArrayList<String>();
   for(Transaction transaction : transactions) {
      previousTreeLayer.add(transaction.transactionId);
   }
   List<String> treeLayer = previousTreeLayer;

   while(count > 1) {
      treeLayer = new ArrayList<String>();
      for(int i=1; i < previousTreeLayer.size(); i+=2) {
         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类加强一下:

代码语言:javascript
复制
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构造函数,因为不再需要传入字符串数据(还记得上集中我们的Block构造函数传入了一个data的字符串,这里我们往块里添加的是交易,也就是transaction),并且在计算哈希方法中包含了merkle根。

并且新增了addTransaction方法来添加一笔交易,并且只有在交易被成功添加时才返回true。

ok,我们的区块链上交易所需的每个零部件都实现了。是时候运转一下了。

7. 大结局

现在我们开始测试吧。发送货币进出钱包,并更新我们的区块链有效性检查。

但首先我们需要一个方法来引入新的币。有许多方法可以创建新的币,比如,在比特币区块链上:矿工可以将交易持有在自己手里,作为对每个块被开采的奖励。

这里,我们将只发行(release)我们希望拥有的所有货币,在第一个块(起源块)。就像比特币一样,我们将对起源块进行硬编码。

现在把ImportChain类更新,包含如下内容:

  • 一个“创世纪”的块,它向钱包A发行100个新币。
  • 帐户交易中的“更新的链”的有效性检查。
  • 一些测试信息,让我们看到内部运行的细节信息。

代码语言:javascript
复制
import java.security.Security;
import java.util.ArrayList;
import java.util.HashMap;

//import java.util.Base64;
//import com.google.gson.GsonBuilder;

public class ImportChain {

   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.verifySignature()) {
               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);
   }
}

运行结果:

代码链接:https://github.com/importsource/blockchain-samples-transaction/tree/master

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-03-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 ImportSource 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
区块链
云链聚未来,协同无边界。腾讯云区块链作为中国领先的区块链服务平台和技术提供商,致力于构建技术、数据、价值、产业互联互通的区块链基础设施,引领区块链底层技术及行业应用创新,助力传统产业转型升级,推动实体经济与数字经济深度融合。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档