From bbe133be88b943ec1de499fbcd457f4ae642a27d Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Sun, 18 Sep 2011 20:09:26 +0000 Subject: [PATCH] Make WalletEventListener an interface with a no-op implementation. Add an onChange() method to the default implementation that is called by the others, for cases where you don't care about what specifically changed, just that a change happened. --- .../core/AbstractWalletEventListener.java | 80 +++++++++++++++++++ .../bitcoin/core/WalletEventListener.java | 19 ++--- .../google/bitcoin/examples/PingService.java | 16 +--- .../bitcoin/examples/RefreshWallet.java | 11 +-- .../google/bitcoin/core/ChainSplitTests.java | 6 +- tests/com/google/bitcoin/core/WalletTest.java | 4 +- 6 files changed, 96 insertions(+), 40 deletions(-) create mode 100644 src/com/google/bitcoin/core/AbstractWalletEventListener.java diff --git a/src/com/google/bitcoin/core/AbstractWalletEventListener.java b/src/com/google/bitcoin/core/AbstractWalletEventListener.java new file mode 100644 index 00000000..6bc3f040 --- /dev/null +++ b/src/com/google/bitcoin/core/AbstractWalletEventListener.java @@ -0,0 +1,80 @@ +/** + * Copyright 2011 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.bitcoin.core; + +import java.math.BigInteger; + +/** + * Implementing a subclass WalletEventListener allows you to learn when the contents of the wallet changes due to + * receiving money or a block chain re-organize. Methods are called with the event listener object locked so your + * implementation does not have to be thread safe. The default method implementations simply call onChange(). + */ +public abstract class AbstractWalletEventListener implements WalletEventListener { + /** + * This is called on a Peer thread when a block is received that sends some coins to you. Note that this will + * also be called when downloading the block chain as the wallet balance catches up so if you don't want that + * register the event listener after the chain is downloaded. It's safe to use methods of wallet during the + * execution of this callback. + * + * @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. + */ + public void onCoinsReceived(Wallet wallet, Transaction tx, BigInteger prevBalance, BigInteger newBalance) { + onChange(); + } + + /** + * This is called on a Peer thread when a block is received that triggers a block chain re-organization.

+ * + * A re-organize means that the consensus (chain) of the network has diverged and now changed from what we + * believed it was previously. Usually this won't matter because the new consensus will include all our old + * transactions assuming we are playing by the rules. However it's theoretically possible for our balance to + * change in arbitrary ways, most likely, we could lose some money we thought we had.

+ * + * It is safe to use methods of wallet whilst inside this callback. + * + * TODO: Finish this interface. + */ + public void onReorganize() { + onChange(); + } + + + /** + * This is called on a Peer thread when a transaction becomes dead. 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.

+ * + * 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. + * + * @param deadTx The transaction that is newly dead. + * @param replacementTx The transaction that killed it. + */ + public void onDeadTransaction(Transaction deadTx, Transaction replacementTx) { + onChange(); + } + + /** + * Called by the other default method implementations when something (anything) changes in the wallet. + */ + public void onChange() { + } +} diff --git a/src/com/google/bitcoin/core/WalletEventListener.java b/src/com/google/bitcoin/core/WalletEventListener.java index 668eb57c..4c8276cc 100644 --- a/src/com/google/bitcoin/core/WalletEventListener.java +++ b/src/com/google/bitcoin/core/WalletEventListener.java @@ -18,14 +18,13 @@ package com.google.bitcoin.core; import java.math.BigInteger; -// TODO: Make this be an interface with a convenience abstract impl. - /** - * Implementing a subclass WalletEventListener allows you to learn when the contents of the wallet changes due to + * Implementing WalletEventListener allows you to learn when the contents of the wallet changes due to * receiving money or a block chain re-organize. Methods are called with the event listener object locked so your - * implementation does not have to be thread safe. The default method implementations do nothing. + * implementation does not have to be thread safe. It may be convenient to derive from + * {@link AbstractWalletEventListener} instead. */ -public abstract class WalletEventListener { +public interface WalletEventListener { /** * This is called on a Peer thread when a block is received that sends some coins to you. Note that this will * also be called when downloading the block chain as the wallet balance catches up so if you don't want that @@ -37,8 +36,7 @@ public abstract class WalletEventListener { * @param prevBalance Balance before the coins were received. * @param newBalance Current balance of the wallet. */ - public void onCoinsReceived(Wallet wallet, Transaction tx, BigInteger prevBalance, BigInteger newBalance) { - } + void onCoinsReceived(Wallet wallet, Transaction tx, BigInteger prevBalance, BigInteger newBalance); /** * This is called on a Peer thread when a block is received that triggers a block chain re-organization.

