From 1f96f850e0197ee607ec5a6dff84f3fc677ca107 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Fri, 25 May 2012 16:05:02 +0200 Subject: [PATCH] Automatically set up fast catchup time on a PeerGroup when wallets are added. Resolves issue 183. --- .../core/AbstractWalletEventListener.java | 5 +++ .../com/google/bitcoin/core/PeerGroup.java | 39 +++++++++++++++++-- .../java/com/google/bitcoin/core/Wallet.java | 8 +++- .../bitcoin/core/WalletEventListener.java | 6 +++ .../google/bitcoin/core/PeerGroupTest.java | 20 ++++++++++ 5 files changed, 74 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/com/google/bitcoin/core/AbstractWalletEventListener.java b/core/src/main/java/com/google/bitcoin/core/AbstractWalletEventListener.java index 859f7deb..4d48b337 100644 --- a/core/src/main/java/com/google/bitcoin/core/AbstractWalletEventListener.java +++ b/core/src/main/java/com/google/bitcoin/core/AbstractWalletEventListener.java @@ -91,9 +91,14 @@ public abstract class AbstractWalletEventListener implements WalletEventListener onChange(); } + public void onKeyAdded(ECKey key) { + onChange(); + } + /** * Called by the other default method implementations when something (anything) changes in the wallet. */ public void onChange() { } + } diff --git a/core/src/main/java/com/google/bitcoin/core/PeerGroup.java b/core/src/main/java/com/google/bitcoin/core/PeerGroup.java index 8bb682d6..25a5f4ec 100644 --- a/core/src/main/java/com/google/bitcoin/core/PeerGroup.java +++ b/core/src/main/java/com/google/bitcoin/core/PeerGroup.java @@ -339,18 +339,41 @@ public class PeerGroup { } /** - * Link the given wallet to this PeerGroup. This is used for two purposes: + *

Link the given wallet to this PeerGroup. This is used for three purposes:

*
    *
  1. So the wallet receives broadcast transactions.
  2. *
  3. Announcing pending transactions that didn't get into the chain yet to our peers.
  4. + *
  5. Set the fast catchup time using {@link PeerGroup#setFastCatchupTimeSecs(long)}, to optimize chain + * download.
  6. *
+ *

Note that this should be done before chain download commences because if you add a wallet with keys earlier + * than the current chain head, the relevant parts of the chain won't be redownloaded for you.

*/ public synchronized void addWallet(Wallet wallet) { - if (wallet == null) - throw new IllegalArgumentException("wallet is null"); + Preconditions.checkNotNull(wallet); wallets.add(wallet); addEventListener(wallet.getPeerEventListener()); announcePendingWalletTransactions(Collections.singletonList(wallet), peers); + + // Don't bother downloading block bodies before the oldest keys in all our wallets. Make sure we recalculate + // if a key is added. Of course, by then we may have downloaded the chain already. Ideally adding keys would + // automatically rewind the block chain and redownload the blocks to find transactions relevant to those keys, + // all transparently and in the background. But we are a long way from that yet. + wallet.addEventListener(new AbstractWalletEventListener() { + @Override + public void onKeyAdded(ECKey key) { + recalculateFastCatchupTime(); + } + }); + recalculateFastCatchupTime(); + } + + private synchronized void recalculateFastCatchupTime() { + long earliestKeyTime = Long.MAX_VALUE; + for (Wallet w : wallets) { + earliestKeyTime = Math.min(earliestKeyTime, w.getEarliestKeyCreationTime()); + } + setFastCatchupTimeSecs(earliestKeyTime); } /** @@ -738,6 +761,16 @@ public class PeerGroup { } } + /** + * Returns the current fast catchup time. The contents of blocks before this time won't be downloaded as they + * cannot contain any interesting transactions. If you use {@link PeerGroup#addWallet(Wallet)} this just returns + * the min of the wallets earliest key times. + * @return a time in seconds since the epoch + */ + public synchronized long getFastCatchupTimeSecs() { + return fastCatchupTimeSecs; + } + protected synchronized void handlePeerDeath(final Peer peer) { if (!isRunning()) { log.info("Peer death while shutting down"); diff --git a/core/src/main/java/com/google/bitcoin/core/Wallet.java b/core/src/main/java/com/google/bitcoin/core/Wallet.java index 595b2a2e..b9fcf9bc 100644 --- a/core/src/main/java/com/google/bitcoin/core/Wallet.java +++ b/core/src/main/java/com/google/bitcoin/core/Wallet.java @@ -1194,9 +1194,15 @@ public class Wallet implements Serializable { /** * Adds the given ECKey to the wallet. There is currently no way to delete keys (that would result in coin loss). */ - public synchronized void addKey(ECKey key) { + public synchronized void addKey(final ECKey key) { checkArgument(!keychain.contains(key), "Key already present"); keychain.add(key); + EventListenerInvoker.invoke(eventListeners, new EventListenerInvoker() { + @Override + public void invoke(WalletEventListener listener) { + listener.onKeyAdded(key); + } + }); } /** diff --git a/core/src/main/java/com/google/bitcoin/core/WalletEventListener.java b/core/src/main/java/com/google/bitcoin/core/WalletEventListener.java index e62540f4..1e46421c 100644 --- a/core/src/main/java/com/google/bitcoin/core/WalletEventListener.java +++ b/core/src/main/java/com/google/bitcoin/core/WalletEventListener.java @@ -99,4 +99,10 @@ public interface WalletEventListener { * @param tx */ void onTransactionConfidenceChanged(Wallet wallet, Transaction tx); + + /** + * Called by the {@link Wallet#addKey(ECKey)} method on whatever the calling thread was. + * @param key + */ + void onKeyAdded(ECKey key); } diff --git a/core/src/test/java/com/google/bitcoin/core/PeerGroupTest.java b/core/src/test/java/com/google/bitcoin/core/PeerGroupTest.java index e3f0509d..efd5b030 100644 --- a/core/src/test/java/com/google/bitcoin/core/PeerGroupTest.java +++ b/core/src/test/java/com/google/bitcoin/core/PeerGroupTest.java @@ -24,6 +24,7 @@ import org.junit.Test; import java.io.IOException; import java.math.BigInteger; import java.net.InetSocketAddress; +import java.util.Date; import java.util.HashSet; import java.util.Set; import java.util.concurrent.BlockingQueue; @@ -306,6 +307,25 @@ public class PeerGroupTest extends TestWithNetworkConnections { assertTrue(n3.outbound() instanceof InventoryMessage); peerGroup.stop(); } + + @Test + public void testWalletCatchupTime() throws Exception { + // Check the fast catchup time was initialized to something around the current runtime. The wallet was + // already added to the peer in setup. + long time = new Date().getTime() / 1000; + assertTrue(peerGroup.getFastCatchupTimeSecs() > time - 10000); + Wallet w2 = new Wallet(params); + ECKey key1 = new ECKey(); + key1.setCreationTimeSeconds(time - 86400); // One day ago. + w2.addKey(key1); + peerGroup.addWallet(w2); + assertEquals(peerGroup.getFastCatchupTimeSecs(), time - 86400); + // Adding a key to the wallet should update the fast catchup time. + ECKey key2 = new ECKey(); + key2.setCreationTimeSeconds(time - 100000); + w2.addKey(key2); + assertEquals(peerGroup.getFastCatchupTimeSecs(), time - 100000); + } private void disconnectAndWait(MockNetworkConnection conn) throws IOException, InterruptedException { conn.disconnect();