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

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

這一系列教程的目的是幫助大家對區塊鏈開發技術有一個大體的藍圖,你能夠在這裏找到教程的第一部分html

在教程的第二部分咱們會:前端

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

以上這些最終會造出咱們本身的加密貨幣(相似那樣吧)!java

不用擔憂這篇文章只是空談,怎麼說都比上一篇教程有更多幹貨!文長不看的話,能夠直接看源碼 Githubandroid


上一篇教程咱們說到,咱們有了一個基本的可驗證區塊鏈。可是如今咱們的區塊鏈只能存儲至關沒用的數據信息。今天咱們要將這些無用數據替換爲交易數據(咱們的區塊將可以存儲屢次交易),這樣咱們即可以創造一個十分簡單的加密貨幣。咱們把這種新幣叫作:「菜鳥幣」(英文原文:noobcoin)。ios

1.準備一個錢包

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

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

讓咱們建立一個 Wallet 類來持有咱們的公鑰和私鑰信息:算法

package noobchain;
import java.security.*;

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

請確保導入了 java.security.* 包 !數據庫

這些公鑰和私鑰是用來幹嗎的?後端

對於咱們的「菜鳥幣」來講,公鑰就是做爲咱們的地址。你能夠與他人分享公鑰以便能收到付款。而咱們的私鑰是用來對咱們的交易進行簽名,這樣除了私鑰的主人就沒人能夠偷花咱們的菜鳥幣。 用戶必須保管好本身的私鑰! 咱們在交易的過程當中也會發送出咱們的公鑰,公鑰也能夠用來驗證咱們的簽名是否合法和數據是否被篡改。

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

咱們以一對 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);
}
複製代碼

實際上,你可能想對更多信息加入簽名,像輸出/輸入或是時間戳(但如今咱們只想對最基本的信息進行簽名)。

簽名能夠由礦工進行驗證,就像一個新交易被驗證後添加到一個區塊中。

當檢查區塊鏈的合法性的時候,咱們一樣也能夠檢查簽名。

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 的公鑰對其簽名。而後就是但願一切能正常工做吧。

你的輸出應該像這樣子:

簽名按照預想應該被驗證爲 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);
	}
	
}
複製代碼

交易輸出會顯示最終發送給各接收方的金額。這些輸出,在新交易中會被看成輸入,做爲你有資金能夠發送出去的憑據。

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裏面移除了,說明一個交易輸出做爲一個輸入只能使用一次。所以,輸入的總數值必須都花出去,這樣發送方纔有剩餘「零錢」可拿回來。

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

最後更新咱們的錢包:

  • 收集咱們的餘額(經過循環 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。

哈哈!每一個想要的咱們都造出來了,如今咱們的區塊鏈上已經能進行交易了!

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);
	}
}
複製代碼

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

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

如今錢包已經能夠在你的區塊鏈上安全地發送資金,固然前提是得有錢。這意味着你已經擁有了本身的本地化加密貨幣了。

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

你已經成功造出你本身的加密貨幣(部分完成)。 你如今的區塊鏈能夠:

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

你能夠在 Github 上面下載這個項目。

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

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

咱們接下來會講 P2P 網絡的部分,共識算法區塊存儲和數據庫。(很快就會發布)

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


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

相關文章
相關標籤/搜索