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);