[譯]用 Java 創造你的第一個區塊鏈之第二部分 —— 交易

IllllllIIl發表於2018-02-26

用 Java 創造你的第一個區塊鏈之第二部分 —— 交易

這一系列教程的目的是幫助你們對區塊鏈開發技術有一個大致的藍圖,你可以在這裡找到教程的第一部分

在教程的第二部分我們會:

  • 生成一個簡單的錢包。
  • 使用我們的區塊鏈傳送帶有簽名的交易。
  • 自我陶醉。

以上這些最終會造出我們自己的加密貨幣(類似那樣吧)!

[譯]用 Java 創造你的第一個區塊鏈之第二部分 —— 交易

不用擔心這篇文章只是空談,怎麼說都比上一篇教程有更多幹貨!文長不看的話,可以直接看原始碼 Github


上一篇教程我們說到,我們有了一個基本的可驗證區塊鏈。但是現在我們的區塊鏈只能儲存相當沒用的資料資訊。今天我們要將這些無用資料替換為交易資料(我們的區塊將能夠儲存多次交易),這樣我們便可以創造一個十分簡單的加密貨幣。我們把這種新幣叫做:“菜鳥幣”(英文原文:noobcoin)。

1.準備一個錢包

在加密貨幣中,貨幣所有權以交易的方式在區塊鏈中轉移,交易參與者持有資金的傳送方和接收方的地址。如果只是錢包的基本形式,錢包可以只儲存這些地址資訊。然而,大多數錢包在軟體層面上也能夠生成新的交易。

[譯]用 Java 創造你的第一個區塊鏈之第二部分 —— 交易

不用擔心關於交易部分的知識,我們很快會解釋這些。

讓我們建立一個 Wallet 類來持有我們的公鑰和私鑰資訊:

package noobchain;
import java.security.*;

public class Wallet {
	public PrivateKey privateKey;
	public PublicKey publicKey;
}
複製程式碼

請確保匯入了 java.security.* 包 !

這些公鑰和私鑰是用來幹嘛的?

對於我們的“菜鳥幣”來說,公鑰就是作為我們的地址。你可以與他人分享公鑰以便能收到付款。而我們的私鑰是用來對我們的交易進行簽名,這樣除了私鑰的主人就沒人可以偷花我們的菜鳥幣。 使用者必須保管好自己的私鑰! 我們在交易的過程中也會傳送出我們的公鑰,公鑰也可以用來驗證我們的簽名是否合法和資料是否被篡改。

[譯]用 Java 創造你的第一個區塊鏈之第二部分 —— 交易

私鑰是用來對我們的資料進行簽名,防止被篡改。公鑰是用來驗證這個簽名。

我們以一對 KeyPair 的形式生成私鑰和公鑰。我們會採用橢圓曲線密碼學去生成我們的 KeyPairs。 我們在 Wallet 類中新增一個 generateKeyPair() 方法,並且在構造方法中呼叫它:

package noobchain;
import java.security.*;

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");
			// 初始化 KeyGenerator 並且生成一對 KeyPair
			keyGen.initialize(ecSpec, random);   //256 位元組大小是可接受的安全等級
	        	KeyPair keyPair = keyGen.generateKeyPair();
	        	// 從 KeyPair中獲取公鑰和私鑰
	        	privateKey = keyPair.getPrivate();
	        	publicKey = keyPair.getPublic();
		}catch(Exception e) {
			throw new RuntimeException(e);
		}
	}
	
}
複製程式碼

關於這個方法你所需要了解的就是它使用了 Java.security.KeyPairGenerator 去生成一個應用橢圓曲線密碼學的 KeyPair。這個方法生成公鑰和私鑰並賦值到對應的公鑰私鑰物件。它很實用。

既然我們對 Wallet 類有了大致的認識,接下來看一下交易的部分。

2. 交易和簽名

每一個交易都包含一定大小的資料:

  • 資金髮送方的公鑰(地址)。
  • 資金接受方的公鑰(地址)。
  • 要轉賬的資金數額。
  • 輸入,是上一次交易的引用,證明傳送方有資金可以傳送出去。
  • 輸出,是在交易中接收方收到的金額。 (在新交易中這些輸出也會被當作是輸入)
  • 一個加密的簽名,證明地址的所有者是傳送這個交易的人並且傳送的資料沒有被篡改。(例如,阻止第三方更改傳送出去的數額)

