diff --git a/.travis.yml.unused b/.travis.yml.unused index 2b712442..0e97f2d4 100644 --- a/.travis.yml.unused +++ b/.travis.yml.unused @@ -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 diff --git a/core/src/main/java/com/dogecoin/dogecoinj/core/Address.java b/core/src/main/java/com/dogecoin/dogecoinj/core/Address.java index 45c19c5d..e81565fb 100644 --- a/core/src/main/java/com/dogecoin/dogecoinj/core/Address.java +++ b/core/src/main/java/com/dogecoin/dogecoinj/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/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 ImmutableList outPoints; diff --git a/core/src/main/java/com/dogecoin/dogecoinj/core/NetworkParameters.java b/core/src/main/java/com/dogecoin/dogecoinj/core/NetworkParameters.java index 0ec7a24b..cb238d78 100644 --- a/core/src/main/java/com/dogecoin/dogecoinj/core/NetworkParameters.java +++ b/core/src/main/java/com/dogecoin/dogecoinj/core/NetworkParameters.java @@ -64,6 +64,9 @@ 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"; + public static final String PAYMENT_PROTOCOL_ID_REGTEST = "regtest"; // TODO: Seed nodes should be here as well. @@ -233,6 +236,10 @@ 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 if (pmtProtocolId.equals(PAYMENT_PROTOCOL_ID_REGTEST)) { + return RegTestParams.get(); } else { return null; } diff --git a/core/src/main/java/com/dogecoin/dogecoinj/core/Peer.java b/core/src/main/java/com/dogecoin/dogecoinj/core/Peer.java index 07825add..58c8e46e 100644 --- a/core/src/main/java/com/dogecoin/dogecoinj/core/Peer.java +++ b/core/src/main/java/com/dogecoin/dogecoinj/core/Peer.java @@ -282,6 +282,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) { @@ -414,9 +422,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); } } diff --git a/core/src/main/java/com/dogecoin/dogecoinj/core/PeerEventListener.java b/core/src/main/java/com/dogecoin/dogecoinj/core/PeerEventListener.java index 04c205d2..6d4ecd64 100644 --- a/core/src/main/java/com/dogecoin/dogecoinj/core/PeerEventListener.java +++ b/core/src/main/java/com/dogecoin/dogecoinj/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/com/dogecoin/dogecoinj/core/PeerGroup.java b/core/src/main/java/com/dogecoin/dogecoinj/core/PeerGroup.java index 5470f304..cb89b31e 100644 --- a/core/src/main/java/com/dogecoin/dogecoinj/core/PeerGroup.java +++ b/core/src/main/java/com/dogecoin/dogecoinj/core/PeerGroup.java @@ -19,6 +19,7 @@ package com.dogecoin.dogecoinj.core; import com.dogecoin.dogecoinj.params.MainNetParams; 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; @@ -70,17 +71,22 @@ 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); - 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); + 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); @@ -1244,10 +1263,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. @@ -1385,7 +1404,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; @@ -1398,7 +1417,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); @@ -1416,11 +1435,12 @@ 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"); } + } else { backoffMap.get(address).trackFailure(); // Put back on inactive list inactives.offer(address); @@ -1502,7 +1522,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(); @@ -1517,11 +1537,58 @@ 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) { + 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; + } finally { + lock.unlock(); + } + } + + /** + * 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 * 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 com.dogecoin.dogecoinj.core.PeerGroup#getMaxConnections()} returns is used. */ public int getMinBroadcastConnections() { @@ -1532,7 +1599,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 { @@ -1609,9 +1676,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 diff --git a/core/src/main/java/com/dogecoin/dogecoinj/core/RejectMessage.java b/core/src/main/java/com/dogecoin/dogecoinj/core/RejectMessage.java index 188491fb..f039a7b2 100644 --- a/core/src/main/java/com/dogecoin/dogecoinj/core/RejectMessage.java +++ b/core/src/main/java/com/dogecoin/dogecoinj/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())); } /** diff --git a/core/src/main/java/com/dogecoin/dogecoinj/core/TransactionBroadcast.java b/core/src/main/java/com/dogecoin/dogecoinj/core/TransactionBroadcast.java index c43d3571..196eca92 100644 --- a/core/src/main/java/com/dogecoin/dogecoinj/core/TransactionBroadcast.java +++ b/core/src/main/java/com/dogecoin/dogecoinj/core/TransactionBroadcast.java @@ -16,18 +16,14 @@ package com.dogecoin.dogecoinj.core; -import com.dogecoin.dogecoinj.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 com.dogecoin.dogecoinj.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; @@ -83,7 +89,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 +127,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 { @@ -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/main/java/com/dogecoin/dogecoinj/core/VersionedChecksummedBytes.java b/core/src/main/java/com/dogecoin/dogecoinj/core/VersionedChecksummedBytes.java index 23388555..ec77362e 100644 --- a/core/src/main/java/com/dogecoin/dogecoinj/core/VersionedChecksummedBytes.java +++ b/core/src/main/java/com/dogecoin/dogecoinj/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 { +public class VersionedChecksummedBytes implements Serializable, Cloneable, Comparable { protected final int version; protected byte[] bytes; @@ -79,6 +80,34 @@ public class VersionedChecksummedBytes implements Serializable { && Arrays.equals(this.bytes, other.bytes); } + /** + * {@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(); + } + + /** + * {@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 openConnection(SocketAddress serverAddress, StreamParser parser); diff --git a/core/src/main/java/com/dogecoin/dogecoinj/net/NioClientManager.java b/core/src/main/java/com/dogecoin/dogecoinj/net/NioClientManager.java index 87f5370c..32253f5b 100644 --- a/core/src/main/java/com/dogecoin/dogecoinj/net/NioClientManager.java +++ b/core/src/main/java/com/dogecoin/dogecoinj/net/NioClientManager.java @@ -17,10 +17,7 @@ package com.dogecoin.dogecoinj.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(); + } + }; + } } diff --git a/core/src/main/java/com/dogecoin/dogecoinj/net/discovery/DnsDiscovery.java b/core/src/main/java/com/dogecoin/dogecoinj/net/discovery/DnsDiscovery.java index b7c5aa6b..19d4b122 100644 --- a/core/src/main/java/com/dogecoin/dogecoinj/net/discovery/DnsDiscovery.java +++ b/core/src/main/java/com/dogecoin/dogecoinj/net/discovery/DnsDiscovery.java @@ -17,14 +17,10 @@ package com.dogecoin.dogecoinj.net.discovery; -import com.dogecoin.dogecoinj.core.NetworkParameters; -import com.google.common.collect.Lists; -import com.dogecoin.dogecoinj.utils.DaemonThreadFactory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import com.dogecoin.dogecoinj.core.*; +import com.dogecoin.dogecoinj.utils.*; -import java.net.InetAddress; -import java.net.InetSocketAddress; +import java.net.*; import java.util.*; import java.util.concurrent.*; @@ -39,12 +35,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 +49,54 @@ 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)); + } + + 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; } @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"); + 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()); + } - // 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/com/dogecoin/dogecoinj/net/discovery/HttpDiscovery.java b/core/src/main/java/com/dogecoin/dogecoinj/net/discovery/HttpDiscovery.java new file mode 100644 index 00000000..23cca6a1 --- /dev/null +++ b/core/src/main/java/com/dogecoin/dogecoinj/net/discovery/HttpDiscovery.java @@ -0,0 +1,98 @@ +/** + * 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 com.dogecoin.dogecoinj.net.discovery; + +import com.google.common.annotations.VisibleForTesting; +import com.google.protobuf.InvalidProtocolBufferException; +import org.bitcoin.crawler.PeerSeedProtos; +import com.dogecoin.dogecoinj.core.*; + +import javax.annotation.Nullable; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.InetSocketAddress; +import java.net.URI; +import java.security.SignatureException; +import java.util.Arrays; +import java.util.concurrent.TimeUnit; +import java.util.zip.GZIPInputStream; + +import static com.google.common.base.Preconditions.checkArgument; + +/** + * A class that knows how to read signed sets of seeds over HTTP, using a simple protobuf based protocol. See the + * peerseeds.proto file for the definition, with a gzipped delimited SignedPeerSeeds being the root of the data. + * 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; + + /** + * Constructs a discovery object that will read data from the given HTTP[S] URI and, if a public key is provided, + * will check the signature using that key. + */ + public HttpDiscovery(NetworkParameters params, URI uri, @Nullable ECKey pubkey) { + checkArgument(uri.getScheme().startsWith("http")); + this.uri = uri; + this.pubkey = pubkey; + this.params = params; + } + + @Override + 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", "dogecoinj " + VersionMessage.BITCOINJ_VERSION); + InputStream stream = conn.getInputStream(); + GZIPInputStream zip = new GZIPInputStream(stream); + PeerSeedProtos.SignedPeerSeeds proto = PeerSeedProtos.SignedPeerSeeds.parseDelimitedFrom(zip); + stream.close(); + return protoToAddrs(proto); + } catch (Exception e) { + throw new PeerDiscoveryException(e); + } + } + + @VisibleForTesting + public InetSocketAddress[] protoToAddrs(PeerSeedProtos.SignedPeerSeeds proto) throws PeerDiscoveryException, InvalidProtocolBufferException, SignatureException { + if (pubkey != null) { + if (!Arrays.equals(proto.getPubkey().toByteArray(), pubkey.getPubKey())) + throw new PeerDiscoveryException("Public key mismatch"); + Sha256Hash hash = Sha256Hash.create(proto.getPeerSeeds().toByteArray()); + pubkey.verifyOrThrow(hash.getBytes(), proto.getSignature().toByteArray()); + } + PeerSeedProtos.PeerSeeds seeds = PeerSeedProtos.PeerSeeds.parseFrom(proto.getPeerSeeds()); + if (seeds.getTimestamp() < Utils.currentTimeSeconds() - (60 * 60 * 24)) + throw new PeerDiscoveryException("Seed data is more than one day old: replay attack?"); + if (!seeds.getNet().equals(params.getPaymentProtocolId())) + throw new PeerDiscoveryException("Network mismatch"); + InetSocketAddress[] results = new InetSocketAddress[seeds.getSeedCount()]; + int i = 0; + for (PeerSeedProtos.PeerSeedData data : seeds.getSeedList()) + results[i++] = new InetSocketAddress(data.getIpAddress(), data.getPort()); + return results; + } + + @Override + public void shutdown() { + } +} diff --git a/core/src/main/java/com/dogecoin/dogecoinj/net/discovery/MultiplexingDiscovery.java b/core/src/main/java/com/dogecoin/dogecoinj/net/discovery/MultiplexingDiscovery.java new file mode 100644 index 00000000..d7fde305 --- /dev/null +++ b/core/src/main/java/com/dogecoin/dogecoinj/net/discovery/MultiplexingDiscovery.java @@ -0,0 +1,106 @@ +/** + * 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 com.dogecoin.dogecoinj.net.discovery; + +import com.google.common.collect.Lists; +import com.dogecoin.dogecoinj.core.NetworkParameters; +import com.dogecoin.dogecoinj.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 = createExecutor(); + 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(); + } + } + + protected ExecutorService createExecutor() { + return Executors.newFixedThreadPool(seeds.size(), new DaemonThreadFactory()); + } + + @Override + public void shutdown() { + ExecutorService tp = vThreadPool; + if (tp != null) + tp.shutdown(); + } +} diff --git a/core/src/main/java/com/dogecoin/dogecoinj/params/RegTestParams.java b/core/src/main/java/com/dogecoin/dogecoinj/params/RegTestParams.java index 461eb78c..30305496 100644 --- a/core/src/main/java/com/dogecoin/dogecoinj/params/RegTestParams.java +++ b/core/src/main/java/com/dogecoin/dogecoinj/params/RegTestParams.java @@ -68,6 +68,6 @@ public class RegTestParams extends TestNet2Params { @Override public String getPaymentProtocolId() { - return null; + return PAYMENT_PROTOCOL_ID_REGTEST; } } diff --git a/core/src/main/java/com/dogecoin/dogecoinj/params/UnitTestParams.java b/core/src/main/java/com/dogecoin/dogecoinj/params/UnitTestParams.java index 03257eb2..1f5a683e 100644 --- a/core/src/main/java/com/dogecoin/dogecoinj/params/UnitTestParams.java +++ b/core/src/main/java/com/dogecoin/dogecoinj/params/UnitTestParams.java @@ -58,6 +58,6 @@ public class UnitTestParams extends NetworkParameters { @Override public String getPaymentProtocolId() { - return null; + return "unittest"; } } diff --git a/core/src/main/java/com/dogecoin/dogecoinj/store/SPVBlockStore.java b/core/src/main/java/com/dogecoin/dogecoinj/store/SPVBlockStore.java index dd93f3ee..52d8e3b6 100644 --- a/core/src/main/java/com/dogecoin/dogecoinj/store/SPVBlockStore.java +++ b/core/src/main/java/com/dogecoin/dogecoinj/store/SPVBlockStore.java @@ -17,26 +17,19 @@ package com.dogecoin.dogecoinj.store; import com.dogecoin.dogecoinj.core.*; -import com.dogecoin.dogecoinj.utils.Threading; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import com.dogecoin.dogecoinj.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,6 +262,10 @@ 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(); } catch (IOException e) { diff --git a/core/src/main/java/com/dogecoin/dogecoinj/store/WindowsMMapHack.java b/core/src/main/java/com/dogecoin/dogecoinj/store/WindowsMMapHack.java new file mode 100644 index 00000000..37b1421e --- /dev/null +++ b/core/src/main/java/com/dogecoin/dogecoinj/store/WindowsMMapHack.java @@ -0,0 +1,23 @@ +package com.dogecoin.dogecoinj.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(); + } +} 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> 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 diff --git a/core/src/test/java/com/dogecoin/dogecoinj/core/TransactionBroadcastTest.java b/core/src/test/java/com/dogecoin/dogecoinj/core/TransactionBroadcastTest.java index f504f6a1..9b57af16 100644 --- a/core/src/test/java/com/dogecoin/dogecoinj/core/TransactionBroadcastTest.java +++ b/core/src/test/java/com/dogecoin/dogecoinj/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 com.dogecoin.dogecoinj.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 diff --git a/core/src/test/java/com/dogecoin/dogecoinj/core/VersionedChecksummedBytesTest.java b/core/src/test/java/com/dogecoin/dogecoinj/core/VersionedChecksummedBytesTest.java new file mode 100644 index 00000000..0db3b1aa --- /dev/null +++ b/core/src/test/java/com/dogecoin/dogecoinj/core/VersionedChecksummedBytesTest.java @@ -0,0 +1,62 @@ +/** + * 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 com.dogecoin.dogecoinj.core; + +import com.dogecoin.dogecoinj.params.MainNetParams; +import com.dogecoin.dogecoinj.params.TestNet3Params; +import org.junit.Test; + +import static com.dogecoin.dogecoinj.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); + } + + @Test + public void comparisonCloneEqualTo() throws Exception { + VersionedChecksummedBytes a = new VersionedChecksummedBytes(testParams.getAddressHeader(), HEX.decode("fda79a24e50ff70ff42f7d89585da5bd19d9e5cc")); + VersionedChecksummedBytes b = a.clone(); + + assertTrue(a.compareTo(b) == 0); + } +} diff --git a/core/src/test/java/com/dogecoin/dogecoinj/core/WalletTest.java b/core/src/test/java/com/dogecoin/dogecoinj/core/WalletTest.java index 897365bd..20bf0211 100644 --- a/core/src/test/java/com/dogecoin/dogecoinj/core/WalletTest.java +++ b/core/src/test/java/com/dogecoin/dogecoinj/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); diff --git a/core/src/test/java/com/dogecoin/dogecoinj/crypto/BIP38PrivateKeyTest.java b/core/src/test/java/com/dogecoin/dogecoinj/crypto/BIP38PrivateKeyTest.java index daf70efb..4c9336bc 100644 --- a/core/src/test/java/com/dogecoin/dogecoinj/crypto/BIP38PrivateKeyTest.java +++ b/core/src/test/java/com/dogecoin/dogecoinj/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); + } + } diff --git a/tools/src/main/java/com/dogecoin/dogecoinj/tools/BuildCheckpoints.java b/tools/src/main/java/com/dogecoin/dogecoinj/tools/BuildCheckpoints.java index cdbb7027..8741c48d 100644 --- a/tools/src/main/java/com/dogecoin/dogecoinj/tools/BuildCheckpoints.java +++ b/tools/src/main/java/com/dogecoin/dogecoinj/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(); diff --git a/tools/src/main/java/com/dogecoin/dogecoinj/tools/WalletTool.java b/tools/src/main/java/com/dogecoin/dogecoinj/tools/WalletTool.java index a66818d5..c5f6ed12 100644 --- a/tools/src/main/java/com/dogecoin/dogecoinj/tools/WalletTool.java +++ b/tools/src/main/java/com/dogecoin/dogecoinj/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/com/dogecoin/dogecoinj/tools/wallet-tool-help.txt b/tools/src/main/resources/com/dogecoin/dogecoinj/tools/wallet-tool-help.txt index 662fe181..f16b011b 100644 --- a/tools/src/main/resources/com/dogecoin/dogecoinj/tools/wallet-tool-help.txt +++ b/tools/src/main/resources/com/dogecoin/dogecoinj/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. diff --git a/wallettemplate/src/main/java/wallettemplate/Main.java b/wallettemplate/src/main/java/wallettemplate/Main.java index 0e0edd7e..97cc00a8 100644 --- a/wallettemplate/src/main/java/wallettemplate/Main.java +++ b/wallettemplate/src/main/java/wallettemplate/Main.java @@ -1,10 +1,9 @@ package wallettemplate; +import com.google.common.util.concurrent.*; import com.dogecoin.dogecoinj.core.NetworkParameters; import com.dogecoin.dogecoinj.kits.WalletAppKit; -import com.dogecoin.dogecoinj.params.MainNetParams; -import com.dogecoin.dogecoinj.params.RegTestParams; -import com.dogecoin.dogecoinj.params.TestNet3Params; +import com.dogecoin.dogecoinj.params.*; import com.dogecoin.dogecoinj.utils.BriefLogFormatter; import com.dogecoin.dogecoinj.utils.Threading; import com.dogecoin.dogecoinj.wallet.DeterministicSeed; @@ -30,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; @@ -96,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); } };