From 0b5b1013437d025a3b39ff606bbc9d193f336bcc Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Wed, 7 Jan 2015 16:43:45 +0100 Subject: [PATCH 01/44] Abstract out multiplexing from DnsDiscovery. --- .../bitcoinj/net/discovery/DnsDiscovery.java | 99 ++++++----------- .../net/discovery/MultiplexingDiscovery.java | 102 ++++++++++++++++++ 2 files changed, 137 insertions(+), 64 deletions(-) create mode 100644 core/src/main/java/org/bitcoinj/net/discovery/MultiplexingDiscovery.java diff --git a/core/src/main/java/org/bitcoinj/net/discovery/DnsDiscovery.java b/core/src/main/java/org/bitcoinj/net/discovery/DnsDiscovery.java index 3af85cb4..c8d0998e 100644 --- a/core/src/main/java/org/bitcoinj/net/discovery/DnsDiscovery.java +++ b/core/src/main/java/org/bitcoinj/net/discovery/DnsDiscovery.java @@ -18,13 +18,10 @@ package org.bitcoinj.net.discovery; import org.bitcoinj.core.NetworkParameters; -import com.google.common.collect.Lists; -import org.bitcoinj.utils.DaemonThreadFactory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.net.InetAddress; import java.net.InetSocketAddress; +import java.net.UnknownHostException; import java.util.*; import java.util.concurrent.*; @@ -39,12 +36,7 @@ import java.util.concurrent.*; * will return up to 30 random peers from the set of those returned within the timeout period. If you want more peers * to connect to, you need to discover them via other means (like addr broadcasts).

