mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-02-07 06:44:16 +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:
parent
846412aa00
commit
cb5025b987
@ -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);
|
||||
}
|
||||
|
@ -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"));
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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 {
|
||||
|
@ -156,4 +156,9 @@ public class TransactionOutput extends Message implements Serializable {
|
||||
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) {
|
||||
// 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");
|
||||
|
@ -208,4 +208,43 @@ public class WalletTest {
|
||||
Transaction send2 = new Transaction(params, send1.bitcoinSerialize());
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user