讓我們寫一個新的 Transaction 類:

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

public class Transaction {
	
	public String transactionId; // 這個也是交易的雜湊值
	public PublicKey sender; // 傳送方地址/公鑰
	public PublicKey reciepient; // 接受方地址/公鑰
	public float value;
	public byte[] signature; // 用來防止他人盜用我們錢包裡的資金
	
	public ArrayList<TransactionInput> inputs = new ArrayList<TransactionInput>();
	public ArrayList<TransactionOutput> outputs = new ArrayList<TransactionOutput>();
	
	private static int sequence = 0; // 對已生成交易個數的粗略計算 
	
	// 構造方法: 
	public Transaction(PublicKey from, PublicKey to, float value,  ArrayList<TransactionInput> inputs) {
		this.sender = from;
		this.reciepient = to;
		this.value = value;
		this.inputs = inputs;
	}
	
	// 用來計算交易的雜湊值(可作為交易的 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
				);
	}
}
複製程式碼

我們應該也寫一個空的 TransactionInput 類和 TransactionOutput 類,我們之後會把它們補上。

我們的交易類也包含了生成/驗證簽名和驗證交易的相關方法。

但等一下。。。

這些簽名的目的和工作方式是什麼?

簽名在我們區塊鏈中起到的兩個很重要的工作就是: 第一,它們允許所有者去花他們的錢,第二,防止他人在新的一個區塊被挖出來之前(進入到整個區塊鏈),篡改他們已提交的交易。

私鑰用來對資料進行簽名,公鑰用來驗證它的合法性。

**例如:**Bob 想給 Sally 兩個菜鳥幣,所以他們的錢包客戶端生成這個交易並且遞交給礦工,使其成為下一個區塊的一部分。有一個礦工嘗試把這兩個幣的接受人篡改為 John。然而,很幸運地是,Bob 已經用他的私鑰把交易資料簽名了,任何人使用 Bob 的公鑰就能驗證這個交易的資料是否被篡改了(其他人的公鑰無法校驗此交易)。

(從之前的程式碼中)我們可以看到我們的簽名會包含很多位元組的資訊,所以我們建立一個生成這些資訊的方法。首先我們在 StringUtil 類中寫幾個輔助方法:

//採用 ECDSA 簽名並返回結果(以位元組形式)
		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;
	}
	
	//驗證一個字串簽名
	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 方法就是接受任何一種私鑰,返回一個加密的字串。

現在我們在 Transaction 類中使用這些簽名相關的方法,新增 generateSignature()verifiySignature() 方法。

//對所有我們不想被篡改的資料進行簽名
public void generateSignature(PrivateKey privateKey) {
	String data = StringUtil.getStringFromKey(sender) + StringUtil.getStringFromKey(reciepient) + Float.toString(value)	;
	signature = StringUtil.applyECDSASig(privateKey,data);		
}
//驗證我們已簽名的資料
public boolean verifiySignature() {
	String data = StringUtil.getStringFromKey(sender) + StringUtil.getStringFromKey(reciepient) + Float.toString(value)	;
	return StringUtil.verifyECDSASig(sender, data, signature);
}
複製程式碼

實際上,你可能想對更多資訊加入簽名,像輸出/輸入或是時間戳(但現在我們只想對最基本的資訊進行簽名)。

簽名可以由礦工進行驗證,就像一個新交易被驗證後新增到一個區塊中。

[譯]用 Java 創造你的第一個區塊鏈之第二部分 —— 交易

當檢查區塊鏈的合法性的時候,我們同樣也可以檢查簽名。

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) {	
		//設定 Bouncey castle 作為 Security Provider
		Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); 
		//建立新的錢包 
		walletA = new Wallet();
		walletB = new Wallet();
		//測試公鑰和私鑰
		System.out.println("Private and public keys:");
		System.out.println(StringUtil.getStringFromKey(walletA.privateKey));
		System.out.println(StringUtil.getStringFromKey(walletA.publicKey));
		//生成從 WalletA 到 walletB 的測試交易 
		Transaction transaction = new Transaction(walletA.publicKey, walletB.publicKey, 5, null);
		transaction.signature = transaction.generateSignature(walletA.privateKey);
		//驗證簽名是否起作用並結合公鑰驗證
		System.out.println("Is signature verified");
		System.out.println(transaction.verifiySignature());
		
	}
