mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-07-31 20:11:23 +00:00
Fix for irrelevant double spend causing protobuf serialization issues
Fixes #1374. Previously, transactions could have spentBy set but the spending transaction dropped as being irrelevant. This would then be serialized to protobuf but could not be deserialized.
This commit is contained in:
committed by
Andreas Schildbach
parent
9bde092143
commit
943a139d08
@@ -1820,6 +1820,8 @@ public class Wallet extends BaseTaggableObject
|
||||
// Now for each pending transaction, see if it shares any outpoints with this tx.
|
||||
Set<Transaction> doubleSpendTxns = Sets.newHashSet();
|
||||
for (Transaction p : candidates.values()) {
|
||||
if (p.equals(tx))
|
||||
continue;
|
||||
for (TransactionInput input : p.getInputs()) {
|
||||
// This relies on the fact that TransactionOutPoint equality is defined at the protocol not object
|
||||
// level - outpoints from two different inputs that point to the same output compare the same.
|
||||
@@ -2185,6 +2187,7 @@ public class Wallet extends BaseTaggableObject
|
||||
// against our pending transactions. Note that a tx may double spend our pending transactions and also send
|
||||
// us money/spend our money.
|
||||
boolean hasOutputsToMe = tx.getValueSentToMe(this).signum() > 0;
|
||||
boolean hasOutputsFromMe = false;
|
||||
if (hasOutputsToMe) {
|
||||
// Needs to go into either unspent or spent (if the outputs were already spent by a pending tx).
|
||||
if (tx.isEveryOwnedOutputSpent(this)) {
|
||||
@@ -2195,6 +2198,7 @@ public class Wallet extends BaseTaggableObject
|
||||
addWalletTransaction(Pool.UNSPENT, tx);
|
||||
}
|
||||
} else if (tx.getValueSentFromMe(this).signum() > 0) {
|
||||
hasOutputsFromMe = true;
|
||||
// Didn't send us any money, but did spend some. Keep it around for record keeping purposes.
|
||||
log.info(" tx {} ->spent", tx.getHashAsString());
|
||||
addWalletTransaction(Pool.SPENT, tx);
|
||||
@@ -2210,6 +2214,19 @@ public class Wallet extends BaseTaggableObject
|
||||
// no need to addTransactionsDependingOn(doubleSpendTxns) because killTxns() already kills dependencies;
|
||||
killTxns(doubleSpendTxns, tx);
|
||||
}
|
||||
if (!hasOutputsToMe
|
||||
&& !hasOutputsFromMe
|
||||
&& !forceAddToPool
|
||||
&& !findDoubleSpendsAgainst(tx, transactions).isEmpty())
|
||||
{
|
||||
// disconnect irrelevant inputs (otherwise might cause protobuf serialization issue)
|
||||
for (TransactionInput input : tx.getInputs()) {
|
||||
TransactionOutput output = input.getConnectedOutput();
|
||||
if (output != null && !output.isMineOrWatched(this)) {
|
||||
input.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -3441,4 +3441,84 @@ public class WalletTest extends TestWithWallet {
|
||||
|
||||
// TODO: test shared wallet calculation here
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIrrelevantDoubleSpend() throws Exception {
|
||||
Transaction tx0 = createFakeTx(PARAMS);
|
||||
Transaction tx1 = createFakeTx(PARAMS);
|
||||
|
||||
Transaction tx2 = new Transaction(PARAMS);
|
||||
tx2.addInput(tx0.getOutput(0));
|
||||
tx2.addOutput(COIN, myAddress);
|
||||
tx2.addOutput(COIN, OTHER_ADDRESS);
|
||||
|
||||
sendMoneyToWallet(AbstractBlockChain.NewBlockType.BEST_CHAIN, tx2, tx1, tx0);
|
||||
|
||||
// tx3 and tx4 double spend each other
|
||||
Transaction tx3 = new Transaction(PARAMS);
|
||||
tx3.addInput(tx1.getOutput(0));
|
||||
tx3.addOutput(COIN, myAddress);
|
||||
tx3.addOutput(COIN, OTHER_ADDRESS);
|
||||
wallet.receivePending(tx3, null);
|
||||
|
||||
// tx4 also spends irrelevant output from tx2
|
||||
Transaction tx4 = new Transaction(PARAMS);
|
||||
tx4.addInput(tx1.getOutput(0)); // spends same output
|
||||
tx4.addInput(tx2.getOutput(1));
|
||||
tx4.addOutput(COIN, OTHER_ADDRESS);
|
||||
|
||||
// tx4 does not actually get added to wallet here since it by itself is irrelevant
|
||||
sendMoneyToWallet(AbstractBlockChain.NewBlockType.BEST_CHAIN, tx4);
|
||||
|
||||
// since tx4 is not saved, tx2 output 1 will have bad spentBy
|
||||
wallet = roundTrip(wallet);
|
||||
|
||||
assertTrue(wallet.isConsistent());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void overridingDeadTxTest() throws Exception {
|
||||
Transaction tx0 = createFakeTx(PARAMS);
|
||||
|
||||
Transaction tx1 = new Transaction(PARAMS);
|
||||
tx1.addInput(tx0.getOutput(0));
|
||||
tx1.addOutput(COIN, OTHER_ADDRESS);
|
||||
tx1.addOutput(COIN, OTHER_ADDRESS);
|
||||
tx1.addOutput(COIN, myAddress); // to save this in wallet
|
||||
|
||||
sendMoneyToWallet(AbstractBlockChain.NewBlockType.BEST_CHAIN, tx0, tx1);
|
||||
|
||||
// tx2, tx3 and tx4 double spend each other
|
||||
Transaction tx2 = new Transaction(PARAMS);
|
||||
tx2.addInput(tx1.getOutput(0));
|
||||
tx2.addInput(tx1.getOutput(1));
|
||||
tx2.addOutput(COIN, myAddress);
|
||||
tx2.addOutput(COIN, OTHER_ADDRESS);
|
||||
wallet.receivePending(tx2, null);
|
||||
|
||||
// irrelevant to the wallet
|
||||
Transaction tx3 = new Transaction(PARAMS);
|
||||
tx3.addInput(tx1.getOutput(0)); // spends same output as tx2
|
||||
tx3.addOutput(COIN, OTHER_ADDRESS);
|
||||
|
||||
// irrelevant to the wallet
|
||||
Transaction tx4 = new Transaction(PARAMS);
|
||||
tx4.addInput(tx1.getOutput(1)); // spends different output, but also in tx2
|
||||
tx4.addOutput(COIN, OTHER_ADDRESS);
|
||||
|
||||
assertUnspent(tx1);
|
||||
assertPending(tx2);
|
||||
sendMoneyToWallet(AbstractBlockChain.NewBlockType.BEST_CHAIN, tx3);
|
||||
assertUnspent(tx1);
|
||||
assertDead(tx2);
|
||||
assertEquals(2, wallet.transactions.size()); // tx3 not saved
|
||||
sendMoneyToWallet(AbstractBlockChain.NewBlockType.BEST_CHAIN, tx4);
|
||||
assertUnspent(tx1);
|
||||
assertDead(tx2);
|
||||
assertEquals(2, wallet.transactions.size()); // tx4 not saved
|
||||
|
||||
// this will fail if tx4 does not get disconnected from tx1
|
||||
wallet = roundTrip(wallet);
|
||||
assertTrue(wallet.isConsistent());
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user