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

Wallet: fix a bug that could cause a temporarily corrupted balance, when two pending transactions arrive backwards

This commit is contained in:
Mike Hearn 2014-12-04 17:09:53 +01:00
parent fccb6f03bd
commit 757e25ba9b
2 changed files with 40 additions and 12 deletions

View File

@ -1975,23 +1975,28 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
// ever occur because we expect transactions to arrive in temporal order, but this assumption can be violated
// when we receive a pending transaction from the mempool that is relevant to us, which spends coins that we
// didn't see arrive on the best chain yet. For instance, because of a chain replay or because of our keys were
// used by another wallet somewhere else.
if (fromChain) {
for (Transaction pendingTx : pending.values()) {
for (TransactionInput input : pendingTx.getInputs()) {
TransactionInput.ConnectionResult result = input.connect(tx, TransactionInput.ConnectMode.ABORT_ON_CONFLICT);
// used by another wallet somewhere else. Also, unconfirmed transactions can arrive from the mempool in more or
// less random order.
for (Transaction pendingTx : pending.values()) {
for (TransactionInput input : pendingTx.getInputs()) {
TransactionInput.ConnectionResult result = input.connect(tx, TransactionInput.ConnectMode.ABORT_ON_CONFLICT);
if (fromChain) {
// This TX is supposed to have just appeared on the best chain, so its outputs should not be marked
// as spent yet. If they are, it means something is happening out of order.
checkState(result != TransactionInput.ConnectionResult.ALREADY_SPENT);
if (result == TransactionInput.ConnectionResult.SUCCESS) {
log.info("Connected pending tx input {}:{}",
pendingTx.getHashAsString(), pendingTx.getInputs().indexOf(input));
}
}
// If the transactions outputs are now all spent, it will be moved into the spent pool by the
// processTxFromBestChain method.
if (result == TransactionInput.ConnectionResult.SUCCESS) {
log.info("Connected pending tx input {}:{}",
pendingTx.getHashAsString(), pendingTx.getInputs().indexOf(input));
}
}
}
if (!fromChain) {
maybeMovePool(tx, "pendingtx");
} else {
// If the transactions outputs are now all spent, it will be moved into the spent pool by the
// processTxFromBestChain method.
}
}
// Updates the wallet when a double spend occurs. overridingTx can be null for the case of coinbases

View File

@ -71,6 +71,9 @@ public class WalletTest extends TestWithWallet {
private SecureRandom secureRandom = new SecureRandom();
private ECKey someOtherKey = new ECKey();
private Address someOtherAddress = someOtherKey.toAddress(params);
@Before
@Override
public void setUp() throws Exception {
@ -1467,7 +1470,7 @@ public class WalletTest extends TestWithWallet {
Transaction tx1 = createFakeTx(params, value, myAddress);
Transaction tx2 = new Transaction(params);
tx2.addInput(tx1.getOutput(0));
tx2.addOutput(valueOf(0, 9), new ECKey());
tx2.addOutput(valueOf(0, 9), someOtherAddress);
// Add a change address to ensure this tx is relevant.
tx2.addOutput(CENT, wallet.getChangeAddress());
wallet.receivePending(tx2, null);
@ -1480,6 +1483,26 @@ public class WalletTest extends TestWithWallet {
assertEquals(0, wallet.getPoolSize(Pool.UNSPENT));
}
@Test
public void outOfOrderPendingTxns() throws Exception {
// Check that if there are two pending transactions which we receive out of order, they are marked as spent
// correctly. For instance, we are watching a wallet, someone pays us (A) and we then pay someone else (B)
// with a change address but the network delivers the transactions to us in order B then A.
Coin value = COIN;
Transaction a = createFakeTx(params, value, myAddress);
Transaction b = new Transaction(params);
b.addInput(a.getOutput(0));
b.addOutput(CENT, someOtherAddress);
Coin v = COIN.subtract(CENT);
b.addOutput(v, wallet.getChangeAddress());
a = roundTripTransaction(params, a);
b = roundTripTransaction(params, b);
wallet.receivePending(b, null);
assertEquals(v, wallet.getBalance(Wallet.BalanceType.ESTIMATED));
wallet.receivePending(a, null);
assertEquals(v, wallet.getBalance(Wallet.BalanceType.ESTIMATED));
}
@Test
public void encryptionDecryptionAESBasic() throws Exception {
Wallet encryptedWallet = new Wallet(params);