複製程式碼

請務必記得把 boncey castle 新增為 security provider。

我們建立了兩個錢包,walletA 和 walletB,然後列印出 walletA 的私鑰和公鑰。生成了一個 Transaction 並使用 walletA 的公鑰對其簽名。然後就是希望一切能正常工作吧。

你的輸出應該像這樣子:

[譯]用 Java 創造你的第一個區塊鏈之第二部分 —— 交易
簽名按照預想應該被驗證為 true。

應該小小地表揚下自己了。現在我們只需建立/驗證輸出和輸入,然後把交易儲存在區塊鏈中。

4. 輸入和輸出 1:自己是怎麼持有加密貨幣的

如果你想擁有一個比特幣,那你要先收到一個比特幣。交易賬單不會真的把一個比特幣加給你,也不會從傳送方那裡減去一個比特幣。傳送方有標識證明他/她之前收到過一個比特幣,然後交易輸出就會生成,顯示一個比特幣已經傳送到你的地址(交易中的輸入來源於之前交易的輸出)。

你的錢包餘額是你所有的未花費的交易輸出。

在這點上我們會跟比特幣的叫法一樣,把未花費的交易輸出稱為:UTXO

我們再寫一個 TransactionInput 類:

public class TransactionInput {
	public String transactionOutputId; //把 TransactionOutputs 標識為對應的transactionId
	public TransactionOutput UTXO; //包括了所有未花費的交易輸出
	
	public TransactionInput(String transactionOutputId) {
		this.transactionOutputId = transactionOutputId;
	}
}
複製程式碼

這個類會被用作未花費的 TransactionOutputs 的引用。transactionOutputId 被用來查詢相關的 TransactionOutput,允許礦工檢查你的所有權。

還有 TransactionOutputs 類:

import java.security.PublicKey;

public class TransactionOutput {
	public String id;
	public PublicKey reciepient; //這些幣的新持有者
	public float value; //他們持有幣的總額
	public String parentTransactionId; //生成這個輸出的之前交易的 id
	
	//構造方法
	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);
	}
	
	//檢查幣是否屬於你
	public boolean isMine(PublicKey publicKey) {
		return (publicKey == reciepient);
	}
	
}
複製程式碼

交易輸出會顯示最終傳送給各接收方的金額。這些輸出,在新交易中會被當作輸入,作為你有資金可以傳送出去的憑據。

[譯]用 Java 創造你的第一個區塊鏈之第二部分 —— 交易

5. 輸入和輸出 2:處理交易

區塊可能收到很多交易並且區塊鏈長度可能會很長,這樣會花非常長時間去處理一個新的交易,因為需要去查詢和檢查它的輸入。為了處理這個問題,我們要再寫一個可用作輸出的未花費交易集合。在 NoobChain 類中,加入 UTXOs 集合:

public class NoobChain {
	
	public static ArrayList<Block> blockchain = new ArrayList<Block>();
	public static HashMap<String,TransactionOutputs> UTXOs = new HashMap<String,TransactionOutputs>(); //未花費交易的 list 
	public static int difficulty = 5;
	public static Wallet walletA;
	public static Wallet walletB;

