Skip to content

Instantly share code, notes, and snippets.

@anhldbk
Created December 31, 2016 14:35
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save anhldbk/37140b3281940d1fed744d8e4fbcf1c3 to your computer and use it in GitHub Desktop.
Save anhldbk/37140b3281940d1fed744d8e4fbcf1c3 to your computer and use it in GitHub Desktop.
Scrooge Coin
// 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);
}
}
}
@luoguizhou
Copy link

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

            //if (!pool.contains(utxo) || usedTxs.contains(utxo)) 
           if (!pool.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;
    }

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?

@ramneekhanda
Copy link

ramneekhanda commented Oct 28, 2017

@DmytroSokhach
Copy link

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