Add a unit test for the case where the block chain overrides a currently pending transaction. This is an edge case that should only occur when the user executes a Finney attack against somebody else.

Fix the bug that Miron pointed out. Resolves comments on r76.
This commit is contained in:
Mike Hearn
2011-05-23 12:13:25 +00:00
parent 846412aa00
commit cb5025b987
7 changed files with 65 additions and 9 deletions

View File

@@ -456,7 +456,7 @@ public class Block extends Message {
// counter in the scriptSig so every transaction has a different hash. The output is also different.
// Real coinbase transactions use <pubkey> OP_CHECKSIG rather than a send to an address though there's
// nothing in the system that enforces that and both are just as valid.
coinbase.inputs.add(new TransactionInput(params, new byte[] { (byte) coinbaseCounter++ } ));
coinbase.inputs.add(new TransactionInput(params, coinbase, new byte[] { (byte) coinbaseCounter++ } ));
coinbase.outputs.add(new TransactionOutput(params, coinbase, Utils.toNanoCoins(50, 0), to));
transactions.add(coinbase);
}

View File

@@ -76,7 +76,7 @@ public class NetworkParameters implements Serializable {
// "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks"
byte[] bytes = Hex.decode
("04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73");
t.inputs.add(new TransactionInput(n, bytes));
t.inputs.add(new TransactionInput(n, t, bytes));
ByteArrayOutputStream scriptPubKeyBytes = new ByteArrayOutputStream();
Script.writeBytes(scriptPubKeyBytes, Hex.decode
("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f"));

View File

@@ -219,7 +219,7 @@ public class Transaction extends Message implements Serializable {
long numInputs = readVarInt();
inputs = new ArrayList<TransactionInput>((int)numInputs);
for (long i = 0; i < numInputs; i++) {
TransactionInput input = new TransactionInput(params, bytes, cursor);
TransactionInput input = new TransactionInput(params, this, bytes, cursor);
inputs.add(input);
cursor += input.getMessageSize();
}
@@ -300,7 +300,7 @@ public class Transaction extends Message implements Serializable {
* accepted by the network.
*/
public void addInput(TransactionOutput from) {
inputs.add(new TransactionInput(params, from));
inputs.add(new TransactionInput(params, this, from));
}
/**

View File

@@ -44,27 +44,33 @@ public class TransactionInput extends Message implements Serializable {
// The Script object obtained from parsing scriptBytes. Only filled in on demand and if the transaction is not
// coinbase.
transient private Script scriptSig;
// A pointer to the transaction that owns this input.
Transaction parentTransaction;
/** Used only in creation of the genesis block. */
TransactionInput(NetworkParameters params, byte[] scriptBytes) {
TransactionInput(NetworkParameters params, Transaction parentTransaction, byte[] scriptBytes) {
super(params);
this.scriptBytes = scriptBytes;
this.outpoint = new TransactionOutPoint(params, -1, null);
this.sequence = 0xFFFFFFFFL;
this.parentTransaction = parentTransaction;
}
/** Creates an UNSIGNED input that links to the given output */
TransactionInput(NetworkParameters params, TransactionOutput output) {
TransactionInput(NetworkParameters params, Transaction parentTransaction, TransactionOutput output) {
super(params);
long outputIndex = output.getIndex();
outpoint = new TransactionOutPoint(params, outputIndex, output.parentTransaction);
scriptBytes = EMPTY_ARRAY;
this.sequence = 0xFFFFFFFFL;
sequence = 0xFFFFFFFFL;
this.parentTransaction = parentTransaction;
}
/** Deserializes an input message. This is usually part of a transaction message. */
public TransactionInput(NetworkParameters params, byte[] payload, int offset) throws ProtocolException {
public TransactionInput(NetworkParameters params, Transaction parentTransaction,
byte[] payload, int offset) throws ProtocolException {
super(params, payload, offset);
this.parentTransaction = parentTransaction;
}
void parse() throws ProtocolException {

View File

@@ -156,4 +156,9 @@ public class TransactionOutput extends Message implements Serializable {
throw new RuntimeException(e);
}
}
/** Returns the connected input. */
TransactionInput getSpentBy() {
return spentBy;
}
}

View File

@@ -316,7 +316,13 @@ public class Wallet implements Serializable {
} else if (result == TransactionInput.ConnectionResult.ALREADY_SPENT) {
// Double spend! This must have overridden a pending tx, or the block is bad (contains transactions
// that illegally double spend: should never occur if we are connected to an honest node).
Transaction connected = input.outpoint.fromTx;
//
// Work backwards like so:
//
// A -> spent by B [pending]
// \-> spent by C [chain]
Transaction doubleSpent = input.outpoint.fromTx; // == A
Transaction connected = doubleSpent.outputs.get((int)input.outpoint.index).getSpentBy().parentTransaction;
if (pending.containsKey(connected.getHash())) {
log.info("Saw double spend from chain override pending tx {}", connected.getHashAsString());
log.info(" <-pending ->dead");