	public static void main(String[] args) {
複製程式碼

HashMaps 通過 key 去找到 value,但你需要引入 java.util.HashMap。

好,接下來就是重點了。

把處理交易的方法 processTransaction 放到 Transaction 類裡面:

//如果新交易可以生成,返回 true	
public boolean processTransaction() {
		
		if(verifiySignature() == false) {
			System.out.println("#Transaction Signature failed to verify");
			return false;
		}
				
		//整合所有交易輸入(確保是未花費的)
		for(TransactionInput i : inputs) {
			i.UTXO = NoobChain.UTXOs.get(i.transactionOutputId);
		}

		//檢查交易是否合法
		if(getInputsValue() < NoobChain.minimumTransaction) {
			System.out.println("#Transaction Inputs to small: " + getInputsValue());
			return false;
		}
		
		//生成交易輸出
		float leftOver = getInputsValue() - value; //獲取剩餘的零錢
		transactionId = calulateHash();
		outputs.add(new TransactionOutput( this.reciepient, value,transactionId)); //send value to recipient
		outputs.add(new TransactionOutput( this.sender, leftOver,transactionId)); //把剩下的“零錢“發回給傳送方		
				
		//新增輸出到未花費的 list 中
		for(TransactionOutput o : outputs) {
			NoobChain.UTXOs.put(o.id , o);
		}
		
		//從 UTXO list裡面移除已花費的交易輸出
		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;
	}
	
//返回輸入(UTXOs) 值的總額
	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;
	}

//返回輸出總額
	public float getOutputsValue() {
		float total = 0;
		for(TransactionOutput o : outputs) {
			total += o.value;
		}
		return total;
	}
複製程式碼

同樣再新增一個 getInputsValue 方法。

通過這個方法進行一些檢查,去驗證交易合法性,然後整合輸入並生成輸出(看看程式碼裡的註釋會清楚點)。

重要的一點,在最後,我們把 Inputs 從 UTXO list裡面移除了,說明一個交易輸出作為一個輸入只能使用一次。因此,輸入的總數值必須都花出去,這樣傳送方才有剩餘“零錢”可拿回來。

[譯]用 Java 創造你的第一個區塊鏈之第二部分 —— 交易

紅色箭頭是輸出。注意綠色的輸入來自之前的輸出。

最後更新我們的錢包:

  • 收集我們的餘額(通過迴圈 UTXO list並檢查一個交易輸出是否是自己的錢幣)
  • 為我們生成交易
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>(); //只是這個錢包擁有的 UTXO 
	
	public Wallet() {...
		
	public void generateKeyPair() {...
	
  //返回餘額並儲存這個錢包的 UTXO 
	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;
	}
	//從這個錢包生成並返回一個新的交易
	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;
		}
    //生成輸入的 ArrayList
		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 root 進行處理(你很快就會讀到關於 merkle tree 的東西)。

在 StringUtils 新增一個方法去生成 merkleroot:

//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;
	}
複製程式碼

*我會很快用一個能返回真正 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>(); //我們的資料就是一個簡單的資訊
	public long timeStamp; //從1970/1/1到現在經過的毫秒時間
	public int nonce;
	
	//構造方法  
	public Block(String previousHash ) {
		this.previousHash = previousHash;
		this.timeStamp = new Date().getTime();
		
		this.hash = calculateHash(); //確保設定了其它值之後再計算雜湊值
	}
	
	//基於區塊內容計算新的雜湊值
	public String calculateHash() {
		String calculatedhash = StringUtil.applySha256( 
				previousHash +
				Long.toString(timeStamp) +
				Integer.toString(nonce) + 
				merkleRoot
				);
		return calculatedhash;
	}
	
	//雜湊目標達成的話,增加 nonce 值
	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);
	}
	
	//新增交易到區塊
	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 方法會新增交易而且只在交易成功新增時返回 true。

哈哈!每個想要的我們都造出來了,現在我們的區塊鏈上已經能進行交易了!

[譯]用 Java 創造你的第一個區塊鏈之第二部分 —— 交易

7. 厲害地總結下(一開始的時候只有菜鳥幣):

現在應該測試從錢包裡傳送出去菜鳥幣或通過錢包接收菜鳥幣,並更新區塊鏈的合法性檢查。但首先我們要找到如何把新挖的菜鳥幣整合到系統中的辦法,有很多途徑去生成新幣,拿比特幣的區塊鏈來說:礦工可以把一個交易變成自己的一部分,作為區塊被挖出來時的獎勵。現在的話,我們就只是在第一個區塊(創始區塊)放出一定數量的幣,滿足我們專案需要即可。像比特幣一樣,我們會硬編碼創始區塊,寫一個固定的值。

