mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-02-12 10:15:52 +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:
parent
3327e02f80
commit
dafb806f05
@ -338,10 +338,13 @@ public class Wallet implements Serializable {
|
|||||||
// A -> spent by B [pending]
|
// A -> spent by B [pending]
|
||||||
// \-> spent by C [chain]
|
// \-> spent by C [chain]
|
||||||
Transaction doubleSpent = input.outpoint.fromTx; // == A
|
Transaction doubleSpent = input.outpoint.fromTx; // == A
|
||||||
|
assert doubleSpent != null;
|
||||||
int index = (int) input.outpoint.index;
|
int index = (int) input.outpoint.index;
|
||||||
TransactionOutput output = doubleSpent.outputs.get(index);
|
TransactionOutput output = doubleSpent.outputs.get(index);
|
||||||
TransactionInput spentBy = output.getSpentBy();
|
TransactionInput spentBy = output.getSpentBy();
|
||||||
|
assert spentBy != null;
|
||||||
Transaction connected = spentBy.parentTransaction;
|
Transaction connected = spentBy.parentTransaction;
|
||||||
|
assert connected != null;
|
||||||
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");
|
||||||
@ -404,15 +407,7 @@ public class Wallet implements Serializable {
|
|||||||
TransactionOutput connectedOutput = input.outpoint.getConnectedOutput();
|
TransactionOutput connectedOutput = input.outpoint.getConnectedOutput();
|
||||||
connectedOutput.markAsSpent(input);
|
connectedOutput.markAsSpent(input);
|
||||||
}
|
}
|
||||||
// Some of the outputs probably send coins back to us, eg for change or because this transaction is just
|
// Add to the pending pool. It'll be moved out once we receive this transaction on the best chain.
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pending.put(tx.getHash(), tx);
|
pending.put(tx.getHash(), tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,6 +184,9 @@ public class WalletTest {
|
|||||||
Address someOtherGuy = new ECKey().toAddress(params);
|
Address someOtherGuy = new ECKey().toAddress(params);
|
||||||
TransactionOutput output = new TransactionOutput(params, tx, Utils.toNanoCoins(0, 5), someOtherGuy);
|
TransactionOutput output = new TransactionOutput(params, tx, Utils.toNanoCoins(0, 5), someOtherGuy);
|
||||||
tx.addOutput(output);
|
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);
|
wallet.receive(tx, null, BlockChain.NewBlockType.BEST_CHAIN);
|
||||||
// Now the other guy creates a transaction which spends that change.
|
// Now the other guy creates a transaction which spends that change.
|
||||||
Transaction tx2 = new Transaction(params);
|
Transaction tx2 = new Transaction(params);
|
||||||
@ -193,6 +196,29 @@ public class WalletTest {
|
|||||||
assertEquals(Utils.toNanoCoins(0, 0), tx2.getValueSentFromMe(wallet));
|
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
|
@Test
|
||||||
public void finneyAttack() throws Exception {
|
public void finneyAttack() throws Exception {
|
||||||
// A Finney attack is where a miner includes a transaction spending coins to themselves but does not
|
// A Finney attack is where a miner includes a transaction spending coins to themselves but does not
|
||||||
|
Loading…
x
Reference in New Issue
Block a user