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

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

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

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

 本文中,还将使用 Bouncy CastleGSON 库。

1. 准备钱包

 在加密货币中,货币的所有权在区块链上以交易的形式流转,交易者拥有一个可以地址可以转入转出。因此,对于一个钱包而言,至少需要能够存储这些地址。更多的,钱包也可以作为一个软件用以在区块链上产生新的交易。

 现在,让我们俩创建一个持有我们公钥和私钥的 Wallet 类:

1
2
3
4
5
6
7
8
9
package noobchain;

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

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

 公钥和私钥有什么用?
 对于我们的加密货币 noobcoin 来说,公钥扮演者我们的钱包地址的角色,我们可以随意分享自己的公钥。私钥,是用来 签署(sign) 交易的,没有人能花费我们的 noobcoin,除非,他拥有我们的私钥。所以,用户的私钥一定要被保管好。公钥部分通常会和交易一起发送的,用以验证交易前面是否合法,交易内容是否被篡改。

 公钥和私钥通过 KeyPair 生成,接下来,我们将使用 椭圆曲线加密算法 来生成密钥对。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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);
}
}
}

 现在,我们的钱包类差不多了,接下来,让我们来看看交易。

2. 交易和签名

 每一个交易,都要携带一定的数据:

  • 发送者资金的公钥(译注:相当发送者的地址)
  • 接受者资金的公钥(译注:相当于接受者的地址)
  • 交易资金的数目
  • inputs,证明发送者有足够的币发送
  • outputs,接收地址收到的总金额
  • 加密签名,保证发送者签署的交易不会被恶意篡改

 现在,我们来创建一个 Transaction 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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
);
}
}

&emsp;此时,inputs 和 outputs 都是空的,后面我们会使用到它们。这个 Transaction 类还将包含生成、验证签名、验证交易等相关方法。但是,签名的目的是是什么?它们是如何工作的?

签名的目的和工作原理

&emsp;签名在区块链中执行了两个非常重要的任务:首先,允许拥有者们消费他们的币,它会放置交易信息被篡改。
&emsp;私钥被用来签署数据,公钥被用来验证数据的完整性。

举个栗子:Bob 想向 Sally 转 2 个 NoobCoin,因此,钱包软件会生成这个交易,然后将这个交易提交给矿工,以将该数据包含连接到区块链中。如果矿工企图将这 2 个币的接收人改为 John。然而,幸运的是 Bob 使用私钥签署了这个交易数据,并且允许任何人使用他的公钥验证该交易的完整性、合法性。

&emsp;从上面的代码中,我们可以看到,所谓签名实际上一个 byte 数组,因此,下面让我们来生成它。首先,这里需要一个 StringUtil 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//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());
}

&emsp;现在,在 Transaction 类的 generateSignature()verifySignature() 方法中运用该签名方法。

1
2
3
4
5
6
7
8
9
10
11

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

&emsp;当该交易被矿工添加到区块链上的一个新块的时候,这个签名将会被验证。

3.测试钱包和签名

&emsp;现在,我们基本上已经完成了一半的工作。在 NoobChain 这个类中,添加一些新的变量,并替换到 main 方法中的一些方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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());
}

&emsp;现在,我们创建了两个钱包,walletA 和 walletB,并且打印了 walletA 的私钥和公钥。你能看到的大概是这样子的

&emsp;回过头来看,现在我们需要创建/验证 outputs 和 inputs,并且将他们存储到区块链上的交易上。

4. Inputs 和 Outputs

1. 加密货币的归属

&emsp;如果你要拥有一个 bitcoin,首先你要收到一个 bitcoin。这个过程,并非是在总账单上将你的 bitcoin 加一 ,将发送者的 bitcoin 减一的过程。事实上,发送者肯定是前面也接收到的一个 bitcoin,然后才能将其发送到你的地址上。
&emsp;钱包的余额,是所有跟你地址(公钥)相关的未花费出去的交易输出的总和。(译注:后面将会看到,实际上这个链会维护一个由 publicKey 做 key,TransactionOutput 做 value 的 HashMap,这个 map 是所有交易输出的记录,通过 publicKey 可以查找到关于其拥有者的 bitcoin 数量)
&emsp;接下来,我们遵从 bitcoin 惯例,将未花费出去的交易输出命名为:UTXO.
&emsp;现在,我们来创建 TransactionInput 类:

1
2
3
4
5
6
7
8
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;
}
}

&emsp;然后,创建 TransactionOutputs 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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);
}
}

&emsp;通过 Transaction outputs 可以获取到交易双方通过交易获取到的各自 bitcoin 的总数。因此,它也可以作为新交易的 inputs,以证明你有足够多的币用以交易。

2. 处理交易

&emsp;链上的区块,可能包含了很多交易,区块链可能会很长很长很长很长,也因此,在处理新块的时候,可能会花费很长很长的时间,因为我们需要查找并检查它的输入。为了绕过这一点,我们将使用一个额外的集合,以保存未花费的交易。在 NoobChain 中,我们通过 UTXO 表示:

1
2
3
4
5
6
7
8
9
10
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) {
......

&emsp;ok,现在是时候来揭晓事情的真相了。
&emsp;让我们把所有事情集中起来,在 Transactoin 处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
//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;
}

&emsp;使用这个方法,我们执行一些检查,确保交易的合法性,接着,收集输入,并产生输出。
&emsp;很重要的一点,在结尾处,我们从 UTXO 中删除了 Inputs。这意味着 transaction output 仅有一次机会作为输入…所有输入值,都将在本次交易中被使用,如果没有使用完,剩余的部分会返回到自身中。

&emsp;最后,让我们来更新下钱包:

  • 计算余额(通过对 UTXO 循环,计算属于“我”的余额,判断是否有足够的余额进行交易 )。
  • 产生新的 transaction(交易)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

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. 将交易添加到区块中

&emsp;现在,我们有了一个可以工作的交易系统了,接下来,需要将其实现到区块链上。现在,我们可以将以前那些无用的数据替换为 ArrayList of transactions(交易列表),同时,使用根哈希的方式,计算区块的哈希值。
&emsp;下面,在 StringUtil 中增加一个获取根哈希(译注:可以参考 维基百科)的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//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;
}

&emsp;再接下来,进行 Block 中的修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
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;
}

}

&emsp;注意,我们更新了 Block 的构造器,不在传入一个字符串,并且添加了用于计算哈希值的 merkle root(根哈希)属性。
&emsp;addTransaction 方法将返回一个 boolean,以表示交易是否成功。

7. 华丽的落幕

&emsp;最后,我们还需要测试从钱包中消费 noobcoin ,更新区块链合法性检测。但是,首先,我们还是需要引入这些新的 coins。其实,还是有很多方式引入新的 coins,比如在比特币区块链上,新币可以作为对矿工挖到矿的奖励。在本文中,我们将采用直接在创世区块中释放所有币的方式。
&emsp;我们来更新下 NoobChain 类:

  • 创世区块将向 walletA 发放 100 个 Noobcoins.
  • 当交易发生后,检测区块链的合法性
  • 通过一些交易,测试是否一切工作 ok
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
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);
}
}

&emsp;你可以在 Github 上下载到这个项目。

参考

哈希树

© 2024 YueGS