mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-07-30 03:21:23 +00:00
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:
@@ -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.
|
// 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
|
// 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.
|
// 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));
|
coinbase.outputs.add(new TransactionOutput(params, coinbase, Utils.toNanoCoins(50, 0), to));
|
||||||
transactions.add(coinbase);
|
transactions.add(coinbase);
|
||||||
}
|
}
|
||||||
|
@@ -76,7 +76,7 @@ public class NetworkParameters implements Serializable {
|
|||||||
// "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks"
|
// "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks"
|
||||||
byte[] bytes = Hex.decode
|
byte[] bytes = Hex.decode
|
||||||
("04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73");
|
("04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73");
|
||||||
t.inputs.add(new TransactionInput(n, bytes));
|
t.inputs.add(new TransactionInput(n, t, bytes));
|
||||||
ByteArrayOutputStream scriptPubKeyBytes = new ByteArrayOutputStream();
|
ByteArrayOutputStream scriptPubKeyBytes = new ByteArrayOutputStream();
|
||||||
Script.writeBytes(scriptPubKeyBytes, Hex.decode
|
Script.writeBytes(scriptPubKeyBytes, Hex.decode
|
||||||
("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f"));
|
("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f"));
|
||||||
|
@@ -219,7 +219,7 @@ public class Transaction extends Message implements Serializable {
|
|||||||
long numInputs = readVarInt();
|
long numInputs = readVarInt();
|
||||||
inputs = new ArrayList<TransactionInput>((int)numInputs);
|
inputs = new ArrayList<TransactionInput>((int)numInputs);
|
||||||
for (long i = 0; i < numInputs; i++) {
|
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);
|
inputs.add(input);
|
||||||
cursor += input.getMessageSize();
|
cursor += input.getMessageSize();
|
||||||
}
|
}
|
||||||
@@ -300,7 +300,7 @@ public class Transaction extends Message implements Serializable {
|
|||||||
* accepted by the network.
|
* accepted by the network.
|
||||||
*/
|
*/
|
||||||
public void addInput(TransactionOutput from) {
|
public void addInput(TransactionOutput from) {
|
||||||
inputs.add(new TransactionInput(params, from));
|
inputs.add(new TransactionInput(params, this, from));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -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
|
// The Script object obtained from parsing scriptBytes. Only filled in on demand and if the transaction is not
|
||||||
// coinbase.
|
// coinbase.
|
||||||
transient private Script scriptSig;
|
transient private Script scriptSig;
|
||||||
|
// A pointer to the transaction that owns this input.
|
||||||
|
Transaction parentTransaction;
|
||||||
|
|
||||||
/** Used only in creation of the genesis block. */
|
/** Used only in creation of the genesis block. */
|
||||||
TransactionInput(NetworkParameters params, byte[] scriptBytes) {
|
TransactionInput(NetworkParameters params, Transaction parentTransaction, byte[] scriptBytes) {
|
||||||
super(params);
|
super(params);
|
||||||
this.scriptBytes = scriptBytes;
|
this.scriptBytes = scriptBytes;
|
||||||
this.outpoint = new TransactionOutPoint(params, -1, null);
|
this.outpoint = new TransactionOutPoint(params, -1, null);
|
||||||
this.sequence = 0xFFFFFFFFL;
|
this.sequence = 0xFFFFFFFFL;
|
||||||
|
this.parentTransaction = parentTransaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Creates an UNSIGNED input that links to the given output */
|
/** Creates an UNSIGNED input that links to the given output */
|
||||||
TransactionInput(NetworkParameters params, TransactionOutput output) {
|
TransactionInput(NetworkParameters params, Transaction parentTransaction, TransactionOutput output) {
|
||||||
super(params);
|
super(params);
|
||||||
long outputIndex = output.getIndex();
|
long outputIndex = output.getIndex();
|
||||||
outpoint = new TransactionOutPoint(params, outputIndex, output.parentTransaction);
|
outpoint = new TransactionOutPoint(params, outputIndex, output.parentTransaction);
|
||||||
scriptBytes = EMPTY_ARRAY;
|
scriptBytes = EMPTY_ARRAY;
|
||||||
this.sequence = 0xFFFFFFFFL;
|
sequence = 0xFFFFFFFFL;
|
||||||
|
this.parentTransaction = parentTransaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Deserializes an input message. This is usually part of a transaction message. */
|
/** 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);
|
super(params, payload, offset);
|
||||||
|
this.parentTransaction = parentTransaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
void parse() throws ProtocolException {
|
void parse() throws ProtocolException {
|
||||||
|
@@ -156,4 +156,9 @@ public class TransactionOutput extends Message implements Serializable {
|
|||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns the connected input. */
|
||||||
|
TransactionInput getSpentBy() {
|
||||||
|
return spentBy;
|
||||||
|
}
|
||||||
}
|
}
|
@@ -316,7 +316,13 @@ public class Wallet implements Serializable {
|
|||||||
} else if (result == TransactionInput.ConnectionResult.ALREADY_SPENT) {
|
} else if (result == TransactionInput.ConnectionResult.ALREADY_SPENT) {
|
||||||
// Double spend! This must have overridden a pending tx, or the block is bad (contains transactions
|
// 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).
|
// 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())) {
|
if (pending.containsKey(connected.getHash())) {
|
||||||
log.info("Saw double spend from chain override pending tx {}", connected.getHashAsString());
|
log.info("Saw double spend from chain override pending tx {}", connected.getHashAsString());
|
||||||
log.info(" <-pending ->dead");
|
log.info(" <-pending ->dead");
|
||||||
|
@@ -208,4 +208,43 @@ public class WalletTest {
|
|||||||
Transaction send2 = new Transaction(params, send1.bitcoinSerialize());
|
Transaction send2 = new Transaction(params, send1.bitcoinSerialize());
|
||||||
assertEquals(nanos, send2.getValueSentFromMe(wallet));
|
assertEquals(nanos, send2.getValueSentFromMe(wallet));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFinneyAttack() throws Exception {
|
||||||
|
// A Finney attack is where a miner includes a transaction spending coins to themselves but does not
|
||||||
|
// broadcast it. When they find a solved block, they hold it back temporarily whilst they buy something with
|
||||||
|
// those same coins. After purchasing, they broadcast the block thus reversing the transaction. It can be
|
||||||
|
// done by any miner for products that can be bought at a chosen time and very quickly (as every second you
|
||||||
|
// withold your block means somebody else might find it first, invalidating your work).
|
||||||
|
//
|
||||||
|
// Test that we handle ourselves performing the attack correctly: a double spend on the chain moves
|
||||||
|
// transactions from pending to dead.
|
||||||
|
//
|
||||||
|
// Note that the other way around, where a pending transaction sending us coins becomes dead,
|
||||||
|
// isn't tested because today BitCoinJ only learns about such transactions when they appear in the chain.
|
||||||
|
final Transaction[] eventDead = new Transaction[1];
|
||||||
|
final Transaction[] eventReplacement = new Transaction[1];
|
||||||
|
wallet.addEventListener(new WalletEventListener() {
|
||||||
|
@Override
|
||||||
|
public void onDeadTransaction(Transaction deadTx, Transaction replacementTx) {
|
||||||
|
eventDead[0] = deadTx;
|
||||||
|
eventReplacement[0] = replacementTx;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Receive 1 BTC.
|
||||||
|
BigInteger nanos = Utils.toNanoCoins(1, 0);
|
||||||
|
Transaction t1 = createFakeTx(nanos, myAddress);
|
||||||
|
wallet.receive(t1, null, BlockChain.NewBlockType.BEST_CHAIN);
|
||||||
|
// Create a send to a merchant.
|
||||||
|
Transaction send1 = wallet.createSend(new ECKey().toAddress(params), toNanoCoins(0, 50));
|
||||||
|
// Create a double spend.
|
||||||
|
Transaction send2 = wallet.createSend(new ECKey().toAddress(params), toNanoCoins(0, 50));
|
||||||
|
// Broadcast send1.
|
||||||
|
wallet.confirmSend(send1);
|
||||||
|
// Receive a block that overrides it.
|
||||||
|
wallet.receive(send2, null, BlockChain.NewBlockType.BEST_CHAIN);
|
||||||
|
assertEquals(send1, eventDead[0]);
|
||||||
|
assertEquals(send2, eventReplacement[0]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user