mirror of
https://github.com/bitcoinj/bitcoinj.git
synced 2025-03-10 17:26:28 +01:00
Change the wallet to relay tx confidence events instead of generating them itself, which is a bit cleaner. Centralize state that needs to be rebuilt after a Java deserialization. Resolves issue 235.
This commit is contained in:
parent
eff88810e2
commit
fedfe9d0e6
3 changed files with 63 additions and 22 deletions
|
@ -923,7 +923,10 @@ public class PeerGroup {
|
||||||
// the transaction. In future when peers sync up their memory pools after they connect
|
// the transaction. In future when peers sync up their memory pools after they connect
|
||||||
// we could come back and change this.
|
// we could come back and change this.
|
||||||
//
|
//
|
||||||
// Now tell the wallet about the transaction as it didn't get informed before.
|
// Now tell the wallet about the transaction. If the wallet created the transaction then
|
||||||
|
// it already knows and will ignore this. If it's a transaction we received from
|
||||||
|
// somebody else via a side channel and are now broadcasting, this will put it into the
|
||||||
|
// wallet now we know it's valid.
|
||||||
for (Wallet wallet : wallets) {
|
for (Wallet wallet : wallets) {
|
||||||
try {
|
try {
|
||||||
wallet.receivePending(pinnedTx);
|
wallet.receivePending(pinnedTx);
|
||||||
|
|
|
@ -161,7 +161,7 @@ public class Wallet implements Serializable {
|
||||||
*/
|
*/
|
||||||
private Sha256Hash lastBlockSeenHash;
|
private Sha256Hash lastBlockSeenHash;
|
||||||
|
|
||||||
transient private ArrayList<WalletEventListener> eventListeners;
|
private transient ArrayList<WalletEventListener> eventListeners;
|
||||||
|
|
||||||
// Auto-save code. This all should be generalized in future to not be file specific so you can easily store the
|
// Auto-save code. This all should be generalized in future to not be file specific so you can easily store the
|
||||||
// wallet into a database using the same mechanism. However we need to inform stores of each specific change with
|
// wallet into a database using the same mechanism. However we need to inform stores of each specific change with
|
||||||
|
@ -173,6 +173,10 @@ public class Wallet implements Serializable {
|
||||||
private transient AutosaveEventListener autosaveEventListener;
|
private transient AutosaveEventListener autosaveEventListener;
|
||||||
private transient long autosaveDelayMs;
|
private transient long autosaveDelayMs;
|
||||||
|
|
||||||
|
// A listener that relays confidence changes from the transaction confidence object to the wallet event listener,
|
||||||
|
// as a convenience to API users so they don't have to register on every transaction themselves.
|
||||||
|
private transient TransactionConfidence.Listener txConfidenceListener;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new, empty wallet with no keys and no transactions. If you want to restore a wallet from disk instead,
|
* Creates a new, empty wallet with no keys and no transactions. If you want to restore a wallet from disk instead,
|
||||||
* see loadFromFile.
|
* see loadFromFile.
|
||||||
|
@ -185,7 +189,16 @@ public class Wallet implements Serializable {
|
||||||
inactive = new HashMap<Sha256Hash, Transaction>();
|
inactive = new HashMap<Sha256Hash, Transaction>();
|
||||||
pending = new HashMap<Sha256Hash, Transaction>();
|
pending = new HashMap<Sha256Hash, Transaction>();
|
||||||
dead = new HashMap<Sha256Hash, Transaction>();
|
dead = new HashMap<Sha256Hash, Transaction>();
|
||||||
|
createTransientState();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createTransientState() {
|
||||||
eventListeners = new ArrayList<WalletEventListener>();
|
eventListeners = new ArrayList<WalletEventListener>();
|
||||||
|
txConfidenceListener = new TransactionConfidence.Listener() {
|
||||||
|
public void onConfidenceChanged(Transaction tx) {
|
||||||
|
invokeOnTransactionConfidenceChanged(tx);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public NetworkParameters getNetworkParameters() {
|
public NetworkParameters getNetworkParameters() {
|
||||||
|
@ -529,7 +542,7 @@ public class Wallet implements Serializable {
|
||||||
|
|
||||||
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
|
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
|
||||||
in.defaultReadObject();
|
in.defaultReadObject();
|
||||||
eventListeners = new ArrayList<WalletEventListener>();
|
createTransientState();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -599,12 +612,14 @@ public class Wallet implements Serializable {
|
||||||
TransactionConfidence.ConfidenceType currentConfidence = tx.getConfidence().getConfidenceType();
|
TransactionConfidence.ConfidenceType currentConfidence = tx.getConfidence().getConfidenceType();
|
||||||
if (currentConfidence == TransactionConfidence.ConfidenceType.UNKNOWN) {
|
if (currentConfidence == TransactionConfidence.ConfidenceType.UNKNOWN) {
|
||||||
tx.getConfidence().setConfidenceType(TransactionConfidence.ConfidenceType.NOT_SEEN_IN_CHAIN);
|
tx.getConfidence().setConfidenceType(TransactionConfidence.ConfidenceType.NOT_SEEN_IN_CHAIN);
|
||||||
|
// Manually invoke the wallet tx confidence listener here as we didn't yet commit therefore the
|
||||||
|
// txConfidenceListener wasn't added.
|
||||||
invokeOnTransactionConfidenceChanged(tx);
|
invokeOnTransactionConfidenceChanged(tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this tx spends any of our unspent outputs, mark them as spent now, then add to the pending pool. This
|
// If this tx spends any of our unspent outputs, mark them as spent now, then add to the pending pool. This
|
||||||
// ensures that if some other client that has our keys broadcasts a spend we stay in sync. Also updates the
|
// ensures that if some other client that has our keys broadcasts a spend we stay in sync. Also updates the
|
||||||
// timestamp on the transaction and runs event listeners.
|
// timestamp on the transaction and registers/runs event listeners.
|
||||||
//
|
//
|
||||||
// Note that after we return from this function, the wallet may have been modified.
|
// Note that after we return from this function, the wallet may have been modified.
|
||||||
commitTx(tx);
|
commitTx(tx);
|
||||||
|
@ -799,7 +814,6 @@ public class Wallet implements Serializable {
|
||||||
Set<Transaction> transactions = getTransactions(true, false);
|
Set<Transaction> transactions = getTransactions(true, false);
|
||||||
for (Transaction tx : transactions) {
|
for (Transaction tx : transactions) {
|
||||||
tx.getConfidence().notifyWorkDone(block);
|
tx.getConfidence().notifyWorkDone(block);
|
||||||
invokeOnTransactionConfidenceChanged(tx);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
queueAutoSave();
|
queueAutoSave();
|
||||||
|
@ -859,7 +873,6 @@ public class Wallet implements Serializable {
|
||||||
dead.put(doubleSpend.getHash(), doubleSpend);
|
dead.put(doubleSpend.getHash(), doubleSpend);
|
||||||
// Inform the event listeners of the newly dead tx.
|
// Inform the event listeners of the newly dead tx.
|
||||||
doubleSpend.getConfidence().setOverridingTransaction(tx);
|
doubleSpend.getConfidence().setOverridingTransaction(tx);
|
||||||
invokeOnTransactionConfidenceChanged(doubleSpend);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -917,7 +930,6 @@ public class Wallet implements Serializable {
|
||||||
input.connect(unspent, TransactionInput.ConnectMode.DISCONNECT_ON_CONFLICT);
|
input.connect(unspent, TransactionInput.ConnectMode.DISCONNECT_ON_CONFLICT);
|
||||||
// Inform the [tx] event listeners of the newly dead tx. This sets confidence type also.
|
// Inform the [tx] event listeners of the newly dead tx. This sets confidence type also.
|
||||||
connected.getConfidence().setOverridingTransaction(tx);
|
connected.getConfidence().setOverridingTransaction(tx);
|
||||||
invokeOnTransactionConfidenceChanged(connected);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// A pending transaction that tried to double spend our coins - we log and ignore it, because either
|
// A pending transaction that tried to double spend our coins - we log and ignore it, because either
|
||||||
|
@ -996,7 +1008,7 @@ public class Wallet implements Serializable {
|
||||||
updateForSpends(tx, false);
|
updateForSpends(tx, false);
|
||||||
// Add to the pending pool. It'll be moved out once we receive this transaction on the best chain.
|
// Add to the pending pool. It'll be moved out once we receive this transaction on the best chain.
|
||||||
log.info("->pending: {}", tx.getHashAsString());
|
log.info("->pending: {}", tx.getHashAsString());
|
||||||
pending.put(tx.getHash(), tx);
|
addWalletTransaction(Pool.PENDING, tx);
|
||||||
|
|
||||||
// Event listeners may re-enter so we cannot make assumptions about wallet state after this loop completes.
|
// Event listeners may re-enter so we cannot make assumptions about wallet state after this loop completes.
|
||||||
try {
|
try {
|
||||||
|
@ -1070,30 +1082,39 @@ public class Wallet implements Serializable {
|
||||||
* deserialization code, such as the {@link WalletProtobufSerializer} class. It isn't normally useful for
|
* deserialization code, such as the {@link WalletProtobufSerializer} class. It isn't normally useful for
|
||||||
* applications. It does not trigger auto saving.
|
* applications. It does not trigger auto saving.
|
||||||
*/
|
*/
|
||||||
public synchronized void addWalletTransaction(WalletTransaction wtx) {
|
public void addWalletTransaction(WalletTransaction wtx) {
|
||||||
switch (wtx.getPool()) {
|
addWalletTransaction(wtx.getPool(), wtx.getTransaction());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the given transaction to the given pools and registers a confidence change listener on it. Not to be used
|
||||||
|
* when moving txns between pools.
|
||||||
|
*/
|
||||||
|
private synchronized void addWalletTransaction(Pool pool, Transaction tx) {
|
||||||
|
switch (pool) {
|
||||||
case UNSPENT:
|
case UNSPENT:
|
||||||
unspent.put(wtx.getTransaction().getHash(), wtx.getTransaction());
|
unspent.put(tx.getHash(), tx);
|
||||||
break;
|
break;
|
||||||
case SPENT:
|
case SPENT:
|
||||||
spent.put(wtx.getTransaction().getHash(), wtx.getTransaction());
|
spent.put(tx.getHash(), tx);
|
||||||
break;
|
break;
|
||||||
case PENDING:
|
case PENDING:
|
||||||
pending.put(wtx.getTransaction().getHash(), wtx.getTransaction());
|
pending.put(tx.getHash(), tx);
|
||||||
break;
|
break;
|
||||||
case DEAD:
|
case DEAD:
|
||||||
dead.put(wtx.getTransaction().getHash(), wtx.getTransaction());
|
dead.put(tx.getHash(), tx);
|
||||||
break;
|
break;
|
||||||
case INACTIVE:
|
case INACTIVE:
|
||||||
inactive.put(wtx.getTransaction().getHash(), wtx.getTransaction());
|
inactive.put(tx.getHash(), tx);
|
||||||
break;
|
break;
|
||||||
case PENDING_INACTIVE:
|
case PENDING_INACTIVE:
|
||||||
pending.put(wtx.getTransaction().getHash(), wtx.getTransaction());
|
pending.put(tx.getHash(), tx);
|
||||||
inactive.put(wtx.getTransaction().getHash(), wtx.getTransaction());
|
inactive.put(tx.getHash(), tx);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new RuntimeException("Unknown wallet transaction type " + wtx.getPool());
|
throw new RuntimeException("Unknown wallet transaction type " + pool);
|
||||||
}
|
}
|
||||||
|
tx.getConfidence().addEventListener(txConfidenceListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1266,6 +1287,11 @@ public class Wallet implements Serializable {
|
||||||
return null; // Not enough money.
|
return null; // Not enough money.
|
||||||
SendResult result = new SendResult();
|
SendResult result = new SendResult();
|
||||||
result.tx = tx;
|
result.tx = tx;
|
||||||
|
// The tx has been committed to the pending pool by this point (via sendCoinsOffline -> commitTx), so it has
|
||||||
|
// a txConfidenceListener registered. Once the tx is broadcast the peers will update the memory pool with the
|
||||||
|
// count of seen peers, the memory pool will update the transaction confidence object, that will invoke the
|
||||||
|
// txConfidenceListener which will in turn invoke the wallets event listener onTransactionConfidenceChanged
|
||||||
|
// method.
|
||||||
result.broadcastComplete = peerGroup.broadcastTransaction(tx);
|
result.broadcastComplete = peerGroup.broadcastTransaction(tx);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -1744,7 +1770,6 @@ public class Wallet implements Serializable {
|
||||||
|
|
||||||
// Set transaction confidence to dead and notify listeners.
|
// Set transaction confidence to dead and notify listeners.
|
||||||
tx.getConfidence().setConfidenceType(ConfidenceType.DEAD);
|
tx.getConfidence().setConfidenceType(ConfidenceType.DEAD);
|
||||||
invokeOnTransactionConfidenceChanged(tx);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1892,7 +1917,6 @@ public class Wallet implements Serializable {
|
||||||
pending.remove(tx.getHash());
|
pending.remove(tx.getHash());
|
||||||
// This updates the tx confidence type automatically.
|
// This updates the tx confidence type automatically.
|
||||||
tx.getConfidence().setOverridingTransaction(replacement);
|
tx.getConfidence().setOverridingTransaction(replacement);
|
||||||
invokeOnTransactionConfidenceChanged(tx);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -303,11 +303,23 @@ public class PeerGroupTest extends TestWithNetworkConnections {
|
||||||
|
|
||||||
assertEquals(Utils.toNanoCoins(50, 0), wallet.getBalance());
|
assertEquals(Utils.toNanoCoins(50, 0), wallet.getBalance());
|
||||||
|
|
||||||
|
// Check that the wallet informs us of changes in confidence as the transaction ripples across the network.
|
||||||
|
final Transaction[] transactions = new Transaction[1];
|
||||||
|
wallet.addEventListener(new AbstractWalletEventListener() {
|
||||||
|
@Override
|
||||||
|
public void onTransactionConfidenceChanged(Wallet wallet, Transaction tx) {
|
||||||
|
transactions[0] = tx;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Now create a spend, and expect the announcement on p1.
|
// Now create a spend, and expect the announcement on p1.
|
||||||
Address dest = new ECKey().toAddress(params);
|
Address dest = new ECKey().toAddress(params);
|
||||||
Wallet.SendResult sendResult = wallet.sendCoins(peerGroup, dest, Utils.toNanoCoins(1, 0));
|
Wallet.SendResult sendResult = wallet.sendCoins(peerGroup, dest, Utils.toNanoCoins(1, 0));
|
||||||
assertNotNull(sendResult.tx);
|
assertNotNull(sendResult.tx);
|
||||||
assertFalse(sendResult.broadcastComplete.isDone());
|
assertFalse(sendResult.broadcastComplete.isDone());
|
||||||
|
assertEquals(transactions[0], sendResult.tx);
|
||||||
|
assertEquals(transactions[0].getConfidence().numBroadcastPeers(), 1);
|
||||||
|
transactions[0] = null;
|
||||||
Transaction t1 = (Transaction) outbound(p1);
|
Transaction t1 = (Transaction) outbound(p1);
|
||||||
assertNotNull(t1);
|
assertNotNull(t1);
|
||||||
// 49 BTC in change.
|
// 49 BTC in change.
|
||||||
|
@ -317,6 +329,8 @@ public class PeerGroupTest extends TestWithNetworkConnections {
|
||||||
inv.addTransaction(t1);
|
inv.addTransaction(t1);
|
||||||
inbound(p2, inv);
|
inbound(p2, inv);
|
||||||
assertTrue(sendResult.broadcastComplete.isDone());
|
assertTrue(sendResult.broadcastComplete.isDone());
|
||||||
|
assertEquals(transactions[0], sendResult.tx);
|
||||||
|
assertEquals(transactions[0].getConfidence().numBroadcastPeers(), 2);
|
||||||
// Confirm it.
|
// Confirm it.
|
||||||
Block b2 = TestUtils.createFakeBlock(params, blockStore, t1).block;
|
Block b2 = TestUtils.createFakeBlock(params, blockStore, t1).block;
|
||||||
inbound(p1, b2);
|
inbound(p1, b2);
|
||||||
|
|
Loading…
Add table
Reference in a new issue