*/ -public class DnsDiscovery implements PeerDiscovery { - private static final Logger log = LoggerFactory.getLogger(DnsDiscovery.class); - - private final String[] dnsSeeds; - private final NetworkParameters netParams; - +public class DnsDiscovery extends MultiplexingDiscovery { /** * Supports finding peers through DNS A records. Community run DNS entry points will be used. * @@ -58,65 +50,44 @@ public class DnsDiscovery implements PeerDiscovery { * Supports finding peers through DNS A records. * * @param dnsSeeds Host names to be examined for seed addresses. - * @param netParams Network parameters to be used for port information. + * @param params Network parameters to be used for port information. */ - public DnsDiscovery(String[] dnsSeeds, NetworkParameters netParams) { - this.dnsSeeds = dnsSeeds; - this.netParams = netParams; + public DnsDiscovery(String[] dnsSeeds, NetworkParameters params) { + super(params, buildDiscoveries(params, dnsSeeds)); } - @Override - public InetSocketAddress[] getPeers(long timeoutValue, TimeUnit timeoutUnit) throws PeerDiscoveryException { - if (dnsSeeds == null || dnsSeeds.length == 0) - throw new PeerDiscoveryException("No DNS seeds configured; unable to find any peers"); + private static List buildDiscoveries(NetworkParameters params, String[] seeds) { + List discoveries = new ArrayList(seeds.length); + for (String seed : seeds) + discoveries.add(new DnsSeedDiscovery(params, seed)); + return discoveries; + } - // Java doesn't have an async DNS API so we have to do all lookups in a thread pool, as sometimes seeds go - // hard down and it takes ages to give up and move on. - ExecutorService threadPool = Executors.newFixedThreadPool(dnsSeeds.length, new DaemonThreadFactory()); - try { - List> tasks = Lists.newArrayList(); - for (final String seed : dnsSeeds) { - tasks.add(new Callable() { - @Override - public InetAddress[] call() throws Exception { - return InetAddress.getAllByName(seed); - } - }); + /** Implements discovery from a single DNS host. */ + public static class DnsSeedDiscovery implements PeerDiscovery { + private final String hostname; + private final NetworkParameters params; + + public DnsSeedDiscovery(NetworkParameters params, String hostname) { + this.hostname = hostname; + this.params = params; + } + + @Override + public InetSocketAddress[] getPeers(long timeoutValue, TimeUnit timeoutUnit) throws PeerDiscoveryException { + try { + InetAddress[] response = InetAddress.getAllByName(hostname); + InetSocketAddress[] result = new InetSocketAddress[response.length]; + for (int i = 0; i < response.length; i++) + result[i] = new InetSocketAddress(response[i], params.getPort()); + return result; + } catch (UnknownHostException e) { + throw new PeerDiscoveryException(e); } - final List> futures = threadPool.invokeAll(tasks, timeoutValue, timeoutUnit); - ArrayList addrs = Lists.newArrayList(); - for (int i = 0; i < futures.size(); i++) { - Future future = futures.get(i); - if (future.isCancelled()) { - log.warn("DNS seed {}: timed out", dnsSeeds[i]); - continue; // Timed out. - } - final InetAddress[] inetAddresses; - try { - inetAddresses = future.get(); - log.info("DNS seed {}: got {} peers", dnsSeeds[i], inetAddresses.length); - } catch (ExecutionException e) { - log.error("DNS seed {}: failed to look up: {}", dnsSeeds[i], e.getMessage()); - continue; - } - for (InetAddress addr : inetAddresses) { - addrs.add(new InetSocketAddress(addr, netParams.getPort())); - } - } - if (addrs.size() == 0) - throw new PeerDiscoveryException("Unable to find any peers via DNS"); - Collections.shuffle(addrs); - threadPool.shutdownNow(); - return addrs.toArray(new InetSocketAddress[addrs.size()]); - } catch (InterruptedException e) { - throw new PeerDiscoveryException(e); - } finally { - threadPool.shutdown(); + } + + @Override + public void shutdown() { } } - - /** We don't have a way to abort a DNS lookup, so this does nothing */ - @Override - public void shutdown() { - } } diff --git a/core/src/main/java/org/bitcoinj/net/discovery/MultiplexingDiscovery.java b/core/src/main/java/org/bitcoinj/net/discovery/MultiplexingDiscovery.java new file mode 100644 index 00000000..f65a4365 --- /dev/null +++ b/core/src/main/java/org/bitcoinj/net/discovery/MultiplexingDiscovery.java @@ -0,0 +1,102 @@ +/** + * Copyright 2014 Mike Hearn + * + * 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 org.bitcoinj.net.discovery; + +import com.google.common.collect.Lists; +import org.bitcoinj.core.NetworkParameters; +import org.bitcoinj.utils.DaemonThreadFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.*; + +import static com.google.common.base.Preconditions.checkArgument; + +/** + * MultiplexingDiscovery queries multiple PeerDiscovery objects, shuffles their responses and then returns the results, + * thus selecting randomly between them and reducing the influence of any particular seed. Any that don't respond + * within the timeout are ignored. Backends are queried in parallel. Backends may block + */ +public class MultiplexingDiscovery implements PeerDiscovery { + private static final Logger log = LoggerFactory.getLogger(MultiplexingDiscovery.class); + + protected final List seeds; + protected final NetworkParameters netParams; + private volatile ExecutorService vThreadPool; + + /** + * Will query the given seeds in parallel before producing a merged response. + */ + public MultiplexingDiscovery(NetworkParameters params, List seeds) { + checkArgument(!seeds.isEmpty()); + this.netParams = params; + this.seeds = seeds; + } + + @Override + public InetSocketAddress[] getPeers(final long timeoutValue, final TimeUnit timeoutUnit) throws PeerDiscoveryException { + vThreadPool = Executors.newFixedThreadPool(seeds.size(), new DaemonThreadFactory()); + try { + List> tasks = Lists.newArrayList(); + for (final PeerDiscovery seed : seeds) { + tasks.add(new Callable() { + @Override + public InetSocketAddress[] call() throws Exception { + return seed.getPeers(timeoutValue, timeoutUnit); + } + }); + } + final List> futures = vThreadPool.invokeAll(tasks, timeoutValue, timeoutUnit); + ArrayList addrs = Lists.newArrayList(); + for (int i = 0; i < futures.size(); i++) { + Future future = futures.get(i); + if (future.isCancelled()) { + log.warn("Seed {}: timed out", seeds.get(i)); + continue; // Timed out. + } + final InetSocketAddress[] inetAddresses; + try { + inetAddresses = future.get(); + } catch (ExecutionException e) { + log.warn("Seed {}: failed to look up: {}", seeds.get(i), e.getMessage()); + continue; + } + Collections.addAll(addrs, inetAddresses); + } + if (addrs.size() == 0) + throw new PeerDiscoveryException("No peer discovery returned any results: check internet connection?"); + Collections.shuffle(addrs); + vThreadPool.shutdownNow(); + return addrs.toArray(new InetSocketAddress[addrs.size()]); + } catch (InterruptedException e) { + throw new PeerDiscoveryException(e); + } finally { + vThreadPool.shutdown(); + } + } + + @Override + public void shutdown() { + ExecutorService tp = vThreadPool; + if (tp != null) + tp.shutdown(); + } +} From e09e3830072a28ec8331086143735ad27206442b Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Wed, 7 Jan 2015 16:44:10 +0100 Subject: [PATCH 02/44] Give unit test params a payment protocol ID. --- core/src/main/java/org/bitcoinj/core/NetworkParameters.java | 4 ++++ core/src/main/java/org/bitcoinj/params/UnitTestParams.java | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/bitcoinj/core/NetworkParameters.java b/core/src/main/java/org/bitcoinj/core/NetworkParameters.java index a1486d0e..7db7f772 100644 --- a/core/src/main/java/org/bitcoinj/core/NetworkParameters.java +++ b/core/src/main/java/org/bitcoinj/core/NetworkParameters.java @@ -62,6 +62,8 @@ public abstract class NetworkParameters implements Serializable { public static final String PAYMENT_PROTOCOL_ID_MAINNET = "main"; /** The string used by the payment protocol to represent the test net. */ public static final String PAYMENT_PROTOCOL_ID_TESTNET = "test"; + /** The string used by the payment protocol to represent unit testing (note that this is non-standard). */ + public static final String PAYMENT_PROTOCOL_ID_UNIT_TESTS = "unittest"; // TODO: Seed nodes should be here as well. @@ -224,6 +226,8 @@ public abstract class NetworkParameters implements Serializable { return MainNetParams.get(); } else if (pmtProtocolId.equals(PAYMENT_PROTOCOL_ID_TESTNET)) { return TestNet3Params.get(); + } else if (pmtProtocolId.equals(PAYMENT_PROTOCOL_ID_UNIT_TESTS)) { + return UnitTestParams.get(); } else { return null; } diff --git a/core/src/main/java/org/bitcoinj/params/UnitTestParams.java b/core/src/main/java/org/bitcoinj/params/UnitTestParams.java index 66696a77..41bee1ea 100644 --- a/core/src/main/java/org/bitcoinj/params/UnitTestParams.java +++ b/core/src/main/java/org/bitcoinj/params/UnitTestParams.java @@ -58,6 +58,6 @@ public class UnitTestParams extends NetworkParameters { @Override public String getPaymentProtocolId() { - return null; + return "unittest"; } } From 57bbb9152b0e60c010e657712293ed90407a9f8f Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Thu, 8 Jan 2015 18:35:11 +0100 Subject: [PATCH 03/44] Give regtest a payment protocol ID too. --- core/src/main/java/org/bitcoinj/core/NetworkParameters.java | 3 +++ core/src/main/java/org/bitcoinj/params/RegTestParams.java | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/bitcoinj/core/NetworkParameters.java b/core/src/main/java/org/bitcoinj/core/NetworkParameters.java index 7db7f772..eee374e7 100644 --- a/core/src/main/java/org/bitcoinj/core/NetworkParameters.java +++ b/core/src/main/java/org/bitcoinj/core/NetworkParameters.java @@ -64,6 +64,7 @@ public abstract class NetworkParameters implements Serializable { public static final String PAYMENT_PROTOCOL_ID_TESTNET = "test"; /** The string used by the payment protocol to represent unit testing (note that this is non-standard). */ public static final String PAYMENT_PROTOCOL_ID_UNIT_TESTS = "unittest"; + public static final String PAYMENT_PROTOCOL_ID_REGTEST = "regtest"; // TODO: Seed nodes should be here as well. @@ -228,6 +229,8 @@ public abstract class NetworkParameters implements Serializable { return TestNet3Params.get(); } else if (pmtProtocolId.equals(PAYMENT_PROTOCOL_ID_UNIT_TESTS)) { return UnitTestParams.get(); + } else if (pmtProtocolId.equals(PAYMENT_PROTOCOL_ID_REGTEST)) { + return RegTestParams.get(); } else { return null; } diff --git a/core/src/main/java/org/bitcoinj/params/RegTestParams.java b/core/src/main/java/org/bitcoinj/params/RegTestParams.java index eeb460ac..c77cf723 100644 --- a/core/src/main/java/org/bitcoinj/params/RegTestParams.java +++ b/core/src/main/java/org/bitcoinj/params/RegTestParams.java @@ -68,6 +68,6 @@ public class RegTestParams extends TestNet2Params { @Override public String getPaymentProtocolId() { - return null; + return PAYMENT_PROTOCOL_ID_REGTEST; } } From 59c1f23931047cee43085c6cf027c466cf50fe13 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Thu, 8 Jan 2015 18:35:19 +0100 Subject: [PATCH 04/44] Adjust getutxo min protocol version. --- core/src/main/java/org/bitcoinj/core/GetUTXOsMessage.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/bitcoinj/core/GetUTXOsMessage.java b/core/src/main/java/org/bitcoinj/core/GetUTXOsMessage.java index 08f5c9f3..0b722fb1 100644 --- a/core/src/main/java/org/bitcoinj/core/GetUTXOsMessage.java +++ b/core/src/main/java/org/bitcoinj/core/GetUTXOsMessage.java @@ -23,7 +23,7 @@ import java.io.OutputStream; import java.util.List; public class GetUTXOsMessage extends Message { - public static final int MIN_PROTOCOL_VERSION = 70003; + public static final int MIN_PROTOCOL_VERSION = 70002; private boolean includeMempool; private ImmutableList outPoints; From b984124ce29f25e9f62bbac6c4a1576ffbcfd5d5 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Fri, 9 Jan 2015 15:17:28 +0100 Subject: [PATCH 05/44] Add javadocs to GetUTXOsMessage and add the service mask required for the feature. --- .../src/main/java/org/bitcoinj/core/GetUTXOsMessage.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/core/src/main/java/org/bitcoinj/core/GetUTXOsMessage.java b/core/src/main/java/org/bitcoinj/core/GetUTXOsMessage.java index 0b722fb1..a887a492 100644 --- a/core/src/main/java/org/bitcoinj/core/GetUTXOsMessage.java +++ b/core/src/main/java/org/bitcoinj/core/GetUTXOsMessage.java @@ -22,8 +22,17 @@ import java.io.IOException; import java.io.OutputStream; import java.util.List; +/** + * This command is supported only by Bitcoin XT nodes, which + * advertise themselves using the second service bit flag. It requests a query of the UTXO set keyed by a set of + * outpoints (i.e. tx hash and output index). The result contains a bitmap of spentness flags, and the contents of + * the associated outputs if they were found. The results aren't authenticated by anything, so the peer could lie, + * or a man in the middle could swap out its answer for something else. + */ public class GetUTXOsMessage extends Message { public static final int MIN_PROTOCOL_VERSION = 70002; + /** Bitmask of service flags required for a node to support this command (0x3) */ + public static final int SERVICE_FLAGS_REQUIRED = 3; private boolean includeMempool; private ImmutableList outPoints; From 11463e729f5973da856c4d2ce317e83fb0d3bdb4 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Fri, 9 Jan 2015 15:17:56 +0100 Subject: [PATCH 06/44] Add a feature to PeerGroup that lets you find/wait for peers that match a certain mask, similar to those methods that exist for peer versions. --- .../java/org/bitcoinj/core/PeerGroup.java | 44 ++++++++++++++++++- .../java/org/bitcoinj/core/PeerGroupTest.java | 25 +++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/bitcoinj/core/PeerGroup.java b/core/src/main/java/org/bitcoinj/core/PeerGroup.java index db88f143..e6dd1162 100644 --- a/core/src/main/java/org/bitcoinj/core/PeerGroup.java +++ b/core/src/main/java/org/bitcoinj/core/PeerGroup.java @@ -1501,7 +1501,7 @@ public class PeerGroup implements TransactionBroadcaster { } /** - * Returns a mutable array list of peers that implement the given protocol version or better. + * Returns an array list of peers that implement the given protocol version or better. */ public List findPeersOfAtLeastVersion(long protocolVersion) { lock.lock(); @@ -1516,6 +1516,48 @@ public class PeerGroup implements TransactionBroadcaster { } } + /** + * Returns a future that is triggered when there are at least the requested number of connected peers that support + * the given protocol version or higher. To block immediately, just call get() on the result. + * + * @param numPeers How many peers to wait for. + * @param mask An integer representing a bit mask that will be ANDed with the peers advertised service masks. + * @return a future that will be triggered when the number of connected peers implementing protocolVersion or higher >= numPeers + */ + public ListenableFuture> waitForPeersWithServiceMask(final int numPeers, final int mask) { + List foundPeers = findPeersWithServiceMask(mask); + if (foundPeers.size() >= numPeers) + return Futures.immediateFuture(foundPeers); + final SettableFuture> future = SettableFuture.create(); + addEventListener(new AbstractPeerEventListener() { + @Override + public void onPeerConnected(Peer peer, int peerCount) { + final List peers = findPeersWithServiceMask(mask); + if (peers.size() >= numPeers) { + future.set(peers); + removeEventListener(this); + } + } + }); + return future; + } + + /** + * Returns an array list of peers that match the requested service bit mask. + */ + public List findPeersWithServiceMask(int mask) { + lock.lock(); + try { + ArrayList results = new ArrayList(peers.size()); + for (Peer peer : peers) + if ((peer.getPeerVersionMessage().localServices & mask) == mask) + results.add(peer); + return results; + } finally { + lock.unlock(); + } + } + /** * Returns the number of connections that are required before transactions will be broadcast. If there aren't * enough, {@link PeerGroup#broadcastTransaction(Transaction)} will wait until the minimum number is reached so diff --git a/core/src/test/java/org/bitcoinj/core/PeerGroupTest.java b/core/src/test/java/org/bitcoinj/core/PeerGroupTest.java index 67746bd4..181afb2f 100644 --- a/core/src/test/java/org/bitcoinj/core/PeerGroupTest.java +++ b/core/src/test/java/org/bitcoinj/core/PeerGroupTest.java @@ -698,6 +698,31 @@ public class PeerGroupTest extends TestWithPeerGroup { assertTrue(future.isDone()); } + @Test + public void waitForPeersWithServiceFlags() throws Exception { + ListenableFuture> future = peerGroup.waitForPeersWithServiceMask(2, 3); + + VersionMessage ver1 = new VersionMessage(params, 10); + ver1.clientVersion = 70000; + ver1.localServices = VersionMessage.NODE_NETWORK; + VersionMessage ver2 = new VersionMessage(params, 10); + ver2.clientVersion = 70000; + ver2.localServices = VersionMessage.NODE_NETWORK | 2; + peerGroup.start(); + assertFalse(future.isDone()); + connectPeer(1, ver1); + assertTrue(peerGroup.findPeersWithServiceMask(3).isEmpty()); + assertFalse(future.isDone()); + connectPeer(2, ver2); + assertFalse(future.isDone()); + assertEquals(1, peerGroup.findPeersWithServiceMask(3).size()); + assertTrue(peerGroup.waitForPeersWithServiceMask(1, 0x3).isDone()); // Immediate completion. + connectPeer(3, ver2); + future.get(); + assertTrue(future.isDone()); + peerGroup.stop(); + } + @Test public void preferLocalPeer() throws IOException { // Because we are using the same port (8333 or 18333) that is used by Satoshi client From 7970b52504fd58dd92c854dc24a43eb9529447e3 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Sat, 10 Jan 2015 00:51:00 +0100 Subject: [PATCH 07/44] PeerGroup: Locking fix in newly added method. A bit more logging. --- .../java/org/bitcoinj/core/PeerGroup.java | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/core/PeerGroup.java b/core/src/main/java/org/bitcoinj/core/PeerGroup.java index e6dd1162..8ed76a43 100644 --- a/core/src/main/java/org/bitcoinj/core/PeerGroup.java +++ b/core/src/main/java/org/bitcoinj/core/PeerGroup.java @@ -884,6 +884,7 @@ public class PeerGroup implements TransactionBroadcaster { torClient.stop(); } vRunning = false; + log.info("Stopped."); } catch (Throwable e) { log.error("Exception when shutting down", e); // The executor swallows exceptions :( } @@ -897,6 +898,7 @@ public class PeerGroup implements TransactionBroadcaster { public void stop() { try { stopAsync(); + log.info("Awaiting PeerGroup shutdown ..."); executor.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS); } catch (InterruptedException e) { throw new RuntimeException(e); @@ -1525,21 +1527,26 @@ public class PeerGroup implements TransactionBroadcaster { * @return a future that will be triggered when the number of connected peers implementing protocolVersion or higher >= numPeers */ public ListenableFuture> waitForPeersWithServiceMask(final int numPeers, final int mask) { - List foundPeers = findPeersWithServiceMask(mask); - if (foundPeers.size() >= numPeers) - return Futures.immediateFuture(foundPeers); - final SettableFuture> future = SettableFuture.create(); - addEventListener(new AbstractPeerEventListener() { - @Override - public void onPeerConnected(Peer peer, int peerCount) { - final List peers = findPeersWithServiceMask(mask); - if (peers.size() >= numPeers) { - future.set(peers); - removeEventListener(this); + lock.lock(); + try { + List foundPeers = findPeersWithServiceMask(mask); + if (foundPeers.size() >= numPeers) + return Futures.immediateFuture(foundPeers); + final SettableFuture> future = SettableFuture.create(); + addEventListener(new AbstractPeerEventListener() { + @Override + public void onPeerConnected(Peer peer, int peerCount) { + final List peers = findPeersWithServiceMask(mask); + if (peers.size() >= numPeers) { + future.set(peers); + removeEventListener(this); + } } - } - }); - return future; + }); + return future; + } finally { + lock.unlock(); + } } /** From 6efa8a4fac9a2b0da83e0737d1c35a304ad93e81 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Sat, 10 Jan 2015 00:51:48 +0100 Subject: [PATCH 08/44] PeerGroup: Don't rethrow a runtime exception and kill the peer if a broadcast takes an exception, this can occur if there is a reject message. --- core/src/main/java/org/bitcoinj/core/PeerGroup.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/core/PeerGroup.java b/core/src/main/java/org/bitcoinj/core/PeerGroup.java index 8ed76a43..1c911cd8 100644 --- a/core/src/main/java/org/bitcoinj/core/PeerGroup.java +++ b/core/src/main/java/org/bitcoinj/core/PeerGroup.java @@ -1657,9 +1657,8 @@ public class PeerGroup implements TransactionBroadcaster { @Override public void onFailure(Throwable throwable) { - // This can't happen with the current code, but just in case one day that changes ... + // This can happen if we get a reject message from a peer. runningBroadcasts.remove(broadcast); - throw new RuntimeException(throwable); } }); // Keep a reference to the TransactionBroadcast object. This is important because otherwise, the entire tree From bde0df8e1c9d57a8a4d68ed3fad4654de5d1e3d1 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Mon, 12 Jan 2015 16:43:13 +0100 Subject: [PATCH 09/44] Peer: better logging for reject messages --- core/src/main/java/org/bitcoinj/core/Peer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/core/Peer.java b/core/src/main/java/org/bitcoinj/core/Peer.java index dffcda90..137a209d 100644 --- a/core/src/main/java/org/bitcoinj/core/Peer.java +++ b/core/src/main/java/org/bitcoinj/core/Peer.java @@ -396,9 +396,9 @@ public class Peer extends PeerSocketHandler { future.set((UTXOsMessage)m); } } else if (m instanceof RejectMessage) { - log.error("Received Message {}", m); + log.error("{} {}: Received {}", this, getPeerVersionMessage().subVer, m); } else { - log.warn("Received unhandled message: {}", m); + log.warn("{}: Received unhandled message: {}", this, m); } } From d9537b8ea9b4299fe9af0246ef6c61e16c0ac03a Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Mon, 12 Jan 2015 16:43:25 +0100 Subject: [PATCH 10/44] Wallet: minor style fixies --- core/src/main/java/org/bitcoinj/core/Wallet.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/core/Wallet.java b/core/src/main/java/org/bitcoinj/core/Wallet.java index 9e609f8a..2508b1c8 100644 --- a/core/src/main/java/org/bitcoinj/core/Wallet.java +++ b/core/src/main/java/org/bitcoinj/core/Wallet.java @@ -3583,9 +3583,8 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha req.tx.shuffleOutputs(); // Now sign the inputs, thus proving that we are entitled to redeem the connected outputs. - if (req.signInputs) { + if (req.signInputs) signTransaction(req); - } // Check size. int size = req.tx.bitcoinSerialize().length; @@ -3593,9 +3592,8 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha throw new ExceededMaxTransactionSize(); final Coin calculatedFee = req.tx.getFee(); - if (calculatedFee != null) { + if (calculatedFee != null) log.info(" with a fee of {}", calculatedFee.toFriendlyString()); - } // Label the transaction as being self created. We can use this later to spend its change output even before // the transaction is confirmed. We deliberately won't bother notifying listeners here as there's not much From 5f07f98c05ca2c6a0268a448efee6507336ae104 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Mon, 12 Jan 2015 16:43:44 +0100 Subject: [PATCH 11/44] WAK: Don't add dns discovery for regtest mode. --- core/src/main/java/org/bitcoinj/kits/WalletAppKit.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/bitcoinj/kits/WalletAppKit.java b/core/src/main/java/org/bitcoinj/kits/WalletAppKit.java index d4fac9cf..ddee2872 100644 --- a/core/src/main/java/org/bitcoinj/kits/WalletAppKit.java +++ b/core/src/main/java/org/bitcoinj/kits/WalletAppKit.java @@ -23,6 +23,7 @@ import com.subgraph.orchid.TorClient; import org.bitcoinj.core.*; import org.bitcoinj.net.discovery.DnsDiscovery; import org.bitcoinj.net.discovery.PeerDiscovery; +import org.bitcoinj.params.*; import org.bitcoinj.protocols.channels.StoredPaymentChannelClientStates; import org.bitcoinj.protocols.channels.StoredPaymentChannelServerStates; import org.bitcoinj.store.BlockStoreException; @@ -301,7 +302,7 @@ public class WalletAppKit extends AbstractIdleService { for (PeerAddress addr : peerAddresses) vPeerGroup.addAddress(addr); vPeerGroup.setMaxConnections(peerAddresses.length); peerAddresses = null; - } else { + } else if (params != RegTestParams.get()) { vPeerGroup.addPeerDiscovery(discovery != null ? discovery : new DnsDiscovery(params)); } vChain.addWallet(vWallet); From d7118d524e52bf135b3ca8369cce7add29f946cb Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Mon, 12 Jan 2015 16:44:10 +0100 Subject: [PATCH 12/44] Threading: print fewer warnings of excess closure buildup. We should really use a rate limiter here. --- core/src/main/java/org/bitcoinj/utils/Threading.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/bitcoinj/utils/Threading.java b/core/src/main/java/org/bitcoinj/utils/Threading.java index 28a20f65..733b2066 100644 --- a/core/src/main/java/org/bitcoinj/utils/Threading.java +++ b/core/src/main/java/org/bitcoinj/utils/Threading.java @@ -116,7 +116,7 @@ public class Threading { @Override public void execute(Runnable command) { final int size = tasks.size(); - if (size > WARNING_THRESHOLD) { + if (size == WARNING_THRESHOLD) { log.warn( "User thread has {} pending tasks, memory exhaustion may occur.\n" + "If you see this message, check your memory consumption and see if it's problematic or excessively spikey.\n" + From b069ffd31222b60b89e1531a8b4753b31456ae94 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Mon, 12 Jan 2015 16:50:59 +0100 Subject: [PATCH 13/44] BriefLogFormatter: Add an initWithSilentBitcoinJ method. --- core/src/main/java/org/bitcoinj/utils/BriefLogFormatter.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/src/main/java/org/bitcoinj/utils/BriefLogFormatter.java b/core/src/main/java/org/bitcoinj/utils/BriefLogFormatter.java index 33eeee7f..c072635f 100644 --- a/core/src/main/java/org/bitcoinj/utils/BriefLogFormatter.java +++ b/core/src/main/java/org/bitcoinj/utils/BriefLogFormatter.java @@ -49,6 +49,11 @@ public class BriefLogFormatter extends Formatter { logger.log(Level.FINE, "test"); } + public static void initWithSilentBitcoinJ() { + init(); + Logger.getLogger("org.bitcoinj").setLevel(Level.SEVERE); + } + @Override public String format(LogRecord logRecord) { Object[] arguments = new Object[6]; From 8863cd5afd1567a59e2fa70b90058f8f8e3612e6 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Mon, 12 Jan 2015 16:53:20 +0100 Subject: [PATCH 14/44] Refresh checkpoints --- .../org.bitcoin.production.checkpoints | Bin 14133 -> 15861 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/core/src/main/resources/org.bitcoin.production.checkpoints b/core/src/main/resources/org.bitcoin.production.checkpoints index d92d362de44a4fb41a33d9a111e12e7f818f7524..a05db876366a11a1530838ee405ecb124d40f90c 100644 GIT binary patch delta 1754 zcmX}rdo+}39Ki8sc&AasP}b`@vvOM$$^BBQ$u$nWqm7h7tr>%zSt>TntXu0=g9wpk ziq*z2xzyTnw<9DYZ4T3g!J%bTN;I@D=l6S_w<+esdDeDRB~Kl1#{j_h zD%v-`9}`Q>1W+zh4gerjVlKSzzo$HKCj7>>MR3^~eadW^PfD>+fpD5c8rlaEkDkMq! zT3jw|ede?$Yj%-wZpHEwuh6iVnG`-?`+pypq){WV-sDYL4^2zzT~wU$6BVS6Z{yNZ z#BL{Be{>nK8>JzLWg_fU6eVE$}}D*y`@$=&NFf?&!60CiuN0&{`= z3P$!ZclY#;X7G6{$ysSs8+WS_P1yhoB%Dd&N}3T}MOb)u>1b;amCaBP@d({HaX4WO z?w?bFVT8kFIKuq3v2cJ=nIqTKl57>sH?ClSDFwXmMD%(5MAf)o{n#3rLuY7_EOv6^pDgHcaQ0uvdn^j8l83E3>OZ6^XOjj6?SWHBSc@HeFIZ3oP}Ukp zg+WPW&jK6)G(wX0IbB!gHZjzMropAIGcRM6*D=wGF(o+j%`%|86lL6UWKo~i;;$8o zg?5$JXUAMDRW@}MN2?3p&FUqymylyRyI>;~3>Tn6hMHrPI_)~p2!MpHQXtjp%&k1y zUwW9L`1!Rtu8DK`%OAGFgZQug_{9~*N<{$Gw{y%0N4Z@5N1mw1@J;i_X{X$~h11ST zv|4$hSltBX-8v=$sC*sGC}C0k0sD>LYzi+0UKex*EkEce*=y*QpDPht+fK-Fk)$WE z3=nxzqOtbT_|F5o=~fQmED`nPpTB&9b_{JIPd4fdt6JXN&LGZ>8^B%xG%bMYeXM(c z>{5jllK?aiq<~7~s=d{gWQmQ>>R9p8bUXV6Mx7(Td?N!|TKqVw)YEci2Y)WhGfx%d zIIu%Ub59syRrfn5e6JYmcfo*Ia1RGlrfFtCPT_>o(C$l?A>&3kb~LtgOAjPXZ?8f@7+yC|<&6g_M@ zXiJQx;NTGAQ3%L+WIGf*e&?Fx*#;m<`>Bsv@Np$@j-xSt!QN5;GOF3*wz{>io zj51-)l_nlR=P(_yn7@t;0lVc$_mXBYo3)HjObMLm@gTP6>cKTbDb+wO>jE>P`k>w+ zhc$qfzb*x+ecX!-Ubb;;;IvI$KK}Phm!h3wNt~|(tWfSZs;W3m3RhD;r?0B~dWhPV zxVNG;plTD^eT?ol&fsSU5-q#5VQ=oIjTJpHB+ZJxKJVQ*l<@7q#8gYy?V1?oH)_UZ9Bx*QHUD+gcW4LSWMbTTZ8*Ix-QhnFzP@4r delta 12 Tcmexby)|!w2;=09q6+2!CoBZ% From cd7dc3e535a06d10c010aa63d20ffeab294dd61b Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Mon, 12 Jan 2015 16:53:45 +0100 Subject: [PATCH 15/44] Don't print logging messages when calculating checkpoints. --- tools/src/main/java/org/bitcoinj/tools/BuildCheckpoints.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/src/main/java/org/bitcoinj/tools/BuildCheckpoints.java b/tools/src/main/java/org/bitcoinj/tools/BuildCheckpoints.java index cc4537a3..6fb61a6a 100644 --- a/tools/src/main/java/org/bitcoinj/tools/BuildCheckpoints.java +++ b/tools/src/main/java/org/bitcoinj/tools/BuildCheckpoints.java @@ -52,7 +52,7 @@ public class BuildCheckpoints { private static final File TEXTUAL_CHECKPOINTS_FILE = new File("checkpoints.txt"); public static void main(String[] args) throws Exception { - BriefLogFormatter.init(); + BriefLogFormatter.initWithSilentBitcoinJ(); // Sorted map of block height to StoredBlock object. final TreeMap checkpoints = new TreeMap(); From 96451e626dcf7d71098ec1fe66251b3a975c4524 Mon Sep 17 00:00:00 2001 From: Andreas Schildbach Date: Tue, 13 Jan 2015 18:21:46 +0100 Subject: [PATCH 16/44] Travis now lists system info with each build, so don't run lsb_release any more. --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2b712442..0e97f2d4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ # configuration for https://travis-ci.org/bitcoinj/bitcoinj language: java jdk: oraclejdk8 -before_install: lsb_release -a install: true # remove default script: - mvn -q clean install From 336b0f6aa2a3c146c30b667ab068864c89c256bb Mon Sep 17 00:00:00 2001 From: Oscar Guindzberg Date: Fri, 16 Jan 2015 11:32:05 -0300 Subject: [PATCH 17/44] Fix typo --- core/src/test/java/org/bitcoinj/core/PeerGroupTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/test/java/org/bitcoinj/core/PeerGroupTest.java b/core/src/test/java/org/bitcoinj/core/PeerGroupTest.java index 181afb2f..71f5d6eb 100644 --- a/core/src/test/java/org/bitcoinj/core/PeerGroupTest.java +++ b/core/src/test/java/org/bitcoinj/core/PeerGroupTest.java @@ -239,7 +239,7 @@ public class PeerGroupTest extends TestWithPeerGroup { @Test - public void receiveTxBroadcastOnAddeweldWallet() throws Exception { + public void receiveTxBroadcastOnAddedWallet() throws Exception { // Check that when we receive transactions on all our peers, we do the right thing. peerGroup.start(); From 3456e896ecd1d8375dbdfdcd3f2fda7ef88c776b Mon Sep 17 00:00:00 2001 From: Sean Gilligan Date: Fri, 2 Jan 2015 23:02:25 -0800 Subject: [PATCH 18/44] Make Address (& super & subs) Cloneable * Implement Cloneable in VersionedChecksummedBytes * Override clone() in VersionedChecksummedBytes * Override clone() in Address * Add Unit test file for VersionedChecksummedBytes * Add clone unit tests for clone for all subclasses of VersionedChecksummedBytes TODO: Consider overriding clone() in DumpedPrivateKey and BIP38PrivateKey --- .../main/java/org/bitcoinj/core/Address.java | 8 +++ .../core/VersionedChecksummedBytes.java | 15 ++++- .../java/org/bitcoinj/core/AddressTest.java | 10 ++++ .../bitcoinj/core/DumpedPrivateKeyTest.java | 12 ++++ .../core/VersionedChecksummedBytesTest.java | 55 +++++++++++++++++++ .../bitcoinj/crypto/BIP38PrivateKeyTest.java | 12 ++++ 6 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 core/src/test/java/org/bitcoinj/core/VersionedChecksummedBytesTest.java diff --git a/core/src/main/java/org/bitcoinj/core/Address.java b/core/src/main/java/org/bitcoinj/core/Address.java index 3289569f..934dc27e 100644 --- a/core/src/main/java/org/bitcoinj/core/Address.java +++ b/core/src/main/java/org/bitcoinj/core/Address.java @@ -168,4 +168,12 @@ public class Address extends VersionedChecksummedBytes { } return false; } + + /** + * This implementation narrows the return type to Address. + */ + @Override + public Address clone() throws CloneNotSupportedException { + return (Address) super.clone(); + } } diff --git a/core/src/main/java/org/bitcoinj/core/VersionedChecksummedBytes.java b/core/src/main/java/org/bitcoinj/core/VersionedChecksummedBytes.java index 4c4f9a49..1907cc9b 100644 --- a/core/src/main/java/org/bitcoinj/core/VersionedChecksummedBytes.java +++ b/core/src/main/java/org/bitcoinj/core/VersionedChecksummedBytes.java @@ -31,7 +31,7 @@ import com.google.common.base.Objects; *

and the result is then Base58 encoded. This format is used for addresses, and private keys exported using the * dumpprivkey command.

*/ -public class VersionedChecksummedBytes implements Serializable { +public class VersionedChecksummedBytes implements Serializable, Cloneable { protected final int version; protected byte[] bytes; @@ -82,6 +82,19 @@ public class VersionedChecksummedBytes implements Serializable { /** * Returns the "version" or "header" byte: the first byte of the data. This is used to disambiguate what the * contents apply to, for example, which network the key or address is valid on. + * {@inheritDoc} + * + * This implementation narrows the return type to VersionedChecksummedBytes + * and allows subclasses to throw CloneNotSupportedException even though it + * is never thrown by this implementation. + */ + @Override + public VersionedChecksummedBytes clone() throws CloneNotSupportedException { + return (VersionedChecksummedBytes) super.clone(); + } + + + /** * * @return A positive number between 0 and 255. */ diff --git a/core/src/test/java/org/bitcoinj/core/AddressTest.java b/core/src/test/java/org/bitcoinj/core/AddressTest.java index ae1d1ca5..3269ab0c 100644 --- a/core/src/test/java/org/bitcoinj/core/AddressTest.java +++ b/core/src/test/java/org/bitcoinj/core/AddressTest.java @@ -168,4 +168,14 @@ public class AddressTest { Address address = Address.fromP2SHScript(mainParams, p2shScript); assertEquals("3N25saC4dT24RphDAwLtD8LUN4E2gZPJke", address.toString()); } + + @Test + public void cloning() throws Exception { + Address a = new Address(testParams, HEX.decode("fda79a24e50ff70ff42f7d89585da5bd19d9e5cc")); + Address b = a.clone(); + + assertEquals(a, b); + assertNotSame(a, b); + } + } diff --git a/core/src/test/java/org/bitcoinj/core/DumpedPrivateKeyTest.java b/core/src/test/java/org/bitcoinj/core/DumpedPrivateKeyTest.java index 82197757..e94c4f46 100644 --- a/core/src/test/java/org/bitcoinj/core/DumpedPrivateKeyTest.java +++ b/core/src/test/java/org/bitcoinj/core/DumpedPrivateKeyTest.java @@ -17,6 +17,7 @@ package org.bitcoinj.core; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -38,4 +39,15 @@ public class DumpedPrivateKeyTest { .readObject(); assertEquals(key, keyCopy); } + + @Test + public void cloning() throws Exception { + DumpedPrivateKey a = new DumpedPrivateKey(MainNetParams.get(), new ECKey().getPrivKeyBytes(), true); + // TODO: Consider overriding clone() in DumpedPrivateKey to narrow the type + DumpedPrivateKey b = (DumpedPrivateKey) a.clone(); + + assertEquals(a, b); + assertNotSame(a, b); + } + } diff --git a/core/src/test/java/org/bitcoinj/core/VersionedChecksummedBytesTest.java b/core/src/test/java/org/bitcoinj/core/VersionedChecksummedBytesTest.java new file mode 100644 index 00000000..3b78076f --- /dev/null +++ b/core/src/test/java/org/bitcoinj/core/VersionedChecksummedBytesTest.java @@ -0,0 +1,55 @@ +/** + * Copyright 2014 BitcoinJ Project + * + * 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 org.bitcoinj.core; + +import org.bitcoinj.params.MainNetParams; +import org.bitcoinj.params.TestNet3Params; +import org.junit.Test; + +import static org.bitcoinj.core.Utils.HEX; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +/** + * + */ +public class VersionedChecksummedBytesTest { + static final NetworkParameters testParams = TestNet3Params.get(); + static final NetworkParameters mainParams = MainNetParams.get(); + + @Test + public void stringification() throws Exception { + // Test a testnet address. + VersionedChecksummedBytes a = new VersionedChecksummedBytes(testParams.getAddressHeader(), HEX.decode("fda79a24e50ff70ff42f7d89585da5bd19d9e5cc")); + assertEquals("n4eA2nbYqErp7H6jebchxAN59DmNpksexv", a.toString()); + + VersionedChecksummedBytes b = new VersionedChecksummedBytes(mainParams.getAddressHeader(), HEX.decode("4a22c3c4cbb31e4d03b15550636762bda0baf85a")); + assertEquals("17kzeh4N8g49GFvdDzSf8PjaPfyoD1MndL", b.toString()); + } + + @Test + public void cloning() throws Exception { + VersionedChecksummedBytes a = new VersionedChecksummedBytes(testParams.getAddressHeader(), HEX.decode("fda79a24e50ff70ff42f7d89585da5bd19d9e5cc")); + VersionedChecksummedBytes b = a.clone(); + + assertEquals(a, b); + assertNotSame(a, b); + } + +} diff --git a/core/src/test/java/org/bitcoinj/crypto/BIP38PrivateKeyTest.java b/core/src/test/java/org/bitcoinj/crypto/BIP38PrivateKeyTest.java index 844adc57..ddaf587f 100644 --- a/core/src/test/java/org/bitcoinj/crypto/BIP38PrivateKeyTest.java +++ b/core/src/test/java/org/bitcoinj/crypto/BIP38PrivateKeyTest.java @@ -28,6 +28,7 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; public class BIP38PrivateKeyTest { @@ -157,4 +158,15 @@ public class BIP38PrivateKeyTest { .readObject(); assertEquals(key, keyCopy); } + + @Test + public void cloning() throws Exception { + BIP38PrivateKey a = new BIP38PrivateKey(TESTNET, "6PfMmVHn153N3x83Yiy4Nf76dHUkXufe2Adr9Fw5bewrunGNeaw2QCpifb"); + // TODO: Consider overriding clone() in BIP38PrivateKey to narrow the type + BIP38PrivateKey b = (BIP38PrivateKey) a.clone(); + + assertEquals(a, b); + assertNotSame(a, b); + } + } From 101ad8390601aec762ee354ef72c66de6e1d7040 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Sat, 17 Jan 2015 15:01:52 +0100 Subject: [PATCH 19/44] Make KeyCrypterScrypt.randomSalt() public. --- core/src/main/java/org/bitcoinj/crypto/KeyCrypterScrypt.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/bitcoinj/crypto/KeyCrypterScrypt.java b/core/src/main/java/org/bitcoinj/crypto/KeyCrypterScrypt.java index 17b694bc..c631d37a 100644 --- a/core/src/main/java/org/bitcoinj/crypto/KeyCrypterScrypt.java +++ b/core/src/main/java/org/bitcoinj/crypto/KeyCrypterScrypt.java @@ -83,7 +83,8 @@ public class KeyCrypterScrypt implements KeyCrypter, Serializable { private static final transient SecureRandom secureRandom; - private static byte[] randomSalt() { + /** Returns SALT_LENGTH (8) bytes of random data */ + public static byte[] randomSalt() { byte[] salt = new byte[SALT_LENGTH]; secureRandom.nextBytes(salt); return salt; From 71e9a2d4b2c29a0cfa2096b323fa06c61343f30a Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Wed, 21 Jan 2015 15:48:42 +0100 Subject: [PATCH 20/44] WalletTemplate: don't override default PeerGroup params as they should be appropriate out of the box. Propagate WAK startup errors to the UI. --- .../src/main/java/wallettemplate/Main.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/wallettemplate/src/main/java/wallettemplate/Main.java b/wallettemplate/src/main/java/wallettemplate/Main.java index 0c61891a..4f12b2dc 100644 --- a/wallettemplate/src/main/java/wallettemplate/Main.java +++ b/wallettemplate/src/main/java/wallettemplate/Main.java @@ -1,9 +1,9 @@ package wallettemplate; +import com.google.common.util.concurrent.*; import org.bitcoinj.core.NetworkParameters; import org.bitcoinj.kits.WalletAppKit; -import org.bitcoinj.params.RegTestParams; -import org.bitcoinj.params.TestNet3Params; +import org.bitcoinj.params.*; import org.bitcoinj.utils.BriefLogFormatter; import org.bitcoinj.utils.Threading; import org.bitcoinj.wallet.DeterministicSeed; @@ -29,7 +29,7 @@ import static wallettemplate.utils.GuiUtils.*; public class Main extends Application { public static String APP_NAME = "WalletTemplate"; - public static NetworkParameters params = TestNet3Params.get(); + public static NetworkParameters params = MainNetParams.get(); public static WalletAppKit bitcoin; public static Main instance; @@ -95,20 +95,23 @@ public class Main extends Application { mainWindow.show(); + bitcoin.addListener(new Service.Listener() { + @Override + public void failed(Service.State from, Throwable failure) { + GuiUtils.crashAlert(failure); + } + }, Platform::runLater); bitcoin.startAsync(); } public void setupWalletKit(@Nullable DeterministicSeed seed) { // If seed is non-null it means we are restoring from backup. - bitcoin = new WalletAppKit(params, new File("."), APP_NAME) { + bitcoin = new WalletAppKit(params, new File("."), APP_NAME + "-" + params.getPaymentProtocolId()) { @Override protected void onSetupCompleted() { // Don't make the user wait for confirmations for now, as the intention is they're sending it // their own money! bitcoin.wallet().allowSpendingUnconfirmedTransactions(); - if (params != RegTestParams.get()) - bitcoin.peerGroup().setMaxConnections(11); - bitcoin.peerGroup().setBloomFilterFalsePositiveRate(0.00001); Platform.runLater(controller::onBitcoinSetup); } }; From b9cf28d358e4d8281857e3c7f3f17e9871587513 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Wed, 21 Jan 2015 15:48:59 +0100 Subject: [PATCH 21/44] TransactionBroadcast: Better logging --- .../src/main/java/org/bitcoinj/core/TransactionBroadcast.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/core/TransactionBroadcast.java b/core/src/main/java/org/bitcoinj/core/TransactionBroadcast.java index cfe92863..0b817751 100644 --- a/core/src/main/java/org/bitcoinj/core/TransactionBroadcast.java +++ b/core/src/main/java/org/bitcoinj/core/TransactionBroadcast.java @@ -83,7 +83,7 @@ public class TransactionBroadcast { public ListenableFuture broadcast() { peerGroup.addEventListener(rejectionListener, Threading.SAME_THREAD); - log.info("Waiting for {} peers required for broadcast ...", minConnections); + log.info("Waiting for {} peers required for broadcast, we have {} ...", minConnections, peerGroup.getConnectedPeers().size()); peerGroup.waitForPeers(minConnections).addListener(new EnoughAvailablePeers(), Threading.SAME_THREAD); return future; } @@ -121,7 +121,7 @@ public class TransactionBroadcast { numWaitingFor = (int) Math.ceil((peers.size() - numToBroadcastTo) / 2.0); Collections.shuffle(peers, random); peers = peers.subList(0, numToBroadcastTo); - log.info("broadcastTransaction: We have {} peers, adding {} to the memory pool and sending to {} peers, will wait for {}: {}", + log.info("broadcastTransaction: We have {} peers, adding {} to the memory pool and sending to {} peers, will wait for {}, sending to: {}", numConnected, tx.getHashAsString(), numToBroadcastTo, numWaitingFor, Joiner.on(",").join(peers)); for (Peer peer : peers) { try { From 46344dd0b1ebcd658a17559955d5d7fdbff41630 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Wed, 21 Jan 2015 16:22:46 +0100 Subject: [PATCH 22/44] PeerGroup: fix the IPv6 routing failure detection which was broken by a recent change and improve logging in a bunch of places. --- .../org/bitcoinj/core/PeerEventListener.java | 3 ++- .../java/org/bitcoinj/core/PeerGroup.java | 20 ++++++++++++------- .../bitcoinj/net/ClientConnectionManager.java | 3 ++- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/core/PeerEventListener.java b/core/src/main/java/org/bitcoinj/core/PeerEventListener.java index 31f4e538..a711491c 100644 --- a/core/src/main/java/org/bitcoinj/core/PeerEventListener.java +++ b/core/src/main/java/org/bitcoinj/core/PeerEventListener.java @@ -65,7 +65,8 @@ public interface PeerEventListener { /** * Called when a peer is disconnected. Note that this won't be called if the listener is registered on a * {@link PeerGroup} and the group is in the process of shutting down. If this listener is registered to a - * {@link Peer} instead of a {@link PeerGroup}, peerCount will always be 0. + * {@link Peer} instead of a {@link PeerGroup}, peerCount will always be 0. This handler can be called without + * a corresponding invocation of onPeerConnected if the initial connection is never successful. * * @param peer * @param peerCount the total number of connected peers diff --git a/core/src/main/java/org/bitcoinj/core/PeerGroup.java b/core/src/main/java/org/bitcoinj/core/PeerGroup.java index 1c911cd8..4969fe3a 100644 --- a/core/src/main/java/org/bitcoinj/core/PeerGroup.java +++ b/core/src/main/java/org/bitcoinj/core/PeerGroup.java @@ -18,6 +18,7 @@ package org.bitcoinj.core; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.*; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; @@ -1169,10 +1170,15 @@ public class PeerGroup implements TransactionBroadcaster { pendingPeers.add(peer); try { - channels.openConnection(address.toSocketAddress(), peer); - } catch (Exception e) { - log.warn("Failed to connect to " + address + ": " + e.getMessage()); - handlePeerDeath(peer, e); + log.info("Attempting connection to {} ({} connected, {} pending, {} max)", address, + peers.size(), pendingPeers.size(), maxConnections); + ListenableFuture future = channels.openConnection(address.toSocketAddress(), peer); + if (future.isDone()) + Uninterruptibles.getUninterruptibly(future); + } catch (ExecutionException e) { + Throwable cause = Throwables.getRootCause(e); + log.warn("Failed to connect to " + address + ": " + cause.getMessage()); + handlePeerDeath(peer, cause); return null; } peer.setSocketTimeout(connectTimeoutMillis); @@ -1245,10 +1251,10 @@ public class PeerGroup implements TransactionBroadcaster { backoffMap.get(peer.getAddress()).trackSuccess(); // Sets up the newly connected peer so it can do everything it needs to. - log.info("{}: New peer", peer); pendingPeers.remove(peer); peers.add(peer); newSize = peers.size(); + log.info("{}: New peer ({} connected, {} pending, {} max)", peer, newSize, pendingPeers.size(), maxConnections); // Give the peer a filter that can be used to probabilistically drop transactions that // aren't relevant to our wallet. We may still receive some false positives, which is // OK because it helps improve wallet privacy. Old nodes will just ignore the message. @@ -1386,7 +1392,7 @@ public class PeerGroup implements TransactionBroadcaster { } } - protected void handlePeerDeath(final Peer peer, @Nullable Exception exception) { + protected void handlePeerDeath(final Peer peer, @Nullable Throwable exception) { // Peer deaths can occur during startup if a connect attempt after peer discovery aborts immediately. if (!isRunning()) return; @@ -1417,7 +1423,7 @@ public class PeerGroup implements TransactionBroadcaster { groupBackoff.trackFailure(); - if (!(exception instanceof NoRouteToHostException)) { + if (exception instanceof NoRouteToHostException) { if (address.getAddr() instanceof Inet6Address && !ipv6Unreachable) { ipv6Unreachable = true; log.warn("IPv6 peer connect failed due to routing failure, ignoring IPv6 addresses from now on"); diff --git a/core/src/main/java/org/bitcoinj/net/ClientConnectionManager.java b/core/src/main/java/org/bitcoinj/net/ClientConnectionManager.java index e4dd5a77..8027023d 100644 --- a/core/src/main/java/org/bitcoinj/net/ClientConnectionManager.java +++ b/core/src/main/java/org/bitcoinj/net/ClientConnectionManager.java @@ -30,7 +30,8 @@ import java.net.SocketAddress; */ public interface ClientConnectionManager extends Service { /** - * Creates a new connection to the given address, with the given parser used to handle incoming data. + * Creates a new connection to the given address, with the given parser used to handle incoming data. Any errors + * that occur during connection will be returned in the given future, including errors that can occur immediately. */ ListenableFuture openConnection(SocketAddress serverAddress, StreamParser parser); From 33228cdb1924706bd15aa1829551d1c3fa7713ab Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Wed, 21 Jan 2015 16:23:50 +0100 Subject: [PATCH 23/44] Bump the number of max peers to 12 by default instead of 4, as we're seeing more tx broadcast flakyness from the network these days. Also use 80% of max peers as the number to wait for before tx broadcast instead of half. --- core/src/main/java/org/bitcoinj/core/PeerGroup.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/core/PeerGroup.java b/core/src/main/java/org/bitcoinj/core/PeerGroup.java index 4969fe3a..977e77e2 100644 --- a/core/src/main/java/org/bitcoinj/core/PeerGroup.java +++ b/core/src/main/java/org/bitcoinj/core/PeerGroup.java @@ -80,7 +80,13 @@ import static com.google.common.base.Preconditions.checkState; */ public class PeerGroup implements TransactionBroadcaster { private static final Logger log = LoggerFactory.getLogger(PeerGroup.class); - private static final int DEFAULT_CONNECTIONS = 4; + /** + * The default number of connections to the p2p network the library will try to build. This is set to 12 empirically. + * It used to be 4, but because we divide the connection pool in two for broadcasting transactions, that meant we + * were only sending transactions to two peers and sometimes this wasn't reliable enough: transactions wouldn't + * get through. + */ + public static final int DEFAULT_CONNECTIONS = 12; private static final int TOR_TIMEOUT_SECONDS = 60; private int vMaxPeersToDiscoverCount = 100; @@ -1575,7 +1581,7 @@ public class PeerGroup implements TransactionBroadcaster { * Returns the number of connections that are required before transactions will be broadcast. If there aren't * enough, {@link PeerGroup#broadcastTransaction(Transaction)} will wait until the minimum number is reached so * propagation across the network can be observed. If no value has been set using - * {@link PeerGroup#setMinBroadcastConnections(int)} a default of half of whatever + * {@link PeerGroup#setMinBroadcastConnections(int)} a default of 80% of whatever * {@link org.bitcoinj.core.PeerGroup#getMaxConnections()} returns is used. */ public int getMinBroadcastConnections() { @@ -1586,7 +1592,7 @@ public class PeerGroup implements TransactionBroadcaster { if (max <= 1) return max; else - return (int) Math.round(getMaxConnections() / 2.0); + return (int) Math.round(getMaxConnections() * 0.8); } return minBroadcastConnections; } finally { From 2138c8aec47c9da88724441b9d67bc3c714294d4 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Wed, 21 Jan 2015 16:44:35 +0100 Subject: [PATCH 24/44] PeerGroup: log connected/pending/max in handlePeerDeath too --- core/src/main/java/org/bitcoinj/core/PeerGroup.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/bitcoinj/core/PeerGroup.java b/core/src/main/java/org/bitcoinj/core/PeerGroup.java index 977e77e2..bb9e6ef0 100644 --- a/core/src/main/java/org/bitcoinj/core/PeerGroup.java +++ b/core/src/main/java/org/bitcoinj/core/PeerGroup.java @@ -1411,7 +1411,7 @@ public class PeerGroup implements TransactionBroadcaster { PeerAddress address = peer.getAddress(); - log.info("{}: Peer died", address); + log.info("{}: Peer died ({} connected, {} pending, {} max)", address, peers.size(), pendingPeers.size(), maxConnections); if (peer == downloadPeer) { log.info("Download peer died. Picking a new one."); setDownloadPeer(null); From a698c5846e75e4e87799158d4c59ae50b5a26070 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Wed, 21 Jan 2015 16:45:03 +0100 Subject: [PATCH 25/44] Peer: invoke disconnection handlers on timeout even if we didn't successfully connect yet. --- core/src/main/java/org/bitcoinj/core/Peer.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/core/src/main/java/org/bitcoinj/core/Peer.java b/core/src/main/java/org/bitcoinj/core/Peer.java index 137a209d..56ec4322 100644 --- a/core/src/main/java/org/bitcoinj/core/Peer.java +++ b/core/src/main/java/org/bitcoinj/core/Peer.java @@ -276,6 +276,14 @@ public class Peer extends PeerSocketHandler { } } + @Override + protected void timeoutOccurred() { + super.timeoutOccurred(); + if (!connectionOpenFuture.isDone()) { + connectionClosed(); // Invoke the event handlers to tell listeners e.g. PeerGroup that we never managed to connect. + } + } + @Override public void connectionClosed() { for (final PeerListenerRegistration registration : eventListeners) { From 068da489efe1802facbb07776c123e44694f1c37 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Thu, 22 Jan 2015 18:09:07 +0100 Subject: [PATCH 26/44] WalletAppKit/SPVBlockStore: release the file lock explicitly when closing, as otherwise apps that try to shut down the store then delete the file can crash on Windows. --- core/src/main/java/org/bitcoinj/kits/WalletAppKit.java | 7 +++---- core/src/main/java/org/bitcoinj/store/SPVBlockStore.java | 1 + 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/kits/WalletAppKit.java b/core/src/main/java/org/bitcoinj/kits/WalletAppKit.java index ddee2872..75d3c9db 100644 --- a/core/src/main/java/org/bitcoinj/kits/WalletAppKit.java +++ b/core/src/main/java/org/bitcoinj/kits/WalletAppKit.java @@ -40,6 +40,7 @@ import java.io.*; import java.net.InetAddress; import java.net.UnknownHostException; import java.nio.channels.FileLock; +import java.nio.file.*; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -275,8 +276,7 @@ public class WalletAppKit extends AbstractIdleService { if (chainFileExists) { log.info("Deleting the chain file in preparation from restore."); vStore.close(); - if (!chainFile.delete()) - throw new Exception("Failed to delete chain file in preparation for restore."); + Files.delete(chainFile.toPath()); vStore = new SPVBlockStore(params, chainFile); } } else { @@ -286,8 +286,7 @@ public class WalletAppKit extends AbstractIdleService { } else if (chainFileExists) { log.info("Deleting the chain file in preparation from restore."); vStore.close(); - if (!chainFile.delete()) - throw new Exception("Failed to delete chain file in preparation for restore."); + Files.delete(chainFile.toPath()); vStore = new SPVBlockStore(params, chainFile); } } diff --git a/core/src/main/java/org/bitcoinj/store/SPVBlockStore.java b/core/src/main/java/org/bitcoinj/store/SPVBlockStore.java index 38634aad..53d2325c 100644 --- a/core/src/main/java/org/bitcoinj/store/SPVBlockStore.java +++ b/core/src/main/java/org/bitcoinj/store/SPVBlockStore.java @@ -271,6 +271,7 @@ public class SPVBlockStore implements BlockStore { buffer.force(); buffer = null; // Allow it to be GCd and the underlying file mapping to go away. randomAccessFile.close(); + fileLock.release(); } catch (IOException e) { throw new BlockStoreException(e); } From d5f47f37d3532e0787b13e72ab24cc0aab77c83a Mon Sep 17 00:00:00 2001 From: Oscar Guindzberg Date: Tue, 20 Jan 2015 12:37:58 -0300 Subject: [PATCH 27/44] Allow building a MarriedKeyChain with a watchingKey --- .../org/bitcoinj/wallet/DeterministicKeyChain.java | 12 ++++++++++-- .../java/org/bitcoinj/wallet/MarriedKeyChain.java | 10 ++++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/wallet/DeterministicKeyChain.java b/core/src/main/java/org/bitcoinj/wallet/DeterministicKeyChain.java index 2ebf6dd4..dc04931f 100644 --- a/core/src/main/java/org/bitcoinj/wallet/DeterministicKeyChain.java +++ b/core/src/main/java/org/bitcoinj/wallet/DeterministicKeyChain.java @@ -162,6 +162,7 @@ public class DeterministicKeyChain implements EncryptableKeyChain { protected long seedCreationTimeSecs; protected byte[] entropy; protected DeterministicSeed seed; + protected DeterministicKey watchingKey; protected Builder() { } @@ -214,6 +215,11 @@ public class DeterministicKeyChain implements EncryptableKeyChain { return self(); } + public T watchingKey(DeterministicKey watchingKey) { + this.watchingKey = watchingKey; + return self(); + } + /** The passphrase to use with the generated mnemonic, or null if you would like to use the default empty string. Currently must be the empty string. */ public T passphrase(String passphrase) { // FIXME support non-empty passphrase @@ -223,7 +229,7 @@ public class DeterministicKeyChain implements EncryptableKeyChain { public DeterministicKeyChain build() { - checkState(random != null || entropy != null || seed != null, "Must provide either entropy or random"); + checkState(random != null || entropy != null || seed != null || watchingKey!= null, "Must provide either entropy or random or seed or watchingKey"); checkState(passphrase == null || seed == null, "Passphrase must not be specified with seed"); DeterministicKeyChain chain; @@ -232,8 +238,10 @@ public class DeterministicKeyChain implements EncryptableKeyChain { chain = new DeterministicKeyChain(random, bits, getPassphrase(), seedCreationTimeSecs); } else if (entropy != null) { chain = new DeterministicKeyChain(entropy, getPassphrase(), seedCreationTimeSecs); - } else { + } else if (seed != null) { chain = new DeterministicKeyChain(seed); + } else { + chain = new DeterministicKeyChain(watchingKey, seedCreationTimeSecs); } return chain; diff --git a/core/src/main/java/org/bitcoinj/wallet/MarriedKeyChain.java b/core/src/main/java/org/bitcoinj/wallet/MarriedKeyChain.java index 3450f909..f2ec0e88 100644 --- a/core/src/main/java/org/bitcoinj/wallet/MarriedKeyChain.java +++ b/core/src/main/java/org/bitcoinj/wallet/MarriedKeyChain.java @@ -91,7 +91,7 @@ public class MarriedKeyChain extends DeterministicKeyChain { } public MarriedKeyChain build() { - checkState(random != null || entropy != null || seed != null, "Must provide either entropy or random"); + checkState(random != null || entropy != null || seed != null || watchingKey!= null, "Must provide either entropy or random or seed or watchingKey"); checkNotNull(followingKeys, "followingKeys must be provided"); MarriedKeyChain chain; if (threshold == 0) @@ -100,8 +100,10 @@ public class MarriedKeyChain extends DeterministicKeyChain { chain = new MarriedKeyChain(random, bits, getPassphrase(), seedCreationTimeSecs); } else if (entropy != null) { chain = new MarriedKeyChain(entropy, getPassphrase(), seedCreationTimeSecs); - } else { + } else if (seed != null) { chain = new MarriedKeyChain(seed); + } else { + chain = new MarriedKeyChain(watchingKey, seedCreationTimeSecs); } chain.addFollowingAccountKeys(followingKeys, threshold); return chain; @@ -117,6 +119,10 @@ public class MarriedKeyChain extends DeterministicKeyChain { super(accountKey, false); } + MarriedKeyChain(DeterministicKey accountKey, long seedCreationTimeSecs) { + super(accountKey, seedCreationTimeSecs); + } + MarriedKeyChain(DeterministicSeed seed, KeyCrypter crypter) { super(seed, crypter); } From c2c3b715f3c56e204801af856ee481590af21775 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Tue, 27 Jan 2015 18:39:46 +0100 Subject: [PATCH 28/44] PeerGroup: fix another regression with handling of disconnected peers --- core/src/main/java/org/bitcoinj/core/PeerGroup.java | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/main/java/org/bitcoinj/core/PeerGroup.java b/core/src/main/java/org/bitcoinj/core/PeerGroup.java index bb9e6ef0..09a55e23 100644 --- a/core/src/main/java/org/bitcoinj/core/PeerGroup.java +++ b/core/src/main/java/org/bitcoinj/core/PeerGroup.java @@ -1434,6 +1434,7 @@ public class PeerGroup implements TransactionBroadcaster { ipv6Unreachable = true; log.warn("IPv6 peer connect failed due to routing failure, ignoring IPv6 addresses from now on"); } + } else { backoffMap.get(address).trackFailure(); // Put back on inactive list inactives.offer(address); From 07d85f24ad124d61f061d65a2fdf42800241dd5f Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Tue, 27 Jan 2015 18:40:58 +0100 Subject: [PATCH 29/44] SPVBlockStore: add a workaround for a Windows specific bug. We should scrap the use of mmap in this class if we can, too many platforms have odd bugs and glitches with it. --- .../org/bitcoinj/store/SPVBlockStore.java | 34 ++++++++----------- .../org/bitcoinj/store/WindowsMMapHack.java | 23 +++++++++++++ 2 files changed, 38 insertions(+), 19 deletions(-) create mode 100644 core/src/main/java/org/bitcoinj/store/WindowsMMapHack.java diff --git a/core/src/main/java/org/bitcoinj/store/SPVBlockStore.java b/core/src/main/java/org/bitcoinj/store/SPVBlockStore.java index 53d2325c..de8c0078 100644 --- a/core/src/main/java/org/bitcoinj/store/SPVBlockStore.java +++ b/core/src/main/java/org/bitcoinj/store/SPVBlockStore.java @@ -17,26 +17,19 @@ package org.bitcoinj.store; import org.bitcoinj.core.*; -import org.bitcoinj.utils.Threading; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.bitcoinj.utils.*; +import org.slf4j.*; -import javax.annotation.Nullable; -import java.io.File; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.nio.ByteBuffer; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel; -import java.nio.channels.FileLock; -import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.concurrent.locks.ReentrantLock; +import javax.annotation.*; +import java.io.*; +import java.nio.*; +import java.nio.channels.*; +import java.util.*; +import java.util.concurrent.locks.*; -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; +import static com.google.common.base.Preconditions.*; + +// TODO: Lose the mmap in this class. There are too many platform bugs that require odd workarounds. /** * An SPVBlockStore holds a limited number of block headers in a memory mapped ring buffer. With such a store, you @@ -269,9 +262,12 @@ public class SPVBlockStore implements BlockStore { public void close() throws BlockStoreException { try { buffer.force(); + if (System.getProperty("os.name").toLowerCase().contains("win")) { + log.info("Windows mmap hack: Forcing buffer cleaning"); + WindowsMMapHack.forceRelease(buffer); + } buffer = null; // Allow it to be GCd and the underlying file mapping to go away. randomAccessFile.close(); - fileLock.release(); } catch (IOException e) { throw new BlockStoreException(e); } diff --git a/core/src/main/java/org/bitcoinj/store/WindowsMMapHack.java b/core/src/main/java/org/bitcoinj/store/WindowsMMapHack.java new file mode 100644 index 00000000..d78aee58 --- /dev/null +++ b/core/src/main/java/org/bitcoinj/store/WindowsMMapHack.java @@ -0,0 +1,23 @@ +package org.bitcoinj.store; + +import sun.misc.*; +import sun.nio.ch.*; + +import java.nio.*; + +/** + *

This class knows how to force an mmap'd ByteBuffer to reliquish its file handles before it becomes garbage collected, + * by exploiting implementation details of the HotSpot JVM implementation.

+ * + *

This is required on Windows because otherwise an attempt to delete a file that is still mmapped will fail. This can + * happen when a user requests a "restore from seed" function, which involves deleting and recreating the chain file. + * At some point we should stop using mmap in SPVBlockStore and we can then delete this class.

+ * + *

It is a separate class to avoid hitting unknown imports when running on other JVMs.

+ */ +public class WindowsMMapHack { + public static void forceRelease(MappedByteBuffer buffer) { + Cleaner cleaner = ((DirectBuffer) buffer).cleaner(); + if (cleaner != null) cleaner.clean(); + } +} From 9b5307ad45fce6051ad6d7eb5cf6a0bd165f5609 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Tue, 27 Jan 2015 19:03:48 +0100 Subject: [PATCH 30/44] WalletAppKit: remove accidental Java 7-ism that was introduced a few commits ago. --- .../java/org/bitcoinj/kits/WalletAppKit.java | 43 ++++++++----------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/kits/WalletAppKit.java b/core/src/main/java/org/bitcoinj/kits/WalletAppKit.java index 75d3c9db..58186d24 100644 --- a/core/src/main/java/org/bitcoinj/kits/WalletAppKit.java +++ b/core/src/main/java/org/bitcoinj/kits/WalletAppKit.java @@ -17,36 +17,25 @@ package org.bitcoinj.kits; -import com.google.common.collect.ImmutableList; +import com.google.common.collect.*; import com.google.common.util.concurrent.*; -import com.subgraph.orchid.TorClient; +import com.subgraph.orchid.*; import org.bitcoinj.core.*; -import org.bitcoinj.net.discovery.DnsDiscovery; -import org.bitcoinj.net.discovery.PeerDiscovery; +import org.bitcoinj.net.discovery.*; import org.bitcoinj.params.*; -import org.bitcoinj.protocols.channels.StoredPaymentChannelClientStates; -import org.bitcoinj.protocols.channels.StoredPaymentChannelServerStates; -import org.bitcoinj.store.BlockStoreException; -import org.bitcoinj.store.SPVBlockStore; -import org.bitcoinj.store.WalletProtobufSerializer; -import org.bitcoinj.wallet.DeterministicSeed; -import org.bitcoinj.wallet.KeyChainGroup; -import org.bitcoinj.wallet.Protos; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.bitcoinj.protocols.channels.*; +import org.bitcoinj.store.*; +import org.bitcoinj.wallet.*; +import org.slf4j.*; -import javax.annotation.Nullable; +import javax.annotation.*; import java.io.*; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.nio.channels.FileLock; -import java.nio.file.*; -import java.util.List; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; +import java.net.*; +import java.nio.channels.*; +import java.util.*; +import java.util.concurrent.*; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; +import static com.google.common.base.Preconditions.*; /** *

Utility class that wraps the boilerplate needed to set up a new SPV bitcoinj app. Instantiate it with a directory @@ -276,7 +265,8 @@ public class WalletAppKit extends AbstractIdleService { if (chainFileExists) { log.info("Deleting the chain file in preparation from restore."); vStore.close(); - Files.delete(chainFile.toPath()); + if (!chainFile.delete()) + throw new IOException("Failed to delete chain file in preparation for restore."); vStore = new SPVBlockStore(params, chainFile); } } else { @@ -286,7 +276,8 @@ public class WalletAppKit extends AbstractIdleService { } else if (chainFileExists) { log.info("Deleting the chain file in preparation from restore."); vStore.close(); - Files.delete(chainFile.toPath()); + if (!chainFile.delete()) + throw new IOException("Failed to delete chain file in preparation for restore."); vStore = new SPVBlockStore(params, chainFile); } } From f9338519be8e0ca873de4511deda3aabccc25ec1 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Tue, 27 Jan 2015 21:52:19 +0100 Subject: [PATCH 31/44] Add 20 second timeout to HttpDiscovery --- .../main/java/org/bitcoinj/net/discovery/HttpDiscovery.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/src/main/java/org/bitcoinj/net/discovery/HttpDiscovery.java b/core/src/main/java/org/bitcoinj/net/discovery/HttpDiscovery.java index ac3537f5..318573fc 100644 --- a/core/src/main/java/org/bitcoinj/net/discovery/HttpDiscovery.java +++ b/core/src/main/java/org/bitcoinj/net/discovery/HttpDiscovery.java @@ -38,6 +38,8 @@ import static com.google.common.base.Preconditions.checkArgument; * This is not currently in use by the Bitcoin community, but rather, is here for experimentation. */ public class HttpDiscovery implements PeerDiscovery { + private static final int TIMEOUT_SECS = 20; + private final ECKey pubkey; private final URI uri; private final NetworkParameters params; @@ -57,6 +59,8 @@ public class HttpDiscovery implements PeerDiscovery { public InetSocketAddress[] getPeers(long timeoutValue, TimeUnit timeoutUnit) throws PeerDiscoveryException { try { HttpURLConnection conn = (HttpURLConnection) uri.toURL().openConnection(); + conn.setReadTimeout(TIMEOUT_SECS * 1000); + conn.setConnectTimeout(TIMEOUT_SECS * 1000); conn.setRequestProperty("User-Agent", "bitcoinj " + VersionMessage.BITCOINJ_VERSION); InputStream stream = conn.getInputStream(); GZIPInputStream zip = new GZIPInputStream(stream); From f4cce4c3c0dee3bb73dc294f68b099afeab4a766 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Wed, 28 Jan 2015 16:13:24 +0100 Subject: [PATCH 32/44] ECKey: add a sanity check for private keys that are zero or one. This should never happen but there have been reports from the wild that somehow once or twice someone managed to get a private key of zero into their wallet. --- core/src/main/java/org/bitcoinj/core/ECKey.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/src/main/java/org/bitcoinj/core/ECKey.java b/core/src/main/java/org/bitcoinj/core/ECKey.java index dab26de7..afa5c2cf 100644 --- a/core/src/main/java/org/bitcoinj/core/ECKey.java +++ b/core/src/main/java/org/bitcoinj/core/ECKey.java @@ -182,6 +182,11 @@ public class ECKey implements EncryptableItem, Serializable { } protected ECKey(@Nullable BigInteger priv, ECPoint pub) { + if (priv != null) { + // Try and catch buggy callers or bad key imports, etc. + checkArgument(!priv.equals(BigInteger.ZERO)); + checkArgument(!priv.equals(BigInteger.ONE)); + } this.priv = priv; this.pub = new LazyECPoint(checkNotNull(pub)); } From ad4fb5103cfc6d62fa778b901e0a987909426411 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Wed, 28 Jan 2015 16:20:51 +0100 Subject: [PATCH 33/44] NioClientManager: use a daemon thread so GUI apps can quit fast if they want to without a slow/hung network thread keeping things hanging around. This may break compatibility with apps that expect to be able to start bitcoinj and then exit the main thread: if it causes too many problems this change may be reverted. --- .../org/bitcoinj/net/NioClientManager.java | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/net/NioClientManager.java b/core/src/main/java/org/bitcoinj/net/NioClientManager.java index 0660000c..efa16d76 100644 --- a/core/src/main/java/org/bitcoinj/net/NioClientManager.java +++ b/core/src/main/java/org/bitcoinj/net/NioClientManager.java @@ -17,10 +17,7 @@ package org.bitcoinj.net; import com.google.common.base.Throwables; -import com.google.common.util.concurrent.AbstractExecutionThreadService; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.SettableFuture; +import com.google.common.util.concurrent.*; import org.slf4j.LoggerFactory; import java.io.IOException; @@ -29,7 +26,7 @@ import java.net.SocketAddress; import java.nio.channels.*; import java.nio.channels.spi.SelectorProvider; import java.util.*; -import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.*; /** * A class which manages a set of client connections. Uses Java NIO to select network events and processes them in a @@ -185,4 +182,16 @@ public class NioClientManager extends AbstractExecutionThreadService implements handler.closeConnection(); // Removes handler from connectedHandlers before returning } } + + @Override + protected Executor executor() { + return new Executor() { + @Override + public void execute(Runnable command) { + Thread thread = new Thread(command, "NioClientManager"); + thread.setDaemon(true); + thread.start(); + } + }; + } } From fe2aff49ae1ab23af9bed01aa131062f47a2e4e7 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Wed, 28 Jan 2015 16:21:40 +0100 Subject: [PATCH 34/44] DnsDiscovery: attempt workaround for apparent lack of working thread safety on some Linux platform C libraries. --- .../bitcoinj/net/discovery/DnsDiscovery.java | 17 +++++++++++++---- .../net/discovery/MultiplexingDiscovery.java | 8 ++++++-- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/net/discovery/DnsDiscovery.java b/core/src/main/java/org/bitcoinj/net/discovery/DnsDiscovery.java index c8d0998e..c6a0e403 100644 --- a/core/src/main/java/org/bitcoinj/net/discovery/DnsDiscovery.java +++ b/core/src/main/java/org/bitcoinj/net/discovery/DnsDiscovery.java @@ -17,11 +17,10 @@ package org.bitcoinj.net.discovery; -import org.bitcoinj.core.NetworkParameters; +import org.bitcoinj.core.*; +import org.bitcoinj.utils.*; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.UnknownHostException; +import java.net.*; import java.util.*; import java.util.concurrent.*; @@ -63,6 +62,16 @@ public class DnsDiscovery extends MultiplexingDiscovery { return discoveries; } + @Override + protected ExecutorService createExecutor() { + // Attempted workaround for reported bugs on Linux in which gethostbyname does not appear to be properly + // thread safe and can cause segfaults on some libc versions. + if (System.getProperty("os.name").toLowerCase().contains("linux")) + return Executors.newSingleThreadExecutor(new DaemonThreadFactory()); + else + return Executors.newFixedThreadPool(seeds.size(), new DaemonThreadFactory()); + } + /** Implements discovery from a single DNS host. */ public static class DnsSeedDiscovery implements PeerDiscovery { private final String hostname; diff --git a/core/src/main/java/org/bitcoinj/net/discovery/MultiplexingDiscovery.java b/core/src/main/java/org/bitcoinj/net/discovery/MultiplexingDiscovery.java index f65a4365..2d9f9110 100644 --- a/core/src/main/java/org/bitcoinj/net/discovery/MultiplexingDiscovery.java +++ b/core/src/main/java/org/bitcoinj/net/discovery/MultiplexingDiscovery.java @@ -33,7 +33,7 @@ import static com.google.common.base.Preconditions.checkArgument; /** * MultiplexingDiscovery queries multiple PeerDiscovery objects, shuffles their responses and then returns the results, * thus selecting randomly between them and reducing the influence of any particular seed. Any that don't respond - * within the timeout are ignored. Backends are queried in parallel. Backends may block + * within the timeout are ignored. Backends are queried in parallel. Backends may block. */ public class MultiplexingDiscovery implements PeerDiscovery { private static final Logger log = LoggerFactory.getLogger(MultiplexingDiscovery.class); @@ -53,7 +53,7 @@ public class MultiplexingDiscovery implements PeerDiscovery { @Override public InetSocketAddress[] getPeers(final long timeoutValue, final TimeUnit timeoutUnit) throws PeerDiscoveryException { - vThreadPool = Executors.newFixedThreadPool(seeds.size(), new DaemonThreadFactory()); + vThreadPool = createExecutor(); try { List> tasks = Lists.newArrayList(); for (final PeerDiscovery seed : seeds) { @@ -93,6 +93,10 @@ public class MultiplexingDiscovery implements PeerDiscovery { } } + protected ExecutorService createExecutor() { + return Executors.newFixedThreadPool(seeds.size(), new DaemonThreadFactory()); + } + @Override public void shutdown() { ExecutorService tp = vThreadPool; From fd10654143eddcb38caf4606ff9c28c63bb67bfb Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Wed, 28 Jan 2015 18:34:27 +0100 Subject: [PATCH 35/44] Fix wallet tests that were using 1 as a private key. --- core/src/test/java/org/bitcoinj/core/WalletTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/test/java/org/bitcoinj/core/WalletTest.java b/core/src/test/java/org/bitcoinj/core/WalletTest.java index 4ea4edff..9cc6416d 100644 --- a/core/src/test/java/org/bitcoinj/core/WalletTest.java +++ b/core/src/test/java/org/bitcoinj/core/WalletTest.java @@ -3092,7 +3092,7 @@ public class WalletTest extends TestWithWallet { @Test(expected = java.lang.IllegalStateException.class) public void sendCoinsNoBroadcasterTest() throws InsufficientMoneyException { - ECKey key = ECKey.fromPrivate(BigInteger.ONE); + ECKey key = ECKey.fromPrivate(BigInteger.TEN); Address notMyAddr = key.toAddress(params); SendRequest req = SendRequest.to(notMyAddr.getParameters(), key, SATOSHI.multiply(12)); wallet.sendCoins(req); @@ -3100,7 +3100,7 @@ public class WalletTest extends TestWithWallet { @Test public void sendCoinsWithBroadcasterTest() throws InsufficientMoneyException { - ECKey key = ECKey.fromPrivate(BigInteger.ONE); + ECKey key = ECKey.fromPrivate(BigInteger.TEN); Address notMyAddr = key.toAddress(params); receiveATransactionAmount(wallet, myAddress, Coin.COIN); MockTransactionBroadcaster broadcaster = new MockTransactionBroadcaster(wallet); From f1f07df11b68eab98eb0db5a9138763ec78e6d82 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Wed, 28 Jan 2015 19:37:13 +0100 Subject: [PATCH 36/44] ECKey: extend the comment about why private keys of zero and one are now forbidden --- core/src/main/java/org/bitcoinj/core/ECKey.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/bitcoinj/core/ECKey.java b/core/src/main/java/org/bitcoinj/core/ECKey.java index afa5c2cf..e9b1cbef 100644 --- a/core/src/main/java/org/bitcoinj/core/ECKey.java +++ b/core/src/main/java/org/bitcoinj/core/ECKey.java @@ -183,7 +183,9 @@ public class ECKey implements EncryptableItem, Serializable { protected ECKey(@Nullable BigInteger priv, ECPoint pub) { if (priv != null) { - // Try and catch buggy callers or bad key imports, etc. + // Try and catch buggy callers or bad key imports, etc. Zero and one are special because these are often + // used as sentinel values and because scripting languages have a habit of auto-casting true and false to + // 1 and 0 or vice-versa. Type confusion bugs could therefore result in private keys with these values. checkArgument(!priv.equals(BigInteger.ZERO)); checkArgument(!priv.equals(BigInteger.ONE)); } From f3fa050c09f0514f936f101c2ea783b189bd959c Mon Sep 17 00:00:00 2001 From: Oscar Guindzberg Date: Wed, 28 Jan 2015 17:17:37 -0300 Subject: [PATCH 37/44] DeterministicKeyChain Builder - add seedCreationTimeSecs() --- .../main/java/org/bitcoinj/wallet/DeterministicKeyChain.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/src/main/java/org/bitcoinj/wallet/DeterministicKeyChain.java b/core/src/main/java/org/bitcoinj/wallet/DeterministicKeyChain.java index dc04931f..befdc5b2 100644 --- a/core/src/main/java/org/bitcoinj/wallet/DeterministicKeyChain.java +++ b/core/src/main/java/org/bitcoinj/wallet/DeterministicKeyChain.java @@ -220,6 +220,11 @@ public class DeterministicKeyChain implements EncryptableKeyChain { return self(); } + public T seedCreationTimeSecs(long seedCreationTimeSecs) { + this.seedCreationTimeSecs = seedCreationTimeSecs; + return self(); + } + /** The passphrase to use with the generated mnemonic, or null if you would like to use the default empty string. Currently must be the empty string. */ public T passphrase(String passphrase) { // FIXME support non-empty passphrase From c981555be4567026d743b50afc404614c035dd78 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Thu, 29 Jan 2015 19:19:28 +0100 Subject: [PATCH 38/44] RejectMessage: add a c'tor for initialising a new message and fix a bug in serialisation. --- .../main/java/org/bitcoinj/core/RejectMessage.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/core/RejectMessage.java b/core/src/main/java/org/bitcoinj/core/RejectMessage.java index 2b8d23de..8ac8d6dd 100644 --- a/core/src/main/java/org/bitcoinj/core/RejectMessage.java +++ b/core/src/main/java/org/bitcoinj/core/RejectMessage.java @@ -72,8 +72,13 @@ public class RejectMessage extends Message { super(params, payload, 0); } - public RejectMessage(NetworkParameters params, byte[] payload, boolean parseLazy, boolean parseRetain, int length) throws ProtocolException { - super(params, payload, 0, parseLazy, parseRetain, length); + /** Constructs a reject message that fingers the object with the given hash as rejected for the given reason. */ + public RejectMessage(NetworkParameters params, RejectCode code, Sha256Hash hash, String message, String reason) throws ProtocolException { + super(params); + this.code = code; + this.messageHash = hash; + this.message = message; + this.reason = reason; } @Override @@ -102,7 +107,7 @@ public class RejectMessage extends Message { stream.write(new VarInt(reasonBytes.length).encode()); stream.write(reasonBytes); if (message.equals("block") || message.equals("tx")) - stream.write(messageHash.getBytes()); + stream.write(Utils.reverseBytes(messageHash.getBytes())); } /** From bc60f0d1f2be41e0154ec2c1bbc8c53ae4a92faf Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Thu, 29 Jan 2015 19:24:02 +0100 Subject: [PATCH 39/44] TransactionBroadcast: only consider a tx rejected if it has more than half peers signalling a reject. --- .../bitcoinj/core/TransactionBroadcast.java | 34 +++++++++++-------- .../core/TransactionBroadcastTest.java | 22 ++++++++++++ 2 files changed, 42 insertions(+), 14 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/core/TransactionBroadcast.java b/core/src/main/java/org/bitcoinj/core/TransactionBroadcast.java index 0b817751..1cb02797 100644 --- a/core/src/main/java/org/bitcoinj/core/TransactionBroadcast.java +++ b/core/src/main/java/org/bitcoinj/core/TransactionBroadcast.java @@ -16,18 +16,14 @@ package org.bitcoinj.core; -import org.bitcoinj.utils.Threading; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Joiner; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.SettableFuture; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import com.google.common.annotations.*; +import com.google.common.base.*; +import com.google.common.util.concurrent.*; +import org.bitcoinj.utils.*; +import org.slf4j.*; -import javax.annotation.Nullable; -import java.util.Collections; -import java.util.List; -import java.util.Random; +import javax.annotation.*; +import java.util.*; /** * Represents a single transaction broadcast that we are performing. A broadcast occurs after a new transaction is created @@ -49,8 +45,12 @@ public class TransactionBroadcast { /** Used for shuffling the peers before broadcast: unit tests can replace this to make themselves deterministic. */ @VisibleForTesting public static Random random = new Random(); + private Transaction pinnedTx; + // Tracks which nodes sent us a reject message about this broadcast, if any. Useful for debugging. + private Map rejects = Collections.synchronizedMap(new HashMap()); + // TODO: Context being owned by BlockChain isn't right w.r.t future intentions so it shouldn't really be optional here. TransactionBroadcast(PeerGroup peerGroup, @Nullable Context context, Transaction tx) { this.peerGroup = peerGroup; @@ -73,8 +73,14 @@ public class TransactionBroadcast { if (m instanceof RejectMessage) { RejectMessage rejectMessage = (RejectMessage)m; if (tx.getHash().equals(rejectMessage.getRejectedObjectHash())) { - future.setException(new RejectedTransactionException(tx, rejectMessage)); - peerGroup.removeEventListener(this); + rejects.put(peer, rejectMessage); + int size = rejects.size(); + long threshold = Math.round(numWaitingFor / 2.0); + if (size > threshold) { + log.warn("Threshold for considering broadcast rejected has been reached ({}/{})", size, threshold); + future.setException(new RejectedTransactionException(tx, rejectMessage)); + peerGroup.removeEventListener(this); + } } } return m; @@ -147,7 +153,7 @@ public class TransactionBroadcast { @Override public void onConfidenceChanged(TransactionConfidence conf, ChangeReason reason) { // The number of peers that announced this tx has gone up. - int numSeenPeers = conf.numBroadcastPeers(); + int numSeenPeers = conf.numBroadcastPeers() + rejects.size(); boolean mined = tx.getAppearsInHashes() != null; log.info("broadcastTransaction: {}: TX {} seen by {} peers{}", reason, pinnedTx.getHashAsString(), numSeenPeers, mined ? " and mined" : ""); diff --git a/core/src/test/java/org/bitcoinj/core/TransactionBroadcastTest.java b/core/src/test/java/org/bitcoinj/core/TransactionBroadcastTest.java index 6706672b..15536154 100644 --- a/core/src/test/java/org/bitcoinj/core/TransactionBroadcastTest.java +++ b/core/src/test/java/org/bitcoinj/core/TransactionBroadcastTest.java @@ -32,6 +32,7 @@ import org.junit.runners.Parameterized; import java.util.Arrays; import java.util.Collection; import java.util.Random; +import java.util.concurrent.*; import static org.bitcoinj.core.Coin.*; import static com.google.common.base.Preconditions.checkNotNull; @@ -97,6 +98,27 @@ public class TransactionBroadcastTest extends TestWithPeerGroup { assertTrue(future.isDone()); } + @Test + public void rejectHandling() throws Exception { + InboundMessageQueuer[] channels = { connectPeer(0), connectPeer(1), connectPeer(2), connectPeer(3), connectPeer(4) }; + Transaction tx = new Transaction(params); + TransactionBroadcast broadcast = new TransactionBroadcast(peerGroup, blockChain.getContext(), tx); + ListenableFuture future = broadcast.broadcast(); + // 0 and 3 are randomly selected to receive the broadcast. + assertEquals(tx, outbound(channels[1])); + assertEquals(tx, outbound(channels[2])); + assertEquals(tx, outbound(channels[4])); + RejectMessage reject = new RejectMessage(params, RejectMessage.RejectCode.DUST, tx.getHash(), "tx", "dust"); + inbound(channels[1], reject); + inbound(channels[4], reject); + try { + future.get(); + fail(); + } catch (ExecutionException e) { + assertEquals(RejectedTransactionException.class, e.getCause().getClass()); + } + } + @Test public void retryFailedBroadcast() throws Exception { // If we create a spend, it's sent to a peer that swallows it, and the peergroup is removed/re-added then From 653773d67a05cd3a7f56a485a07a336671a21eb6 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Thu, 29 Jan 2015 19:24:14 +0100 Subject: [PATCH 40/44] PeerGroup: don't try and trigger connections during shutdown --- core/src/main/java/org/bitcoinj/core/PeerGroup.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/bitcoinj/core/PeerGroup.java b/core/src/main/java/org/bitcoinj/core/PeerGroup.java index 09a55e23..7c1497a7 100644 --- a/core/src/main/java/org/bitcoinj/core/PeerGroup.java +++ b/core/src/main/java/org/bitcoinj/core/PeerGroup.java @@ -507,7 +507,8 @@ public class PeerGroup implements TransactionBroadcaster { private void triggerConnections() { // Run on a background thread due to the need to potentially retry and back off in the background. - executor.execute(triggerConnectionsJob); + if (!executor.isShutdown()) + executor.execute(triggerConnectionsJob); } /** The maximum number of connections that we will create to peers. */ From 0d51cee24fca19410d357326cd3651414356bc49 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Mon, 2 Feb 2015 17:29:50 +0100 Subject: [PATCH 41/44] PeerGroup: ignore another source of RejectedExecutionException during shutdown --- core/src/main/java/org/bitcoinj/core/PeerGroup.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/core/PeerGroup.java b/core/src/main/java/org/bitcoinj/core/PeerGroup.java index 7c1497a7..b587096c 100644 --- a/core/src/main/java/org/bitcoinj/core/PeerGroup.java +++ b/core/src/main/java/org/bitcoinj/core/PeerGroup.java @@ -1039,7 +1039,7 @@ public class PeerGroup implements TransactionBroadcaster { return inFlightRecalculations.get(mode); inFlightRecalculations.put(mode, future); } - executor.execute(new Runnable() { + Runnable command = new Runnable() { @Override public void run() { try { @@ -1089,7 +1089,12 @@ public class PeerGroup implements TransactionBroadcaster { } future.set(result.filter); } - }); + }; + try { + executor.execute(command); + } catch (RejectedExecutionException e) { + // Can happen during shutdown. + } return future; } From c72c48cd586c510befb5262b39648a85fc3ead71 Mon Sep 17 00:00:00 2001 From: Sean Gilligan Date: Fri, 2 Jan 2015 23:02:25 -0800 Subject: [PATCH 42/44] Add Comparable to VersionedChecksummedBytes * Add Comparable interface to VersionedChecksummedBytes * Add compareTo() method to VersionedChecksummedBytes * Add test for VersionedChecksummedBytes * Add tests for Address --- .../core/VersionedChecksummedBytes.java | 22 +++++++-- .../java/org/bitcoinj/core/AddressTest.java | 47 +++++++++++++++++++ .../core/VersionedChecksummedBytesTest.java | 7 +++ 3 files changed, 73 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/core/VersionedChecksummedBytes.java b/core/src/main/java/org/bitcoinj/core/VersionedChecksummedBytes.java index 1907cc9b..c919329d 100644 --- a/core/src/main/java/org/bitcoinj/core/VersionedChecksummedBytes.java +++ b/core/src/main/java/org/bitcoinj/core/VersionedChecksummedBytes.java @@ -22,6 +22,7 @@ import java.io.Serializable; import java.util.Arrays; import com.google.common.base.Objects; +import com.google.common.primitives.UnsignedBytes; /** *

In Bitcoin the following format is often used to represent some type of key:

@@ -31,7 +32,7 @@ import com.google.common.base.Objects; *

and the result is then Base58 encoded. This format is used for addresses, and private keys exported using the * dumpprivkey command.

*/ -public class VersionedChecksummedBytes implements Serializable, Cloneable { +public class VersionedChecksummedBytes implements Serializable, Cloneable, Comparable { protected final int version; protected byte[] bytes; @@ -80,8 +81,6 @@ public class VersionedChecksummedBytes implements Serializable, Cloneable { } /** - * Returns the "version" or "header" byte: the first byte of the data. This is used to disambiguate what the - * contents apply to, for example, which network the key or address is valid on. * {@inheritDoc} * * This implementation narrows the return type to VersionedChecksummedBytes @@ -93,8 +92,25 @@ public class VersionedChecksummedBytes implements Serializable, Cloneable { return (VersionedChecksummedBytes) super.clone(); } + /** + * {@inheritDoc} + * + * This implementation uses an optimized Google Guava method to compare bytes. + */ + @Override + public int compareTo(VersionedChecksummedBytes o) { + int versionCompare = Integer.valueOf(this.version).compareTo(Integer.valueOf(o.version)); // JDK 6 way + if (versionCompare == 0) { + // Would there be a performance benefit to caching the comparator? + return UnsignedBytes.lexicographicalComparator().compare(this.bytes, o.bytes); + } else { + return versionCompare; + } + } /** + * Returns the "version" or "header" byte: the first byte of the data. This is used to disambiguate what the + * contents apply to, for example, which network the key or address is valid on. * * @return A positive number between 0 and 255. */ diff --git a/core/src/test/java/org/bitcoinj/core/AddressTest.java b/core/src/test/java/org/bitcoinj/core/AddressTest.java index 3269ab0c..8b7209ba 100644 --- a/core/src/test/java/org/bitcoinj/core/AddressTest.java +++ b/core/src/test/java/org/bitcoinj/core/AddressTest.java @@ -178,4 +178,51 @@ public class AddressTest { assertNotSame(a, b); } + @Test + public void comparisonCloneEqualTo() throws Exception { + Address a = new Address(mainParams, "1Dorian4RoXcnBv9hnQ4Y2C1an6NJ4UrjX"); + Address b = a.clone(); + + int result = a.compareTo(b); + assertEquals(0, result); + } + + @Test + public void comparisonEqualTo() throws Exception { + Address a = new Address(mainParams, "1Dorian4RoXcnBv9hnQ4Y2C1an6NJ4UrjX"); + Address b = a.clone(); + + int result = a.compareTo(b); + assertEquals(0, result); + } + + @Test + public void comparisonLessThan() throws Exception { + Address a = new Address(mainParams, "1Dorian4RoXcnBv9hnQ4Y2C1an6NJ4UrjX"); + Address b = new Address(mainParams, "1EXoDusjGwvnjZUyKkxZ4UHEf77z6A5S4P"); + + int result = a.compareTo(b); + assertTrue(result < 0); + } + + @Test + public void comparisonGreaterThan() throws Exception { + Address a = new Address(mainParams, "1EXoDusjGwvnjZUyKkxZ4UHEf77z6A5S4P"); + Address b = new Address(mainParams, "1Dorian4RoXcnBv9hnQ4Y2C1an6NJ4UrjX"); + + int result = a.compareTo(b); + assertTrue(result > 0); + } + + @Test + public void comparisonBytesVsString() throws Exception { + // TODO: To properly test this we need a much larger data set + Address a = new Address(mainParams, "1Dorian4RoXcnBv9hnQ4Y2C1an6NJ4UrjX"); + Address b = new Address(mainParams, "1EXoDusjGwvnjZUyKkxZ4UHEf77z6A5S4P"); + + int resultBytes = a.compareTo(b); + int resultsString = a.toString().compareTo(b.toString()); + assertTrue( resultBytes < 0 ); + assertTrue( resultsString < 0 ); + } } diff --git a/core/src/test/java/org/bitcoinj/core/VersionedChecksummedBytesTest.java b/core/src/test/java/org/bitcoinj/core/VersionedChecksummedBytesTest.java index 3b78076f..2b03ca25 100644 --- a/core/src/test/java/org/bitcoinj/core/VersionedChecksummedBytesTest.java +++ b/core/src/test/java/org/bitcoinj/core/VersionedChecksummedBytesTest.java @@ -52,4 +52,11 @@ public class VersionedChecksummedBytesTest { assertNotSame(a, b); } + @Test + public void comparisonCloneEqualTo() throws Exception { + VersionedChecksummedBytes a = new VersionedChecksummedBytes(testParams.getAddressHeader(), HEX.decode("fda79a24e50ff70ff42f7d89585da5bd19d9e5cc")); + VersionedChecksummedBytes b = a.clone(); + + assertTrue(a.compareTo(b) == 0); + } } From 815c4b9ced0c58ddf7a7e266ca29b2273ef15dc7 Mon Sep 17 00:00:00 2001 From: Andreas Schildbach Date: Mon, 26 Jan 2015 14:55:19 +0100 Subject: [PATCH 43/44] Add current-receive-addr action to wallet-tool. --- tools/src/main/java/org/bitcoinj/tools/WalletTool.java | 7 +++++++ .../main/resources/org/bitcoinj/tools/wallet-tool-help.txt | 2 ++ 2 files changed, 9 insertions(+) diff --git a/tools/src/main/java/org/bitcoinj/tools/WalletTool.java b/tools/src/main/java/org/bitcoinj/tools/WalletTool.java index 1e16abff..02114bc5 100644 --- a/tools/src/main/java/org/bitcoinj/tools/WalletTool.java +++ b/tools/src/main/java/org/bitcoinj/tools/WalletTool.java @@ -166,6 +166,7 @@ public class WalletTool { ADD_KEY, ADD_ADDR, DELETE_KEY, + CURRENT_RECEIVE_ADDR, SYNC, RESET, SEND, @@ -342,6 +343,7 @@ public class WalletTool { case ADD_KEY: addKey(); break; case ADD_ADDR: addAddr(); break; case DELETE_KEY: deleteKey(); break; + case CURRENT_RECEIVE_ADDR: currentReceiveAddr(); break; case RESET: reset(); break; case SYNC: syncChain(); break; case SEND: @@ -1074,6 +1076,11 @@ public class WalletTool { wallet.removeKey(key); } + private static void currentReceiveAddr() { + ECKey key = wallet.currentReceiveKey(); + System.out.println(key.toAddress(params) + " " + key); + } + private static void dumpWallet() throws BlockStoreException { // Setup to get the chain height so we can estimate lock times, but don't wipe the transactions if it's not // there just for the dump case. diff --git a/tools/src/main/resources/org/bitcoinj/tools/wallet-tool-help.txt b/tools/src/main/resources/org/bitcoinj/tools/wallet-tool-help.txt index 662fe181..f16b011b 100644 --- a/tools/src/main/resources/org/bitcoinj/tools/wallet-tool-help.txt +++ b/tools/src/main/resources/org/bitcoinj/tools/wallet-tool-help.txt @@ -21,6 +21,8 @@ Usage: wallet-tool --flags action-name If --lookahead-size is specified, pre-generate at least this many keys ahead of where we are. add-addr Requires --addr to be specified, and adds it as a watching address. delete-key Removes the key specified by --pubkey or --addr from the wallet. + current-receive-addr Prints the current receive address, deriving one if needed. Addresses derived with this action are + independent of addresses derived with the add-key action. sync Sync the wallet with the latest block chain (download new transactions). If the chain file does not exist this will RESET the wallet. reset Deletes all transactions from the wallet, for if you want to replay the chain. From 212aa41143f1302b55dff47226741109ac1e45dc Mon Sep 17 00:00:00 2001 From: Carlos Lopez-Camey Date: Sat, 7 Feb 2015 17:04:22 -0600 Subject: [PATCH 44/44] Updates PeerGroup's javadoc: PeerGroup is not a guava service as from 27bc229 --- core/src/main/java/org/bitcoinj/core/PeerGroup.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/core/PeerGroup.java b/core/src/main/java/org/bitcoinj/core/PeerGroup.java index b587096c..ee71e492 100644 --- a/core/src/main/java/org/bitcoinj/core/PeerGroup.java +++ b/core/src/main/java/org/bitcoinj/core/PeerGroup.java @@ -70,13 +70,12 @@ import static com.google.common.base.Preconditions.checkState; *

The PeerGroup can broadcast a transaction to the currently connected set of peers. It can * also handle download of the blockchain from peers, restarting the process when peers die.

* - *

PeerGroup implements the {@link Service} interface. This means before it will do anything, - * you must call the {@link com.google.common.util.concurrent.Service#start()} method (which returns - * a future) or {@link com.google.common.util.concurrent.Service#startAndWait()} method, which will block - * until peer discovery is completed and some outbound connections have been initiated (it will return - * before handshaking is done, however). You should call {@link com.google.common.util.concurrent.Service#stop()} - * when finished. Note that not all methods of PeerGroup are safe to call from a UI thread as some may do - * network IO, but starting and stopping the service should be fine.

+ *

A PeerGroup won't do anything until you call the {@link PeerGroup#start()} method + * which will block until peer discovery is completed and some outbound connections + * have been initiated (it will return before handshaking is done, however). + * You should call {@link PeerGroup#stop()} when finished. Note that not all methods + * of PeerGroup are safe to call from a UI thread as some may do network IO, + * but starting and stopping the service should be fine.

*/ public class PeerGroup implements TransactionBroadcaster { private static final Logger log = LoggerFactory.getLogger(PeerGroup.class);