讓我們完整地更新 NoobChain 類:

  • 一個創始區塊,發了 100 個菜鳥幣給錢包 A。
  • 因為增加了交易部分,更新了區塊鏈的合法性檢查。
  • 一些測試類交易去驗證是否正常運作。
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) {	
		//新增我們的區塊到區塊鏈 ArrayList中
		Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); //設定 Bouncey castle 為 Security Provider
		
		//生成錢包
		walletA = new Wallet();
		walletB = new Wallet();		
		Wallet coinbase = new Wallet();
		
		//生成創始交易,內容是傳送100個菜鳥幣到 walletA
		genesisTransaction = new Transaction(coinbase.publicKey, walletA.publicKey, 100f, null);
		genesisTransaction.generateSignature(coinbase.privateKey);	 //手動對創始交易簽名
		genesisTransaction.transactionId = "0"; //手動設定交易 id
		genesisTransaction.outputs.add(new TransactionOutput(genesisTransaction.reciepient, genesisTransaction.value, genesisTransaction.transactionId)); //手動新增交易輸出
		UTXOs.put(genesisTransaction.outputs.get(0).id, genesisTransaction.outputs.get(0)); //在 UTXO list 裡面儲存第一個交易很重要
		
		System.out.println("Creating and Mining Genesis block... ");
		Block genesis = new Block("0");
		genesis.addTransaction(genesisTransaction);
		addBlock(genesis);
		
		//測試
		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>(); //對給定的區塊狀態,一個臨時的未花費交易輸出list
		tempUTXOs.put(genesisTransaction.outputs.get(0).id, genesisTransaction.outputs.get(0));
		
		//迴圈區塊鏈去檢查雜湊值
		for(int i=1; i < blockchain.size(); i++) {
			
			currentBlock = blockchain.get(i);
			previousBlock = blockchain.get(i-1);
			//比較當前區塊儲存的雜湊值和計算得出的雜湊值
			if(!currentBlock.hash.equals(currentBlock.calculateHash()) ){
				System.out.println("#Current Hashes not equal");
				return false;
			}
			//比較前一個區塊的雜湊值和當前區塊中儲存的上一個區塊雜湊值
			if(!previousBlock.hash.equals(currentBlock.previousHash) ) {
				System.out.println("#Previous Hashes not equal");
				return false;
			}
			//檢查雜湊值是否解出來了
			if(!currentBlock.hash.substring( 0, difficulty).equals(hashTarget)) {
				System.out.println("#This block hasn't been mined");
				return false;
			}
			
			//迴圈區塊鏈交易
			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);
	}
}
複製程式碼

這些是比較長的方法 。。。

我們的輸出應該是像這樣的:

[譯]用 Java 創造你的第一個區塊鏈之第二部分 —— 交易

現在錢包已經可以在你的區塊鏈上安全地傳送資金,當然前提是得有錢。這意味著你已經擁有了自己的本地化加密貨幣了。

你現在已經實現了你區塊鏈的交易部分!

[譯]用 Java 創造你的第一個區塊鏈之第二部分 —— 交易

你已經成功造出你自己的加密貨幣(部分完成)。 你現在的區塊鏈可以:

  • 允許使用者用 new Wallet() 的方式生成錢包。
  • 提供採用橢圓曲線加密方式對公鑰和私鑰進行加密的錢包。
  • 通過一個數字簽名演算法證明資金所有權,保護資金的傳輸過程。
  • 最後允許使用者通過 Block.addTransaction(walletA.sendFunds( walletB.publicKey, 20)) 在你的區塊鏈上發起交易。

你可以在 Github 上面下載這個專案。

[譯]用 Java 創造你的第一個區塊鏈之第二部分 —— 交易

你可以關注我,以便下一個教程或其它區塊鏈開發文章釋出的時候收到通知。很重視你們的任何反饋意見。謝謝。

用 Java 實現你的第一個區塊鏈。 第三部分:

我們接下來會講 P2P 網路的部分,共識演算法區塊儲存和資料庫。(很快就會發布)

聯絡我: kassCrypto@gmail.com 問題交流discord.gg/ZsyQqyk(我在 discord 上面的區塊鏈開發者俱樂部)


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章