3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-02-12 02:05:53 +00:00

Don't mark outputs that spend to non-wallet addresses as spent. This avoids a problem in the case where you send somebody coins with change and they immediately send you the coins back. Add a unit test to prove the bug is solved. Existing wallets will need to be refreshed. Resolves issue 64.

This commit is contained in:
Mike Hearn 2011-08-05 12:36:05 +00:00
parent 3327e02f80
commit dafb806f05
2 changed files with 30 additions and 9 deletions

View File

@ -338,10 +338,13 @@ public class Wallet implements Serializable {
// A -> spent by B [pending]
// \-> spent by C [chain]
Transaction doubleSpent = input.outpoint.fromTx; // == A
assert doubleSpent != null;
int index = (int) input.outpoint.index;
TransactionOutput output = doubleSpent.outputs.get(index);
TransactionInput spentBy = output.getSpentBy();
assert spentBy != null;
Transaction connected = spentBy.parentTransaction;
assert connected != null;
if (pending.containsKey(connected.getHash())) {
log.info("Saw double spend from chain override pending tx {}", connected.getHashAsString());
log.info(" <-pending ->dead");
@ -404,15 +407,7 @@ public class Wallet implements Serializable {
TransactionOutput connectedOutput = input.outpoint.getConnectedOutput();
connectedOutput.markAsSpent(input);
}
// Some of the outputs probably send coins back to us, eg for change or because this transaction is just
// consolidating the wallet. Mark any output that is NOT back to us as spent. Then add this TX to the
// pending pool.
for (TransactionOutput output : tx.outputs) {
if (!output.isMine(this)) {
// This output didn't go to us, so by definition it is now spent.
output.markAsSpent(null);
}
}
// Add to the pending pool. It'll be moved out once we receive this transaction on the best chain.
pending.put(tx.getHash(), tx);
}

View File

@ -184,6 +184,9 @@ public class WalletTest {
Address someOtherGuy = new ECKey().toAddress(params);
TransactionOutput output = new TransactionOutput(params, tx, Utils.toNanoCoins(0, 5), someOtherGuy);
tx.addOutput(output);
// Note that tx is no longer valid: it spends more than it imports. However checking transactions balance
// correctly isn't possible in SPV mode because value is a property of outputs not inputs. Without all
// transactions you can't check they add up.
wallet.receive(tx, null, BlockChain.NewBlockType.BEST_CHAIN);
// Now the other guy creates a transaction which spends that change.
Transaction tx2 = new Transaction(params);
@ -193,6 +196,29 @@ public class WalletTest {
assertEquals(Utils.toNanoCoins(0, 0), tx2.getValueSentFromMe(wallet));
}
@Test
public void bounce() throws Exception {
// This test covers bug 64 (False double spends). Check that if we create a spend and it's immediately sent
// back to us, this isn't considered as a double spend.
BigInteger coin1 = Utils.toNanoCoins(1, 0);
BigInteger coinHalf = Utils.toNanoCoins(0, 50);
// Start by giving us 1 coin.
Transaction inbound1 = createFakeTx(params, coin1, myAddress);
wallet.receive(inbound1, null, BlockChain.NewBlockType.BEST_CHAIN);
// Send half to some other guy. Sending only half then waiting for a confirm is important to ensure the tx is
// in the unspent pool, not pending or spent.
Address someOtherGuy = new ECKey().toAddress(params);
Transaction outbound1 = wallet.createSend(someOtherGuy, coinHalf);
wallet.confirmSend(outbound1);
wallet.receive(outbound1, null, BlockChain.NewBlockType.BEST_CHAIN);
// That other guy gives us the coins right back.
Transaction inbound2 = new Transaction(params);
inbound2.addOutput(new TransactionOutput(params, inbound2, coinHalf, myAddress));
inbound2.addInput(outbound1.outputs.get(0));
wallet.receive(inbound2, null, BlockChain.NewBlockType.BEST_CHAIN);
assertEquals(coin1, wallet.getBalance());
}
@Test
public void finneyAttack() throws Exception {
// A Finney attack is where a miner includes a transaction spending coins to themselves but does not