Merge remote-tracking branch 'upstream/master'

Conflicts:
	core/src/main/java/com/dogecoin/dogecoinj/core/PeerGroup.java
	core/src/main/java/com/dogecoin/dogecoinj/core/TransactionBroadcast.java
	core/src/main/java/com/dogecoin/dogecoinj/core/Wallet.java
	core/src/main/java/com/dogecoin/dogecoinj/kits/WalletAppKit.java
	core/src/main/java/com/dogecoin/dogecoinj/net/discovery/DnsDiscovery.java
	core/src/main/java/com/dogecoin/dogecoinj/store/SPVBlockStore.java
	core/src/main/java/org/bitcoinj/net/discovery/HttpDiscovery.java
	wallettemplate/src/main/java/wallettemplate/Main.java
This commit is contained in:
langerhans
2015-02-12 23:15:09 +01:00
39 changed files with 764 additions and 190 deletions

View File

@@ -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

View File

@@ -168,4 +168,12 @@ public class Address extends VersionedChecksummedBytes {
}
return false;
}
/**
* This implementation narrows the return type to <code>Address</code>.
*/
@Override
public Address clone() throws CloneNotSupportedException {
return (Address) super.clone();
}
}

View File

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

View File

@@ -22,8 +22,17 @@ import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
/**
* This command is supported only by <a href="http://github.com/bitcoinxt/bitcoinxt">Bitcoin XT</a> 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<TransactionOutPoint> outPoints;

View File

@@ -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;
}

View File

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

View File

@@ -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

View File

@@ -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;
* <p>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.</p>
*
* <p>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.</p>
* <p>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.</p>
*/
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<SocketAddress> 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<Peer> 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<List<Peer>> waitForPeersWithServiceMask(final int numPeers, final int mask) {
lock.lock();
try {
List<Peer> foundPeers = findPeersWithServiceMask(mask);
if (foundPeers.size() >= numPeers)
return Futures.immediateFuture(foundPeers);
final SettableFuture<List<Peer>> future = SettableFuture.create();
addEventListener(new AbstractPeerEventListener() {
@Override
public void onPeerConnected(Peer peer, int peerCount) {
final List<Peer> 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<Peer> findPeersWithServiceMask(int mask) {
lock.lock();
try {
ArrayList<Peer> results = new ArrayList<Peer>(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

View File

@@ -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()));
}
/**

View File

@@ -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<Peer, RejectMessage> rejects = Collections.synchronizedMap(new HashMap<Peer, RejectMessage>());
// 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<Transaction> 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" : "");

View File

@@ -22,6 +22,7 @@ import java.io.Serializable;
import java.util.Arrays;
import com.google.common.base.Objects;
import com.google.common.primitives.UnsignedBytes;
/**
* <p>In Bitcoin the following format is often used to represent some type of key:</p>
@@ -31,7 +32,7 @@ import com.google.common.base.Objects;
* <p>and the result is then Base58 encoded. This format is used for addresses, and private keys exported using the
* dumpprivkey command.</p>
*/
public class VersionedChecksummedBytes implements Serializable {
public class VersionedChecksummedBytes implements Serializable, Cloneable, Comparable<VersionedChecksummedBytes> {
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 <code>VersionedChecksummedBytes</code>
* and allows subclasses to throw <code>CloneNotSupportedException</code> 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 <code>bytes</code>.
*/
@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.

View File

@@ -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

View File

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

View File

@@ -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.*;
/**
* <p>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);

View File

@@ -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<SocketAddress> openConnection(SocketAddress serverAddress, StreamParser parser);

View File

@@ -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();
}
};
}
}

View File

@@ -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).</p>
*/
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<PeerDiscovery> buildDiscoveries(NetworkParameters params, String[] seeds) {
List<PeerDiscovery> discoveries = new ArrayList<PeerDiscovery>(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<Callable<InetAddress[]>> tasks = Lists.newArrayList();
for (final String seed : dnsSeeds) {
tasks.add(new Callable<InetAddress[]>() {
@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<Future<InetAddress[]>> futures = threadPool.invokeAll(tasks, timeoutValue, timeoutUnit);
ArrayList<InetSocketAddress> addrs = Lists.newArrayList();
for (int i = 0; i < futures.size(); i++) {
Future<InetAddress[]> 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() {
}
}

View File

@@ -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() {
}
}

View File

@@ -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<PeerDiscovery> 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<PeerDiscovery> 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<Callable<InetSocketAddress[]>> tasks = Lists.newArrayList();
for (final PeerDiscovery seed : seeds) {
tasks.add(new Callable<InetSocketAddress[]>() {
@Override
public InetSocketAddress[] call() throws Exception {
return seed.getPeers(timeoutValue, timeoutUnit);
}
});
}
final List<Future<InetSocketAddress[]>> futures = vThreadPool.invokeAll(tasks, timeoutValue, timeoutUnit);
ArrayList<InetSocketAddress> addrs = Lists.newArrayList();
for (int i = 0; i < futures.size(); i++) {
Future<InetSocketAddress[]> 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();
}
}

View File

@@ -68,6 +68,6 @@ public class RegTestParams extends TestNet2Params {
@Override
public String getPaymentProtocolId() {
return null;
return PAYMENT_PROTOCOL_ID_REGTEST;
}
}

View File

@@ -58,6 +58,6 @@ public class UnitTestParams extends NetworkParameters {
@Override
public String getPaymentProtocolId() {
return null;
return "unittest";
}
}

View File

@@ -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) {

View File

@@ -0,0 +1,23 @@
package com.dogecoin.dogecoinj.store;
import sun.misc.*;
import sun.nio.ch.*;
import java.nio.*;
/**
* <p>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.</p>
*
* <p>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.</p>
*
* <p>It is a separate class to avoid hitting unknown imports when running on other JVMs.</p>
*/
public class WindowsMMapHack {
public static void forceRelease(MappedByteBuffer buffer) {
Cleaner cleaner = ((DirectBuffer) buffer).cleaner();
if (cleaner != null) cleaner.clean();
}
}

View File

@@ -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];

View File

@@ -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" +

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<List<Peer>> 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

View File

@@ -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<Transaction> 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

View File

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

View File

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

View File

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

View File

@@ -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<Integer, StoredBlock> checkpoints = new TreeMap<Integer, StoredBlock>();

View File

@@ -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.

View File

@@ -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.

View File

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