Invoke onCoinsSent() when receiving a pending transaction that spends the wallets coins.

This commit is contained in:
Mike Hearn 2012-02-02 14:49:46 +01:00
parent cf76cf064f
commit 6b2275890a
3 changed files with 37 additions and 33 deletions

View File

@ -281,9 +281,13 @@ public class Wallet implements Serializable {
return;
}
BigInteger value = tx.getValueSentToMe(this);
log.info("Received a pending transaction {} that sends us {} BTC", tx.getHashAsString(),
Utils.bitcoinValueToFriendlyString(value));
BigInteger valueSentToMe = tx.getValueSentToMe(this);
BigInteger valueSentFromMe = tx.getValueSentFromMe(this);
if (log.isInfoEnabled()) {
log.info(String.format("Received a pending transaction %s that spends %s BTC and sends us %s BTC", tx.getHashAsString(),
Utils.bitcoinValueToFriendlyString(valueSentFromMe),
Utils.bitcoinValueToFriendlyString(valueSentToMe)));
}
// Mark the tx as having been seen but is not yet in the chain. This will normally have been done already by
// the Peer before we got to this point, but in some cases (unit tests, other sources of transactions) it may
@ -293,15 +297,19 @@ public class Wallet implements Serializable {
currentConfidence == TransactionConfidence.ConfidenceType.NOT_SEEN_IN_CHAIN : currentConfidence;
tx.getConfidence().setConfidenceType(TransactionConfidence.ConfidenceType.NOT_SEEN_IN_CHAIN);
BigInteger balance = getBalance();
// 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
// timestamp on the transaction.
commitTx(tx);
// Event listeners may re-enter so we cannot make assumptions about wallet state after this loop completes.
BigInteger balance = getBalance();
BigInteger newBalance = balance.add(value);
invokeOnCoinsReceived(tx, balance, newBalance);
BigInteger newBalance = balance.add(valueSentToMe).subtract(valueSentFromMe);
if (valueSentToMe.compareTo(BigInteger.ZERO) > 0)
invokeOnCoinsReceived(tx, balance, newBalance);
if (valueSentFromMe.compareTo(BigInteger.ZERO) > 0)
invokeOnCoinsSent(tx, balance, newBalance);
}
// Boilerplate that allows event listeners to delete themselves during execution, and auto locks the listener.

View File

@ -39,7 +39,7 @@ public interface WalletEventListener {
* @param wallet The wallet object that received the coins
* @param tx The transaction which sent us the coins.
* @param prevBalance Balance before the coins were received.
* @param newBalance Current balance of the wallet.
* @param newBalance Current balance of the wallet. This is the 'estimated' balance.
*/
void onCoinsReceived(Wallet wallet, Transaction tx, BigInteger prevBalance, BigInteger newBalance);
@ -57,7 +57,7 @@ public interface WalletEventListener {
* @param wallet The wallet object that this callback relates to (that sent the coins).
* @param tx The transaction that sent the coins to someone else.
* @param prevBalance The wallets balance before this transaction was seen.
* @param newBalance The wallets balance after this transaction was seen (should be less than prevBalance).
* @param newBalance The wallets balance after this transaction was seen. This is the 'estimated' balance.
*/
void onCoinsSent(Wallet wallet, Transaction tx, BigInteger prevBalance, BigInteger newBalance);

View File

@ -101,25 +101,6 @@ public class WalletTest {
assertEquals(v1, wallet.getBalance());
}
@Test
public void listeners() throws Exception {
final Transaction fakeTx = createFakeTx(params, Utils.toNanoCoins(1, 0), myAddress);
final boolean[] didRun = new boolean[1];
WalletEventListener listener = new AbstractWalletEventListener() {
@Override
public void onCoinsReceived(Wallet w, Transaction tx, BigInteger prevBalance, BigInteger newBalance) {
assertTrue(prevBalance.equals(BigInteger.ZERO));
assertTrue(newBalance.equals(Utils.toNanoCoins(1, 0)));
assertEquals(tx, fakeTx); // Same object.
assertEquals(w, wallet); // Same object.
didRun[0] = true;
}
};
wallet.addEventListener(listener);
wallet.receiveFromBlock(fakeTx, null, BlockChain.NewBlockType.BEST_CHAIN);
assertTrue(didRun[0]);
}
@Test
public void balance() throws Exception {
// Receive 5 coins then half a coin.
@ -394,19 +375,34 @@ public class WalletTest {
@Test
public void pending2() throws Exception {
// Check that if we receive a pending tx we did not send, it updates our spent flags correctly.
final Transaction txn[] = new Transaction[1];
final BigInteger bigints[] = new BigInteger[2];
wallet.addEventListener(new AbstractWalletEventListener() {
@Override
public void onCoinsSent(Wallet wallet, Transaction tx, BigInteger prevBalance, BigInteger newBalance) {
txn[0] = tx;
bigints[0] = prevBalance;
bigints[1] = newBalance;
}
});
// Receive some coins.
BigInteger nanos = Utils.toNanoCoins(1, 0);
Transaction t1 = createFakeTx(params, nanos, myAddress);
StoredBlock b1 = createFakeBlock(params, blockStore, t1).storedBlock;
wallet.receiveFromBlock(t1, b1, BlockChain.NewBlockType.BEST_CHAIN);
assertEquals(nanos, wallet.getBalance());
// Create a spend with them, but don't commit it (ie it's from somewhere else but using our keys).
Transaction t2 = wallet.createSend(new ECKey().toAddress(params), nanos);
// Create a spend with them, but don't commit it (ie it's from somewhere else but using our keys). This TX
// will have change as we don't spend our entire balance.
BigInteger halfNanos = Utils.toNanoCoins(0, 50);
Transaction t2 = wallet.createSend(new ECKey().toAddress(params), halfNanos);
// Now receive it as pending.
wallet.receivePending(t2);
// Our balance is now zero.
assertEquals(BigInteger.ZERO, wallet.getBalance());
// We received an onCoinsSent() callback.
assertEquals(t2, txn[0]);
assertEquals(nanos, bigints[0]);
assertEquals(halfNanos, bigints[1]);
// Our balance is now 0.50 BTC
assertEquals(halfNanos, wallet.getBalance(Wallet.BalanceType.ESTIMATED));
}
@Test
@ -517,7 +513,7 @@ public class WalletTest {
// Test migration from appearsIn to appearsInHashes
Transaction tx1 = createFakeTx(params, Utils.toNanoCoins(1, 0), myAddress);
StoredBlock b1 = createFakeBlock(params, blockStore, tx1).storedBlock;
tx1 .appearsIn = new HashSet<StoredBlock>();
tx1.appearsIn = new HashSet<StoredBlock>();
tx1.appearsIn.add(b1);
assertEquals(1, tx1.getAppearsInHashes().size());
assertTrue(tx1.getAppearsInHashes().contains(b1.getHeader().getHash()));