3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-02-14 19:25:51 +00:00

Replace WalletEventListener.onDeadTransaction with a generic confidence changed callback, this simplifies the case of listening for all confidence changes in all wallet transactions and gives a single place to save the wallet from.

This commit is contained in:
Mike Hearn 2012-02-19 15:01:30 +01:00
parent 68424281c5
commit 54a2a71460
5 changed files with 81 additions and 65 deletions

View File

@ -76,18 +76,18 @@ public abstract class AbstractWalletEventListener implements WalletEventListener
}
/**
* This is called on a Peer thread when a transaction becomes <i>dead</i>. A dead transaction is one that has
* been overridden by a double spend from the network and so will never confirm no matter how long you wait.<p>
* Called on a Peer thread when a transaction changes its confidence level. You can also attach event listeners to
* the individual transactions, if you don't care about all of them. Usually you would save the wallet to disk after
* receiving this callback.<p>
*
* A dead transaction can occur if somebody is attacking the network, or by accident if keys are being shared.
* You can use this event handler to inform the user of the situation. A dead spend will show up in the BitCoin
* C++ client of the recipient as 0/unconfirmed forever, so if it was used to purchase something,
* the user needs to know their goods will never arrive.
* You should pay attention to this callback in case a transaction becomes <i>dead</i>, that is, somebody
* successfully executed a double spend against you. This is a (very!) rare situation but the user should be
* notified that money they thought they had, was taken away from them.<p>
*
* @param deadTx The transaction that is newly dead.
* @param replacementTx The transaction that killed it.
* @param wallet
* @param tx
*/
public void onDeadTransaction(Wallet wallet, Transaction deadTx, Transaction replacementTx) {
public void onTransactionConfidenceChanged(Wallet wallet, Transaction tx) {
onChange();
}

View File

@ -339,9 +339,10 @@ public class Wallet implements Serializable {
// the Peer before we got to this point, but in some cases (unit tests, other sources of transactions) it may
// have been missed out.
TransactionConfidence.ConfidenceType currentConfidence = tx.getConfidence().getConfidenceType();
assert currentConfidence == TransactionConfidence.ConfidenceType.UNKNOWN ||
currentConfidence == TransactionConfidence.ConfidenceType.NOT_SEEN_IN_CHAIN : currentConfidence;
tx.getConfidence().setConfidenceType(TransactionConfidence.ConfidenceType.NOT_SEEN_IN_CHAIN);
if (currentConfidence == TransactionConfidence.ConfidenceType.UNKNOWN) {
tx.getConfidence().setConfidenceType(TransactionConfidence.ConfidenceType.NOT_SEEN_IN_CHAIN);
invokeOnTransactionConfidenceChanged(tx);
}
BigInteger balance = getBalance();
@ -472,17 +473,14 @@ public class Wallet implements Serializable {
pending.put(tx.getHash(), tx);
}
} else {
// Mark the tx as appearing in this block so we can find it later after a re-org. If this IS a re-org taking
// place, this call will let the Transaction update its confidence and timestamp data to reflect the new
// best chain, as long as the new best chain blocks are passed to receive() last.
if (block != null)
tx.setBlockAppearance(block, bestChain);
// This TX didn't originate with us. It could be sending us coins and also spending our own coins if keys
// are being shared between different wallets.
if (sideChain) {
log.info(" ->inactive");
inactive.put(tx.getHash(), tx);
} else if (bestChain) {
// This can trigger tx confidence listeners to be run in the case of double spends. We may need to
// delay the execution of the listeners until the bottom to avoid the wallet mutating during updates.
processTxFromBestChain(tx);
}
}
@ -497,6 +495,7 @@ public class Wallet implements Serializable {
// transaction update its confidence and timestamp bookkeeping data.
if (block != null) {
tx.setBlockAppearance(block, bestChain);
invokeOnTransactionConfidenceChanged(tx);
}
// Inform anyone interested that we have received or sent coins but only if:
@ -508,7 +507,8 @@ public class Wallet implements Serializable {
// listeners.
//
// TODO: Decide whether to run the event listeners, if a tx confidence listener already modified the wallet.
if (!reorg && bestChain && wtx == null) {
boolean wasPending = wtx != null;
if (!reorg && bestChain && !wasPending) {
BigInteger newBalance = getBalance();
if (valueSentToMe.compareTo(BigInteger.ZERO) > 0) {
invokeOnCoinsReceived(tx, prevBalance, newBalance);
@ -556,7 +556,7 @@ public class Wallet implements Serializable {
dead.put(doubleSpend.getHash(), doubleSpend);
// Inform the event listeners of the newly dead tx.
doubleSpend.getConfidence().setOverridingTransaction(tx);
invokeOnDeadTransaction(doubleSpend, tx);
invokeOnTransactionConfidenceChanged(doubleSpend);
}
}
@ -610,13 +610,9 @@ public class Wallet implements Serializable {
dead.put(connected.getHash(), connected);
// Now forcibly change the connection.
input.connect(unspent, true);
// Inform the event listeners of the newly dead tx.
// Inform the [tx] event listeners of the newly dead tx. This sets confidence type also.
connected.getConfidence().setOverridingTransaction(tx);
for (WalletEventListener listener : eventListeners) {
synchronized (listener) {
listener.onDeadTransaction(this, connected, tx);
}
}
invokeOnTransactionConfidenceChanged(connected);
}
} else {
// A pending transaction that tried to double spend our coins - we log and ignore it, because either
@ -1426,15 +1422,13 @@ public class Wallet implements Serializable {
log.info("post-reorg balance is {}", Utils.bitcoinValueToFriendlyString(getBalance()));
// Inform event listeners that a re-org took place.
for (WalletEventListener l : eventListeners) {
// Synchronize on the event listener as well. This allows a single listener to handle events from
// multiple wallets without needing to worry about being thread safe.
synchronized (l) {
l.onReorganize(this);
// Inform event listeners that a re-org took place. They should save the wallet at this point.
EventListenerInvoker.invoke(eventListeners, new EventListenerInvoker<WalletEventListener>() {
@Override
public void invoke(WalletEventListener listener) {
listener.onReorganize(Wallet.this);
}
}
});
assert isConsistent();
}
@ -1464,7 +1458,9 @@ public class Wallet implements Serializable {
Transaction replacement = doubleSpent.getSpentBy().getParentTransaction();
dead.put(tx.getHash(), tx);
pending.remove(tx.getHash());
invokeOnDeadTransaction(tx, replacement);
// This updates the tx confidence type automatically.
tx.getConfidence().setOverridingTransaction(replacement);
invokeOnTransactionConfidenceChanged(tx);
break;
}
}
@ -1481,19 +1477,16 @@ public class Wallet implements Serializable {
}
}
private void invokeOnDeadTransaction(Transaction tx, Transaction replacement) {
for (int i = 0; i < eventListeners.size(); i++) {
WalletEventListener listener = eventListeners.get(i);
synchronized (listener) {
listener.onDeadTransaction(this, tx, replacement);
private void invokeOnTransactionConfidenceChanged(final Transaction tx) {
EventListenerInvoker.invoke(eventListeners, new EventListenerInvoker<WalletEventListener>() {
@Override
public void invoke(WalletEventListener listener) {
listener.onTransactionConfidenceChanged(Wallet.this, tx);
}
if (eventListeners.get(i) != listener) {
// Listener removed itself.
i--;
}
}
});
}
/**
* Returns an immutable view of the transactions currently waiting for network confirmations.
*/

View File

@ -75,17 +75,28 @@ public interface WalletEventListener {
*/
void onReorganize(Wallet wallet);
// TODO: Flesh out the docs below some more to clarify what happens during re-orgs and other edge cases.
/**
* This is called on a Peer thread when a transaction becomes <i>dead</i>. A dead transaction is one that has
* been overridden by a double spend from the network and so will never confirm no matter how long you wait.<p>
* <p/>
* A dead transaction can occur if somebody is attacking the network, or by accident if keys are being shared.
* You can use this event handler to inform the user of the situation. A dead spend will show up in the BitCoin
* C++ client of the recipient as 0/unconfirmed forever, so if it was used to purchase something,
* the user needs to know their goods will never arrive.
* Called on a Peer thread when a transaction changes its confidence level. You can also attach event listeners to
* the individual transactions, if you don't care about all of them. Usually you would save the wallet to disk after
* receiving this callback.<p>
*
* @param deadTx The transaction that is newly dead.
* @param replacementTx The transaction that killed it.
* You should pay attention to this callback in case a transaction becomes <i>dead</i>, that is, a transaction you
* believed to be active (send or receive) becomes overridden by the network. This can happen if<p>
*
* <ol>
* <li>You are sharing keys between wallets and accidentally create/broadcast a double spend.</li>
* <li>Somebody is attacking the network and reversing transactions, ie, the user is a victim of fraud.</li>
* <li>A bug: for example you create a transaction, broadcast it but fail to commit it. The {@link Wallet}
* will then re-use the same outputs when creating the next spend.</li>
* </ol><p>
*
* To find if the transaction is dead, you can use <tt>tx.getConfidence().getConfidenceType() ==
* TransactionConfidence.ConfidenceType.OVERRIDDEN_BY_DOUBLE_SPEND</tt>. If it is, you should notify the user
* in some way so they know the thing they bought may not arrive/the thing they sold should not be dispatched.
*
* @param wallet
* @param tx
*/
void onDeadTransaction(Wallet wallet, Transaction deadTx, Transaction replacementTx);
void onTransactionConfidenceChanged(Wallet wallet, Transaction tx);
}

View File

@ -187,8 +187,10 @@ public class ChainSplitTests {
final boolean[] eventCalled = new boolean[1];
wallet.addEventListener(new AbstractWalletEventListener() {
@Override
public void onDeadTransaction(Wallet wallet, Transaction deadTx, Transaction replacementTx) {
eventCalled[0] = true;
public void onTransactionConfidenceChanged(Wallet wallet, Transaction tx) {
super.onTransactionConfidenceChanged(wallet, tx);
if (tx.getConfidence().getConfidenceType() == TransactionConfidence.ConfidenceType.OVERRIDDEN_BY_DOUBLE_SPEND)
eventCalled[0] = true;
}
});
@ -227,9 +229,10 @@ public class ChainSplitTests {
final Transaction[] eventReplacement = new Transaction[1];
wallet.addEventListener(new AbstractWalletEventListener() {
@Override
public void onDeadTransaction(Wallet wallet, Transaction deadTx, Transaction replacementTx) {
eventDead[0] = deadTx;
eventReplacement[0] = replacementTx;
public void onTransactionConfidenceChanged(Wallet wallet, Transaction tx) {
super.onTransactionConfidenceChanged(wallet, tx);
eventDead[0] = tx;
eventReplacement[0] = tx.getConfidence().getOverridingTransaction();
}
});

View File

@ -310,9 +310,13 @@ public class WalletTest {
final Transaction[] eventReplacement = new Transaction[1];
wallet.addEventListener(new AbstractWalletEventListener() {
@Override
public void onDeadTransaction(Wallet wallet, Transaction deadTx, Transaction replacementTx) {
eventDead[0] = deadTx;
eventReplacement[0] = replacementTx;
public void onTransactionConfidenceChanged(Wallet wallet, Transaction tx) {
super.onTransactionConfidenceChanged(wallet, tx);
if (tx.getConfidence().getConfidenceType() ==
TransactionConfidence.ConfidenceType.OVERRIDDEN_BY_DOUBLE_SPEND) {
eventDead[0] = tx;
eventReplacement[0] = tx.getConfidence().getOverridingTransaction();
}
}
});
@ -474,9 +478,14 @@ public class WalletTest {
called[0] = tx;
}
public void onDeadTransaction(Wallet wallet, Transaction deadTx, Transaction replacementTx) {
called[0] = deadTx;
called[1] = replacementTx;
@Override
public void onTransactionConfidenceChanged(Wallet wallet, Transaction tx) {
super.onTransactionConfidenceChanged(wallet, tx);
if (tx.getConfidence().getConfidenceType() ==
TransactionConfidence.ConfidenceType.OVERRIDDEN_BY_DOUBLE_SPEND) {
called[0] = tx;
called[1] = tx.getConfidence().getOverridingTransaction();
}
}
});