Created
December 31, 2016 14:35
-
-
Save anhldbk/37140b3281940d1fed744d8e4fbcf1c3 to your computer and use it in GitHub Desktop.
Scrooge Coin
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// https://www.coursera.org/learn/cryptocurrency/programming/KOo3V/scrooge-coin | |
import java.security.PublicKey; | |
import java.util.ArrayList; | |
import java.util.HashSet; | |
import java.util.Set; | |
import java.util.concurrent.ConcurrentHashMap; | |
public class TxHandler { | |
private UTXOPool pool; | |
private TxValidator validator = new TxValidator(); | |
/** | |
* Creates a public ledger whose current UTXOPool (collection of unspent transaction outputs) is | |
* {@code utxoPool}. This should make a copy of utxoPool by using the UTXOPool(UTXOPool uPool) | |
* constructor. | |
*/ | |
public TxHandler(UTXOPool utxoPool) { | |
this.pool = new UTXOPool(utxoPool); | |
} | |
private class TxValidator { | |
private double inSum = 0, outSum = 0; | |
public boolean validate(UTXOPool pool, Transaction tx) { | |
return notNull(tx) && inputValid(pool, tx) && outputValid(tx); | |
} | |
private boolean notNull(Transaction tx) { | |
return tx != null; | |
} | |
private boolean inputValid(UTXOPool pool, Transaction tx) { | |
inSum = 0; | |
Set<UTXO> usedTxs = new HashSet<>(); | |
// check for rule #1 | |
for (int i = 0; i < tx.numInputs(); i++) { | |
Transaction.Input input = tx.getInput(i); | |
if (input == null) { | |
return false; | |
} | |
UTXO utxo = new UTXO(input.prevTxHash, input.outputIndex); | |
if (!pool.contains(utxo) || usedTxs.contains(utxo)) { | |
return false; | |
} | |
Transaction.Output prevTxOut = pool.getTxOutput(utxo); | |
// verify rule #2 | |
PublicKey pubKey = prevTxOut.address; | |
byte[] message = tx.getRawDataToSign(i); | |
byte[] signature = input.signature; | |
if (!Crypto.verifySignature(pubKey, message, signature)) { | |
return false; | |
} | |
// rule #3 | |
// if the signatures are valid, then remove the associated output from the pool | |
// by removing the item, we ensure that no UTXO is claimed multiple times | |
//this.pool.removeUTXO(utxo); | |
usedTxs.add(utxo); | |
inSum += prevTxOut.value; | |
} | |
return true; | |
} | |
private boolean outputValid(Transaction tx) { | |
// check for rule #4 | |
outSum = 0; | |
for (int i = 0; i < tx.numOutputs(); i++) { | |
Transaction.Output out = tx.getOutput(i); | |
if (out.value < 0) { | |
return false; | |
} | |
outSum += out.value; | |
} | |
// check for rule #5 | |
return inSum >= outSum; | |
} | |
} | |
/** | |
* @return true if: | |
* (1) all outputs claimed by {@code tx} are in the current UTXO pool, | |
* (2) the signatures on each input of {@code tx} are valid, | |
* (3) no UTXO is claimed multiple times by {@code tx}, | |
* (4) all of {@code tx}s output values are non-negative, and | |
* (5) the sum of {@code tx}s input values is greater than or equal to the sum of its output | |
* values; and false otherwise. | |
*/ | |
public boolean isValidTx(Transaction tx) { | |
return validator.validate(this.pool, tx); | |
} | |
private ConcurrentHashMap<byte[], Transaction> getTxMap(Transaction[] possibleTxs) { | |
ConcurrentHashMap<byte[], Transaction> txs = new ConcurrentHashMap<>(); | |
for (Transaction tx : possibleTxs) { | |
if (tx == null) { | |
continue; | |
} | |
// buffer the hashes so we don't have to re-calculate them later on | |
tx.finalize(); | |
txs.put(tx.getHash(), tx); | |
} | |
return txs; | |
} | |
/** | |
* Handles each epoch by receiving an unordered array of proposed transactions, checking each | |
* transaction for correctness, returning a mutually valid array of accepted transactions, and | |
* updating the current UTXO pool as appropriate. | |
*/ | |
public Transaction[] handleTxs(Transaction[] possibleTxs) { | |
if (possibleTxs == null) { | |
return new Transaction[0]; | |
} | |
ConcurrentHashMap<byte[], Transaction> txs = getTxMap(possibleTxs); | |
ArrayList<Transaction> valid = new ArrayList<>(); | |
int txCount; | |
do { | |
txCount = txs.size(); | |
for (Transaction tx : txs.values()) { | |
if (!isValidTx(tx)) { | |
continue; | |
} | |
valid.add(tx); | |
this.applyTx(tx); | |
txs.remove(tx.getHash()); | |
} | |
if (txCount == txs.size() || txCount == 0) { // still the same | |
break; // nothing to check | |
} | |
} while (true); | |
return valid.toArray(new Transaction[valid.size()]); | |
} | |
private void applyTx(Transaction tx) { | |
if (tx == null) { | |
return; | |
} | |
for (Transaction.Input input : tx.getInputs()) { | |
UTXO utxo = new UTXO(input.prevTxHash, input.outputIndex); | |
this.pool.removeUTXO(utxo); | |
} | |
byte[] txHash = tx.getHash(); | |
int index = 0; | |
for (Transaction.Output output : tx.getOutputs()) { | |
UTXO utxo = new UTXO(txHash, index); | |
index += 1; | |
this.pool.addUTXO(utxo, output); | |
} | |
} | |
} |
line 44: || usedTxs.contains(utxo)
seems to be redundant.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
private boolean inputValid(UTXOPool pool, Transaction tx) {
inSum = 0;
//Set usedTxs = new HashSet<>();
// check for rule #1
for (int i = 0; i < tx.numInputs(); i++) {
Transaction.Input input = tx.getInput(i);
if (input == null) {
return false;
}
UTXO utxo = new UTXO(input.prevTxHash, input.outputIndex);
Running 15 tests
Test 1: test isValidTx() with valid transactions
==> FAILED
Test 2: test isValidTx() with transactions containing signatures of incorrect data
==> passed
Test 3: test isValidTx() with transactions containing signatures using incorrect private keys
==> FAILED
Test 4: test isValidTx() with transactions whose total output value exceeds total input value
==> FAILED
Test 5: test isValidTx() with transactions that claim outputs not in the current utxoPool
==> FAILED
Test 6: test isValidTx() with transactions that claim the same UTXO multiple times
==> FAILED
Test 7: test isValidTx() with transactions that contain a negative output value
==> FAILED
Test 8: test handleTransactions() with simple and valid transactions
Total Transactions = 2
Number of transactions returned valid by student = 2
Total Transactions = 50
Number of transactions returned valid by student = 50
Total Transactions = 100
Number of transactions returned valid by student = 100
==> passed
Test 9: test handleTransactions() with simple but some invalid transactions because of invalid signatures
Total Transactions = 2
Number of transactions returned valid by student = 0
Total Transactions = 50
Number of transactions returned valid by student = 2
Total Transactions = 100
Number of transactions returned valid by student = 1
==> passed
Test 10: test handleTransactions() with simple but some invalid transactions because of inputSum < outputSum
Total Transactions = 2
Number of transactions returned valid by student = 2
Total Transactions = 50
Number of transactions returned valid by student = 25
Total Transactions = 100
Number of transactions returned valid by student = 42
==> passed
Test 11: test handleTransactions() with simple and valid transactions with some double spends
Total Transactions = 2
Number of transactions returned valid by student = 1
Total Transactions = 50
Number of transactions returned valid by student = 17
Returned set is not a maximal set of transactions
Total Transactions = 100
Number of transactions returned valid by student = 33
Returned set is not a maximal set of transactions
==> FAILED
Test 12: test handleTransactions() with valid but some transactions are simple, some depend on other transactions
Total Transactions = 2
Number of transactions returned valid by student = 2
Total Transactions = 50
Number of transactions returned valid by student = 23
Returned set is not a maximal set of transactions
Total Transactions = 100
Number of transactions returned valid by student = 74
Returned set is not a maximal set of transactions
==> FAILED
Test 13: test handleTransactions() with valid and simple but some transactions take inputs from non-exisiting utxo's
Total Transactions = 2
Number of transactions returned valid by student = 1
Total Transactions = 50
Number of transactions returned valid by student = 12
Total Transactions = 100
Number of transactions returned valid by student = 57
==> passed
Test 14: test handleTransactions() with complex Transactions
Total Transactions = 2
Number of transactions returned valid by student = 2
Total Transactions = 50
Number of transactions returned valid by student = 5
Returned set is not a maximal set of transactions
Total Transactions = 100
Number of transactions returned valid by student = 22
Returned set is not a maximal set of transactions
==> FAILED
Test 15: test handleTransactions() with simple, valid transactions being called again to check for changes made in the pool
Total Transactions = 2
Number of transactions returned valid by student = 2
Total Transactions = 50
Number of transactions returned valid by student = 48
Total Transactions = 100
Number of transactions returned valid by student = 53
==> passed
Total:6/15 tests passed!
Do you know why it failed?