Address
.
+ */
+ @Override
+ public Address clone() throws CloneNotSupportedException {
+ return (Address) super.clone();
+ }
}
diff --git a/core/src/main/java/com/dogecoin/dogecoinj/core/ECKey.java b/core/src/main/java/com/dogecoin/dogecoinj/core/ECKey.java
index dc2b6516..f4ad6810 100644
--- a/core/src/main/java/com/dogecoin/dogecoinj/core/ECKey.java
+++ b/core/src/main/java/com/dogecoin/dogecoinj/core/ECKey.java
@@ -182,6 +182,13 @@ 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. 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));
+ }
this.priv = priv;
this.pub = new LazyECPoint(checkNotNull(pub));
}
diff --git a/core/src/main/java/com/dogecoin/dogecoinj/core/GetUTXOsMessage.java b/core/src/main/java/com/dogecoin/dogecoinj/core/GetUTXOsMessage.java
index 6602e3d8..21be7020 100644
--- a/core/src/main/java/com/dogecoin/dogecoinj/core/GetUTXOsMessage.java
+++ b/core/src/main/java/com/dogecoin/dogecoinj/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 = 70003;
+ 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 ImmutableListThe 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); - 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; @@ -501,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. */ @@ -885,6 +892,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 :( } @@ -898,6 +906,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); @@ -1030,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 { @@ -1080,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; } @@ -1168,10 +1182,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); + ListenableFutureIn 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 { +public class VersionedChecksummedBytes implements Serializable, Cloneable, ComparableVersionedChecksummedBytes
+ * 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();
+ }
+
+ /**
+ * {@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.
diff --git a/core/src/main/java/com/dogecoin/dogecoinj/core/Wallet.java b/core/src/main/java/com/dogecoin/dogecoinj/core/Wallet.java
index 3edfe92f..8b65141a 100644
--- a/core/src/main/java/com/dogecoin/dogecoinj/core/Wallet.java
+++ b/core/src/main/java/com/dogecoin/dogecoinj/core/Wallet.java
@@ -3601,9 +3601,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;
@@ -3611,9 +3610,8 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
throw new ExceededMaxTransactionSize();
final Coin calculatedFee = totalInput.subtract(totalOutput);
- 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
diff --git a/core/src/main/java/com/dogecoin/dogecoinj/crypto/KeyCrypterScrypt.java b/core/src/main/java/com/dogecoin/dogecoinj/crypto/KeyCrypterScrypt.java
index 760104bd..69038497 100644
--- a/core/src/main/java/com/dogecoin/dogecoinj/crypto/KeyCrypterScrypt.java
+++ b/core/src/main/java/com/dogecoin/dogecoinj/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;
diff --git a/core/src/main/java/com/dogecoin/dogecoinj/kits/WalletAppKit.java b/core/src/main/java/com/dogecoin/dogecoinj/kits/WalletAppKit.java
index ac082509..7a308992 100644
--- a/core/src/main/java/com/dogecoin/dogecoinj/kits/WalletAppKit.java
+++ b/core/src/main/java/com/dogecoin/dogecoinj/kits/WalletAppKit.java
@@ -15,36 +15,27 @@
* limitations under the License.
*/
-package com.dogecoin.dogecoinj.kits;
+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 com.dogecoin.dogecoinj.core.*;
-import com.dogecoin.dogecoinj.net.discovery.DnsDiscovery;
-import com.dogecoin.dogecoinj.net.discovery.PeerDiscovery;
-import com.dogecoin.dogecoinj.protocols.channels.StoredPaymentChannelClientStates;
-import com.dogecoin.dogecoinj.protocols.channels.StoredPaymentChannelServerStates;
-import com.dogecoin.dogecoinj.store.BlockStoreException;
-import com.dogecoin.dogecoinj.store.SPVBlockStore;
-import com.dogecoin.dogecoinj.store.WalletProtobufSerializer;
-import com.dogecoin.dogecoinj.wallet.DeterministicSeed;
-import com.dogecoin.dogecoinj.wallet.KeyChainGroup;
-import com.dogecoin.dogecoinj.wallet.Protos;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import com.dogecoin.dogecoinj.net.discovery.*;
+import com.dogecoin.dogecoinj.params.*;
+import com.dogecoin.dogecoinj.protocols.channels.*;
+import com.dogecoin.dogecoinj.store.*;
+import com.dogecoin.dogecoinj.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.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
@@ -275,7 +266,7 @@ public class WalletAppKit extends AbstractIdleService {
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.");
+ throw new IOException("Failed to delete chain file in preparation for restore.");
vStore = new SPVBlockStore(params, chainFile);
}
} else {
@@ -286,7 +277,7 @@ public class WalletAppKit extends AbstractIdleService {
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.");
+ throw new IOException("Failed to delete chain file in preparation for restore.");
vStore = new SPVBlockStore(params, chainFile);
}
}
@@ -301,7 +292,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);
diff --git a/core/src/main/java/com/dogecoin/dogecoinj/net/ClientConnectionManager.java b/core/src/main/java/com/dogecoin/dogecoinj/net/ClientConnectionManager.java
index 0ac01be4..71c5b444 100644
--- a/core/src/main/java/com/dogecoin/dogecoinj/net/ClientConnectionManager.java
+++ b/core/src/main/java/com/dogecoin/dogecoinj/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
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(); + } +} diff --git a/core/src/main/java/com/dogecoin/dogecoinj/utils/BriefLogFormatter.java b/core/src/main/java/com/dogecoin/dogecoinj/utils/BriefLogFormatter.java index cb34bfd4..0390114a 100644 --- a/core/src/main/java/com/dogecoin/dogecoinj/utils/BriefLogFormatter.java +++ b/core/src/main/java/com/dogecoin/dogecoinj/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]; diff --git a/core/src/main/java/com/dogecoin/dogecoinj/utils/Threading.java b/core/src/main/java/com/dogecoin/dogecoinj/utils/Threading.java index be73781f..dc8ebae3 100644 --- a/core/src/main/java/com/dogecoin/dogecoinj/utils/Threading.java +++ b/core/src/main/java/com/dogecoin/dogecoinj/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" + diff --git a/core/src/main/java/com/dogecoin/dogecoinj/wallet/DeterministicKeyChain.java b/core/src/main/java/com/dogecoin/dogecoinj/wallet/DeterministicKeyChain.java index fb2c8f10..23d6d50e 100644 --- a/core/src/main/java/com/dogecoin/dogecoinj/wallet/DeterministicKeyChain.java +++ b/core/src/main/java/com/dogecoin/dogecoinj/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,16 @@ public class DeterministicKeyChain implements EncryptableKeyChain { return self(); } + public T watchingKey(DeterministicKey watchingKey) { + this.watchingKey = watchingKey; + 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 @@ -223,7 +234,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 +243,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/com/dogecoin/dogecoinj/wallet/MarriedKeyChain.java b/core/src/main/java/com/dogecoin/dogecoinj/wallet/MarriedKeyChain.java index c54dca7e..a80bed83 100644 --- a/core/src/main/java/com/dogecoin/dogecoinj/wallet/MarriedKeyChain.java +++ b/core/src/main/java/com/dogecoin/dogecoinj/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); } diff --git a/core/src/main/resources/org.bitcoin.production.checkpoints b/core/src/main/resources/org.bitcoin.production.checkpoints index d92d362d..a05db876 100644 Binary files a/core/src/main/resources/org.bitcoin.production.checkpoints and b/core/src/main/resources/org.bitcoin.production.checkpoints differ diff --git a/core/src/test/java/com/dogecoin/dogecoinj/core/AddressTest.java b/core/src/test/java/com/dogecoin/dogecoinj/core/AddressTest.java index 088f48db..dbca86b2 100644 --- a/core/src/test/java/com/dogecoin/dogecoinj/core/AddressTest.java +++ b/core/src/test/java/com/dogecoin/dogecoinj/core/AddressTest.java @@ -168,4 +168,61 @@ public class AddressTest { Address address = Address.fromP2SHScript(mainParams, p2shScript); assertEquals("ACdJj7YT7dJkV6bv6cRenUMCTDQxSdZSo5", 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); + } + + @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/com/dogecoin/dogecoinj/core/DumpedPrivateKeyTest.java b/core/src/test/java/com/dogecoin/dogecoinj/core/DumpedPrivateKeyTest.java index d0f76799..d6eb25f0 100644 --- a/core/src/test/java/com/dogecoin/dogecoinj/core/DumpedPrivateKeyTest.java +++ b/core/src/test/java/com/dogecoin/dogecoinj/core/DumpedPrivateKeyTest.java @@ -17,6 +17,7 @@ package com.dogecoin.dogecoinj.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/com/dogecoin/dogecoinj/core/PeerGroupTest.java b/core/src/test/java/com/dogecoin/dogecoinj/core/PeerGroupTest.java index 4d919288..12c3ea61 100644 --- a/core/src/test/java/com/dogecoin/dogecoinj/core/PeerGroupTest.java +++ b/core/src/test/java/com/dogecoin/dogecoinj/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(); @@ -698,6 +698,31 @@ public class PeerGroupTest extends TestWithPeerGroup { assertTrue(future.isDone()); } + @Test + public void waitForPeersWithServiceFlags() throws Exception { + ListenableFuture