本文我们将会做以下事情:
1、创建一个钱包(wallet)。
2、使用我们的前面创建的区块链发送一笔签名的交易出去。
3、还有其他更叼的事情等等。
听起来是不是就让人心动。
最后的结果就是我们有了自己的加密货币,是的,crypto coin。
前面我们已经构建了一个基本的区块链。但目前这个区块链的区块中的message是一些没有什么实际用途和意义的数据。本文我们就尝试让区块中能够存储一些交易数据(一个区块中可以存储多笔交易数据),这样我们就可以创建自己的加密货币(当然还是一个简单的),这里给我们的货币起个名字叫:“NoobCoin”。
1、创建钱包
在加密货币(crypto-currencies)中,货币所有权被作为交易(transaction)在区块链上进行转移,参与者有一个收发资金的地址。
好,现在让我们创建一个钱包(Wallet)来持有pubkey和private key:
import java.security.*;
public class Wallet {
public PrivateKey privateKey;
public PublicKey publicKey;
}
公钥和私钥的用途是什么?
对于我们的“noobcoin”,公钥(public key)就是我们的一个地址,address。
可以与其他人共享这个公钥,来接受支付。我们的私钥是用来签署(sign)我们的交易(transaction),所以除了私钥(private key)的所有者,没有人可以花我们的钱。用户将不得不对自己的私钥保密!我们还将公钥与交易(transaction)一起发送,它可以用来验证我们的签名是否有效,并且数据没有被篡改。
私钥用于对我们不希望被篡改的数据进行签名。公钥用于验证签名。
我们在一个KeyPair中生成我们的私钥和公钥。这里使用
Elliptic-curve加密来生成KeyPair。现在我们就去Wallet类中添加一个方法generateKeyPair(),然后在构造函数中调用它:
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、一个加密签名。证明地址的所有者是发起该交易的人,并且数据没有被更改。(例如:防止第三方更改发送的金额)
让我们创建交易类吧:
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方法:
//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()方法:
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方法的内容如下:
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类:
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类:
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的集合:
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:
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())
并可以生成交易。
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():
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类加强一下:
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类更新,包含如下内容:
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
本文分享自 ImportSource 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!