@@ -52,9 +50,7 @@ public abstract class WalletEventListener { * * TODO: Finish this interface. */ - public void onReorganize() { - } - + void onReorganize(); /** * This is called on a Peer thread when a transaction becomes dead. A dead transaction is one that has @@ -68,6 +64,5 @@ public abstract class WalletEventListener { * @param deadTx The transaction that is newly dead. * @param replacementTx The transaction that killed it. */ - public void onDeadTransaction(Transaction deadTx, Transaction replacementTx) { - } + void onDeadTransaction(Transaction deadTx, Transaction replacementTx); } diff --git a/src/com/google/bitcoin/examples/PingService.java b/src/com/google/bitcoin/examples/PingService.java index 68e5cca6..99df1cd1 100644 --- a/src/com/google/bitcoin/examples/PingService.java +++ b/src/com/google/bitcoin/examples/PingService.java @@ -16,19 +16,7 @@ package com.google.bitcoin.examples; -import com.google.bitcoin.core.Address; -import com.google.bitcoin.core.BlockChain; -import com.google.bitcoin.core.DownloadListener; -import com.google.bitcoin.core.ECKey; -import com.google.bitcoin.core.NetworkParameters; -import com.google.bitcoin.core.PeerAddress; -import com.google.bitcoin.core.PeerGroup; -import com.google.bitcoin.core.ScriptException; -import com.google.bitcoin.core.Transaction; -import com.google.bitcoin.core.TransactionInput; -import com.google.bitcoin.core.Utils; -import com.google.bitcoin.core.Wallet; -import com.google.bitcoin.core.WalletEventListener; +import com.google.bitcoin.core.*; import com.google.bitcoin.store.BlockStore; import com.google.bitcoin.store.BoundedOverheadBlockStore; @@ -94,7 +82,7 @@ public class PingService { peerGroup.start(); // We want to know when the balance changes. - wallet.addEventListener(new WalletEventListener() { + wallet.addEventListener(new AbstractWalletEventListener() { @Override public void onCoinsReceived(Wallet w, Transaction tx, BigInteger prevBalance, BigInteger newBalance) { // Running on a peer thread. diff --git a/src/com/google/bitcoin/examples/RefreshWallet.java b/src/com/google/bitcoin/examples/RefreshWallet.java index d31c35d9..fb4ed08e 100644 --- a/src/com/google/bitcoin/examples/RefreshWallet.java +++ b/src/com/google/bitcoin/examples/RefreshWallet.java @@ -16,14 +16,7 @@ package com.google.bitcoin.examples; -import com.google.bitcoin.core.BlockChain; -import com.google.bitcoin.core.DownloadListener; -import com.google.bitcoin.core.NetworkParameters; -import com.google.bitcoin.core.PeerAddress; -import com.google.bitcoin.core.PeerGroup; -import com.google.bitcoin.core.Transaction; -import com.google.bitcoin.core.Wallet; -import com.google.bitcoin.core.WalletEventListener; +import com.google.bitcoin.core.*; import com.google.bitcoin.store.BlockStore; import com.google.bitcoin.store.MemoryBlockStore; @@ -49,7 +42,7 @@ public class RefreshWallet { peerGroup.addAddress(new PeerAddress(InetAddress.getLocalHost())); peerGroup.start(); - wallet.addEventListener(new WalletEventListener() { + wallet.addEventListener(new AbstractWalletEventListener() { @Override public void onCoinsReceived(Wallet w, Transaction tx, BigInteger prevBalance, BigInteger newBalance) { System.out.println("\nReceived tx " + tx.getHashAsString()); diff --git a/tests/com/google/bitcoin/core/ChainSplitTests.java b/tests/com/google/bitcoin/core/ChainSplitTests.java index eba11ebe..5b4ff4e0 100644 --- a/tests/com/google/bitcoin/core/ChainSplitTests.java +++ b/tests/com/google/bitcoin/core/ChainSplitTests.java @@ -51,7 +51,7 @@ public class ChainSplitTests { // TODO: Change this test to not use coinbase transactions as they are special (maturity rules). final boolean[] reorgHappened = new boolean[1]; reorgHappened[0] = false; - wallet.addEventListener(new WalletEventListener() { + wallet.addEventListener(new AbstractWalletEventListener() { @Override public void onReorganize() { reorgHappened[0] = true; @@ -185,7 +185,7 @@ public class ChainSplitTests { // double spend on the new best chain. final boolean[] eventCalled = new boolean[1]; - wallet.addEventListener(new WalletEventListener() { + wallet.addEventListener(new AbstractWalletEventListener() { @Override public void onDeadTransaction(Transaction deadTx, Transaction replacementTx) { eventCalled[0] = true; @@ -225,7 +225,7 @@ public class ChainSplitTests { final Transaction[] eventDead = new Transaction[1]; final Transaction[] eventReplacement = new Transaction[1]; - wallet.addEventListener(new WalletEventListener() { + wallet.addEventListener(new AbstractWalletEventListener() { @Override public void onDeadTransaction(Transaction deadTx, Transaction replacementTx) { eventDead[0] = deadTx; diff --git a/tests/com/google/bitcoin/core/WalletTest.java b/tests/com/google/bitcoin/core/WalletTest.java index 8eb774d9..42302c90 100644 --- a/tests/com/google/bitcoin/core/WalletTest.java +++ b/tests/com/google/bitcoin/core/WalletTest.java @@ -101,7 +101,7 @@ public class WalletTest { public void listeners() throws Exception { final Transaction fakeTx = createFakeTx(params, Utils.toNanoCoins(1, 0), myAddress); final boolean[] didRun = new boolean[1]; - WalletEventListener listener = new WalletEventListener() { + WalletEventListener listener = new AbstractWalletEventListener() { public void onCoinsReceived(Wallet w, Transaction tx, BigInteger prevBalance, BigInteger newBalance) { assertTrue(prevBalance.equals(BigInteger.ZERO)); assertTrue(newBalance.equals(Utils.toNanoCoins(1, 0))); @@ -249,7 +249,7 @@ public class WalletTest { // isn't tested because today BitCoinJ only learns about such transactions when they appear in the chain. final Transaction[] eventDead = new Transaction[1]; final Transaction[] eventReplacement = new Transaction[1]; - wallet.addEventListener(new WalletEventListener() { + wallet.addEventListener(new AbstractWalletEventListener() { @Override public void onDeadTransaction(Transaction deadTx, Transaction replacementTx) { eventDead[0] = deadTx;